Compare commits

...

90 Commits

Author SHA1 Message Date
bert hubert c27b3466e6 added new link to GPS status 2022-06-07 12:20:21 +02:00
bert hubert 21443e89cf remove scary use of memset 2022-03-07 14:03:36 +01:00
bert hubert 0427adb27b comment unused code and variables 2022-03-07 14:03:11 +01:00
bert hubert 290721f1c0 add missing include 2022-03-07 14:02:24 +01:00
bert hubert eed416e30b new doctest to fix compilation 2022-03-07 13:56:50 +01:00
bert hubert 7c9127ab73 html updates for new URLs 2022-03-07 13:10:16 +01:00
bert hubert c10bce3a2c quiet down navdump a lot 2022-03-07 13:10:16 +01:00
bert hubert f4256dac8b Merge branch 'master' of github.com:berthubert/galmon 2021-11-13 13:00:07 +01:00
bert hubert 13325316de fix compilation on fedora 35 2021-11-13 12:59:53 +01:00
bert hubert 1837fd0bea -pthread should unfuck the testsuite 2021-09-22 20:09:07 +02:00
bert hubert f5d29a3087 Merge branch 'master' of github.com:berthubert/galmon 2021-09-21 17:31:46 +02:00
bert hubert 8686561404 enhance GGTO logging somewhat 2021-09-21 17:31:40 +02:00
bert hubert 95a41e9612 ok 2021-09-21 17:30:57 +02:00
bert hubert 155f4c6e72 fix galileo/gps time offset calculations for week number rollovers 2021-09-21 17:26:32 +02:00
bert hubert e1b8225ddb get navdump to print beidou leap second data 2021-08-03 00:00:47 +02:00
bert hubert 025427c21d parse BeiDou leap second data 2021-08-03 00:00:29 +02:00
bert hubert 616e71270d make gndate do BeiDou week numbers etc 2021-08-03 00:00:10 +02:00
bert hubert 07c07dd2f1 add getBeiDouDate and note some more leap second errors 2021-08-02 23:59:49 +02:00
bert hubert aa8c3b20e3 add galileo leap second stuff 2021-08-02 20:45:59 +02:00
bert hubert 3a28c48ce2 parse & show some more GPS leap second bits 2021-07-24 20:57:01 +02:00
bert hubert 21df895835 pretty print C/NAV a bit better 2021-05-25 13:55:41 +02:00
bert hubert 8ddeff2a6b fix CNAV parsing for HAS 2021-05-25 13:55:17 +02:00
bert hubert 4860006fdc improve OSX instructions 2021-03-27 17:44:12 +01:00
bert hubert 7134316efe add OSX instructions 2021-03-27 17:12:25 +01:00
bert hubert 30be749712 move osnma dump so it dumps on all word types 2021-03-27 14:33:28 +01:00
bert hubert bedcc4dc33 remover buffering 2021-02-16 22:12:07 +01:00
bert hubert 88d6773a29 add --osnma fname.csv 2021-02-16 21:53:15 +01:00
bert hubert a88f6034f1 add --rinex to navdump, will emit ephemerides as RINEX 3.0.3 2021-02-16 17:49:23 +01:00
bert hubert f1746adabb add OSNMA parsing to septentrio tooling 2021-02-11 22:09:44 +01:00
bert hubert 6a515aaef0 stop filtering out E14/E18, add support for DVS, add support for remote influxdb 2020-12-06 14:30:33 +01:00
bert hubert a291a0ff03 silence some warning, add special E14/E18 support 2020-12-06 14:29:32 +01:00
bert hubert 233b201a76 add osnma tweets 2020-11-18 12:52:41 +01:00
bert hubert 085a3e93c9 docs 2020-11-15 12:48:04 +01:00
bert hubert 1feaa97638 silence debugging 2020-11-15 12:47:41 +01:00
bert hubert 83afbe125d make sure we display Galileo E6 C/NAV sv id correctly 2020-11-15 12:44:34 +01:00
bert hubert f971a8b3b0 silence navnexus debugging output 2020-11-12 12:29:57 +01:00
bert hubert 8179fca981 add client logging for navrecv 2020-11-12 12:20:48 +01:00
bert hubert 27ee8daae2 tighten Makefile a bit 2020-11-12 12:04:10 +01:00
bert hubert cfefdc6814 add osnma output 2020-11-11 16:49:16 +01:00
bert hubert 4b537c593c OSNMA handling 2020-11-11 16:47:19 +01:00
bert hubert ffe82326e7 add commented out nequick handling 2020-11-11 16:45:25 +01:00
bert hubert ce9aed389d small cleanup, plus add storage of phase bias to influxdb 2020-11-11 16:44:32 +01:00
bert hubert 7da22078a4 phase bias parsing 2020-11-11 16:43:01 +01:00
bert hubert ed8ff0e823 add output of phase information 2020-11-11 16:41:53 +01:00
bert hubert ef62317f8c change defaul url 2020-11-11 16:41:28 +01:00
bert hubert 04efe69238 Merge branch 'master' of github.com:berthubert/galmon 2020-11-11 16:40:47 +01:00
bert hubert 34b7acbcbc update the hardcoded septentrio serial number, plus add compression 2020-11-11 16:40:24 +01:00
berthubert 4b8fd97dca
Merge pull request #109 from philcrump/phil-fix-uptime
ubxtool.cc: Use monotonic clock for uptime calculation.
2020-10-13 21:47:56 +02:00
bert hubert 37077b8296 various speedups, C/NAV support for Galileo 2020-09-27 16:49:59 +02:00
bert hubert 5d1cc3d75f teach navcat to combine different directories 2020-09-27 16:43:53 +02:00
bert hubert 386ad8dd7d add more time formats 2020-09-27 16:12:39 +02:00
bert hubert bccc3eb63a remove some dead code 2020-09-27 16:11:34 +02:00
bert hubert 749d8bf687 teach reporter about galileo week numbers, improve LaTeX output 2020-09-27 16:07:29 +02:00
bert hubert 71086169f5 silence a warning 2020-09-27 16:06:55 +02:00
bert hubert 6a963b181c add option to output bare numbers for use in scripts 2020-09-27 16:04:57 +02:00
bert hubert 42c46f532f add OSNMA output as 'res1' 2020-08-23 17:02:38 +02:00
berthubert 94009f150f
Merge pull request #139 from gizmoguy/actions-fix
Fix apt errors in ccpp github action.
2020-08-23 00:12:32 +02:00
Brad Cowie c1ba585937 Fix apt errors in ccpp github action. 2020-08-23 10:04:30 +12:00
bert hubert cab1ec9594 add OSNMA and other fields to Galileo storage 2020-08-22 22:31:31 +02:00
berthubert 8eff5956b2
Merge pull request #137 from gizmoguy/multi-stage-docker
Use multiple stages to build docker image
2020-08-05 23:01:12 +02:00
Brad Cowie 42810562f8 Update docker documentation in README.md. 2020-08-05 20:51:38 +12:00
Brad Cowie 06abf58801 Use multi-stage docker build to reduce image size. 2020-08-05 20:51:35 +12:00
bert hubert b1f0ad01d1 add navmerge for HA operations 2020-08-04 10:00:49 +02:00
bert hubert 5b03310e62
Merge pull request #127 from gizmoguy/docker-image-name
Update official docker image name in README.md.
2020-08-04 09:51:46 +02:00
bert hubert 9955463c93 teach nmmsender to also transmit already serialized messages 2020-08-03 23:20:17 +02:00
bert hubert aed36e1bf8 note a potential bug 2020-08-03 23:19:43 +02:00
bert hubert b032867261 implement sourcing from an IP address, plus fix C/NAV bug 2020-08-03 23:16:33 +02:00
bert hubert e5820ec0f7 error in sp3feed example 2020-08-03 23:16:01 +02:00
bert hubert ab436b0fb3 add Galileo E5a to HTML and Javascript 2020-08-01 21:59:49 +02:00
bert hubert 58cb1a6335 silence some more javascript debugging 2020-08-01 21:59:21 +02:00
bert hubert fbf213da70 silence some javascript debugging 2020-08-01 21:56:26 +02:00
bert hubert 761a896938 add C/NAV to septool, plus additional signal types 2020-08-01 21:50:08 +02:00
bert hubert ac3494541c make navparse stay quiet over Galileo C/NAV types 2020-08-01 21:19:16 +02:00
bert hubert 74db87012c add Galileo C/NAV type, and support for it to navdump 2020-08-01 21:19:01 +02:00
bert hubert 85fc93bb03 document septool more 2020-07-29 20:49:10 +02:00
bert hubert d4d97181f9 document septool 2020-07-29 20:41:15 +02:00
bert hubert a555418542 include galileo.o for F/NAV to navparse 2020-07-29 19:53:59 +02:00
bert hubert 000e15e761 implement F/NAV messages in navparse.cc 2020-07-29 19:46:51 +02:00
bert hubert b1427472cd make navdump print F/NAV messages 2020-07-29 11:39:23 +02:00
bert hubert f96df29038 septool! 2020-07-29 11:38:32 +02:00
bert hubert b500182649 Septentrio SBF tool 2020-07-29 11:37:51 +02:00
bert hubert 84cc20f019 F/NAV message type 2020-07-29 11:36:28 +02:00
bert hubert 9950905ba8 add unsigned char hexdump function 2020-07-29 11:31:55 +02:00
bert hubert ee1dd794aa missing commit for IOD 2020-07-29 11:18:33 +02:00
bert hubert dc531ca4a6 make rinjoin use the IOD field 2020-07-29 11:12:04 +02:00
bert hubert 72f789198a teach rinex ephemeris parser about IOD 2020-07-29 11:11:46 +02:00
bert hubert edce2900bb Galileo F/NAV parser 2020-07-29 11:11:25 +02:00
bert hubert a15ff4da7d add sp3feed description 2020-07-24 13:35:08 +02:00
Brad Cowie bf66486f63 Update official docker image name in README.md. 2020-07-10 20:56:28 +12:00
Phil Crump 4095e911d8 ubxtool.cc: Use monotonic clock for uptime calculation. 2020-02-22 23:28:14 +00:00
49 changed files with 4026 additions and 1374 deletions

View File

@ -4,15 +4,15 @@ on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
submodules: "recursive"
- name: deps
run: sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
- name: submodules
run: git submodule update --init --recursive
run: |
sudo apt-get update
sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
- name: config
run: echo WSLAY=-lwslay > Makefile.local
- name: make

View File

@ -24,7 +24,7 @@ jobs:
- name: Run buildx
run: |
docker buildx build \
--tag galmon/galmon \
--tag berthubert/galmon \
--platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 \
--output "type=registry" \
--build-arg MAKE_FLAGS=-j1 \

View File

@ -1,25 +1,38 @@
FROM ubuntu:eoan
#
# First stage - builder
#
FROM debian:10-slim AS builder
ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C.UTF-8
# This allows you to use a local Ubuntu mirror
ARG APT_URL=
ENV APT_URL ${APT_URL:-http://archive.ubuntu.com/ubuntu/}
RUN sed -i "s%http://archive.ubuntu.com/ubuntu/%${APT_URL}%" /etc/apt/sources.list
# This allows you to use a local Debian mirror
ARG APT_URL=http://deb.debian.org/debian/
ARG MAKE_FLAGS=-j2
# Update packages and install dependencies
RUN apt-get update && apt-get -y upgrade && apt-get -y clean
RUN apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libeigen3-dev libzstd-dev \
make gcc g++ git build-essential curl autoconf automake libfmt-dev libncurses5-dev \
&& apt-get -y clean
RUN sed -i "s%http://deb.debian.org/debian/%${APT_URL}%" /etc/apt/sources.list \
&& apt-get update && apt-get -y upgrade \
&& apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev \
libeigen3-dev libzstd-dev libfmt-dev libncurses-dev \
make gcc g++ git build-essential curl autoconf automake help2man
# Build
ARG MAKE_FLAGS=-j2
ADD . /galmon/
WORKDIR /galmon/
RUN make $MAKE_FLAGS
ENV PATH=/galmon:${PATH}
ADD . /galmon-src/
RUN cd /galmon-src/ \
&& make $MAKE_FLAGS \
&& prefix=/galmon make install
#
# Second stage - contains just the binaries
#
FROM debian:10-slim
RUN apt-get update && apt-get -y upgrade \
&& apt-get install -y libcurl4 libssl1.1 libprotobuf17 libh2o-evloop0.13 \
libncurses6 \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /galmon/ /galmon/
ENV PATH=/galmon/bin:${PATH}
ENV LC_ALL C.UTF-8
WORKDIR /galmon/bin

View File

@ -1,18 +0,0 @@
#FROM ubuntu:disco
FROM arm32v7/debian:unstable
ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C.UTF-8
# Update packages and install dependencies
RUN apt-get update && apt-get -y upgrade && apt-get -y clean
RUN apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libeigen3-dev libzstd-dev \
make clang-9 git build-essential curl autoconf automake libfmt-dev libncurses5-dev \
&& apt-get -y clean
# Build
ADD . /galmon/
WORKDIR /galmon/
RUN make
ENV PATH=/galmon:${PATH}

View File

@ -25,7 +25,7 @@ endif
CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter sp3feed \
galmonmon rinreport rinjoin rtcmtool gndate
galmonmon rinreport rinjoin rtcmtool gndate septool navmerge
all: navmon.pb.cc $(PROGRAMS)
@ -76,7 +76,7 @@ download-raspbian-package:
decrypt: decrypt.o bits.o ext/fmt-6.1.2/src/format.o
$(CXX) -std=gnu++17 $^ -o $@
navparse: navparse.o ext/fmt-6.1.2/src/format.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o
navparse: navparse.o ext/fmt-6.1.2/src/format.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o galileo.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lh2o-evloop -lssl -lcrypto -lz -lcurl -lprotobuf $(WSLAY)
reporter: reporter.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o influxpush.o
@ -94,14 +94,14 @@ galmonmon: galmonmon.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) minicurl.o ub
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
navdump: navdump.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o ${EXTRADEP}
navdump: navdump.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o galileo.o ${EXTRADEP}
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz
navdisplay: navdisplay.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses
navnexus: navnexus.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o githash.o
navnexus: navnexus.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
navcat: navcat.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o
@ -111,6 +111,10 @@ navcat: navcat.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmo
navrecv: navrecv.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd
navmerge: navmerge.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o nmmsender.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
@ -128,8 +132,12 @@ rtcmtool: rtcmtool.o navmon.pb.o githash.o ext/fmt-6.1.2/src/format.o bits.o nm
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd
septool: navmon.pb.o septool.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd
testrunner: navmon.pb.o testrunner.o ubx.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o ephemeris.o sp3.o osen.o navmon.o rinex.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz -pthread
gndate: gndate.o githash.o ext/fmt-6.1.2/src/format.o navmon.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib

120
README.md
View File

@ -4,14 +4,17 @@ galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
Live website: https://galmon.eu/
Theoretically multi-vendor, although currently only the U-blox 8 and 9
chipsets are supported. Navilock NL-8012U receiver works really well, as
does the U-blox evaluation kit for the 8MT. In addition, many stations have
reported success with this very cheap [AliExpress sourced
device](https://www.aliexpress.com/item/32816656706.html). The best and
most high-end receiver, which does all bands, all the time, is the Ublox
F9P, several of us use the
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board.
Multi-vendor, with support for U-blox 8 and 9 chipsets and many Septentrio
devices. Navilock NL-8012U receiver works really well, as does the U-blox
evaluation kit for the 8MT. In addition, many stations have reported
success with this very cheap [AliExpress sourced
device](https://www.aliexpress.com/item/32816656706.html).
For ublox, there is good support for the F9P, several of us use the
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board. It adds
the Galileo E5b band.
Septentrio devices support even more bands.
An annotated presentation about our project aimed at GNSS professionals can
be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
@ -29,9 +32,10 @@ guidelines](https://github.com/ahupowerdns/galmon/blob/master/Operator.md).
Highlights
----------
* Support for Septentrio and U-blox.
* Processes raw frames/strings/words from GPS, GLONASS, BeiDou and Galileo
* All-band support (E1, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
so far, GPS L2C and Galileo E5a pending).
* All-band support (E1, E5a, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
so far.
* Calculate ephemeris positions
* Comparison of ephemerides to independent SP3 data to determine SISE
* Globally, locally, worst user location
@ -68,8 +72,8 @@ Goals:
Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD.
Build locally
-------------
Build locally (Linux, Debian, Ubuntu)
-------------------------------------
To get started, make sure you have a C++17 compiler (like g++ 8 or higher),
git, protobuf-compiler. Then run 'make ubxtool navdump' to build the
@ -79,7 +83,7 @@ To build everything, including the webserver, try:
```
apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev \
libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev g++
git clone https://github.com/ahupowerdns/galmon.git --recursive
cd galmon
make
@ -92,25 +96,41 @@ library installed. If you get an error about 'wslay', do the following, and run
echo WSLAY=-lwslay > Makefile.local
```
Building on OSX
---------------
With thanks to a contributor from Prague. First make sure you've installed
brew, which you can get [here](https://brew.sh/). Then do:
```
brew install protobuf lzlib zstd h2o eigen
```
And then:
```
git clone https://github.com/ahupowerdns/galmon.git --recursive
cd galmon
make
```
Running in Docker
-----------------
We publish official Docker images for galmon on
[docker hub](https://hub.docker.com/r/faucet/faucet) for multiple architectures.
[docker hub](https://hub.docker.com/r/berthubert/galmon) for multiple architectures.
To run a container with a shell in there (this will also expose a port so you
can view the UI too and assumes a ublox GPS device too -
you may need to tweak as necessary):
```
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 galmon/galmon
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 berthubert/galmon
```
Running a daemonized docker container reporting data to a remote server
might look like:
```
docker run -d --restart=always --device=/dev/ttyACM0 --name=galmon galmon/galmon /galmon/ubxtool --wait --port /dev/ttyACM0 --gps --galileo --glonass --destination [server] --station [station-id] --owner [owner]
docker run -d --restart=always --device=/dev/ttyACM0 --name=galmon berthubert/galmon ubxtool --wait --port /dev/ttyACM0 --gps --galileo --glonass --destination [server] --station [station-id] --owner [owner]
```
To make your docker container update automatically you could use a tool such as
@ -118,10 +138,15 @@ To make your docker container update automatically you could use a tool such as
Running
-------
On u-blox:
Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0
--station 1 --stdout --galileo | ./navparse --bind [::1]:10000`
For Septentrio, try: `nc 192.168.1.1 29000 | ./septool --station x --stdout |
./navparse --bind [::1]:10000`, assuming your Septentrio can be reached on
192.168.1.1.1 and you have defined an SBF stream on port 29000. For more
details, please see below.
Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and
you should be in business. ubxtool changes (non-permanently) the
configuration of your u-blox receiver so it emits the required frames for
@ -159,6 +184,10 @@ Tooling:
* ubxtool: can configure a u-blox 8 chipset, parses its output & will
convert it into a protbuf stream of GNSS NAV frames + metadata
Adds 64-bit timestamps plus origin information to each message
* septool: ingests the Septentrio binary format (SBF) and converts it to our
protobuf format. Supports same protocol as ubxtool.
* rtcmtool: ingest ntripclient output, decodes RTCM messages and converts
them to our protobuf format
* xtool: if you have another chipset, build something that extracts NAV
frames & metadata. Not done yet.
* navrecv: receives GNSS NAV frames and stores them on disk, split out per
@ -229,6 +258,47 @@ This also works for `navparse` for the pretty website and influx storage, `nc 12
if you have an influxdb running on localhost with a galileo database in there.
The default URL is http://127.0.0.1:29599/
Septentrio specifics
--------------------
Unlike `ubxtool`, our `septool` does not (re)configure your Septentrio
device. Instead, the tool expects Septentrio Binary Format (SBF) on input,
and that this stream includes at least the following messages:
* MeasEpoch
* PVTCartesian
We currently parse and understand:
* GALRawFNAV
* GALRawINAV
Support will be added soon for:
* GPSRawCA
* GPSRawL2C
* GPSRawL5
* GLORawCA
* BDSRaw
* BDSRawB1C
* BDSRawB2a
A typical invocation of `septool` looks like this:
```
nc 192.168.1.1 29000 | ./septool --station x --destination galmon-eu-server.example.com
```
Or to test, try:
```
nc 192.168.1.1 29000 | ./septool --station x --stdout | ./navdump
```
This is assuming that you can reach your Septentrio on 192.168.1.1 and that
you have defined a TCP server stream on port 29000.
Septool will also accept input from a serial port or basically anything that
can provide SBF. Please let us know if our tooling can make your life
easier.
Internals
---------
The transport format consists of repeats of:
@ -275,16 +345,26 @@ The software can interpret SP3 files, good sources:
to have less of a delay than the ESA ESM series.
* GBU = ultra rapid, still a few days delay, but much more recent.
Uncompress and concatenate all downloaded files into 'all.sp3' and run
'navdump ' on collected protobuf, and it will output 'sp3.csv' with fit data.
To get SP3 GBM from GFZ Potsdam for GPS week number 2111:
```
WN=2111
lftp -c "mget ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss/${WN}/gbm*sp3.Z"
gunzip gbm*sp3.Z
```
To feed data, use:
```
./sp3feed --sp3src=gbm --influxdb=galileo gbm*sp3
```
This will populate the sp3 tables in the database. A subsequent run of
`reporter`, while setting the `--sp3src` parameter, will provide SP3
deviation statistics, and fill out the `sp3delta` table in there, which
stores deviations from the SP3 provided position, per sp3src.
Further interesting (ephemeris) data is on http://mgex.igs.org/IGS_MGEX_Products.php
RTCM
----

View File

@ -228,7 +228,8 @@ struct BeidouMessage : GPSLikeEphemeris
// 2^-30 2^-50
int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc;
int8_t deltaTLS;
int8_t deltaTLS, deltaTLSF;
uint8_t wnLSF, dn;
// in Beidou the offset is a0utc + SOW * a1utc
std::pair<double, double> getUTCOffset(int tow) const
@ -268,6 +269,9 @@ struct BeidouMessage : GPSLikeEphemeris
a0utc = bbits(91, 32);
a1utc = bbits(131, 24);
deltaTLS = bbits(31+12+1+7, 8);
deltaTLSF = bbits(61+6, 8);
wnLSF = bbits(61+6+8, 8);
dn = bbits(151+12, 8);
}
else {
alma.sqrtA = bbitu(51, 24);

File diff suppressed because it is too large Load Diff

View File

@ -24,3 +24,91 @@ bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint
return false;
}
int GalileoMessage::parseFnav(std::basic_string_view<uint8_t> page)
{
const uint8_t* ptr = &page[0];
int offset=0;
auto gbum=[&ptr, &offset](int bits) {
unsigned int ret = getbitu(ptr, offset, bits);
offset += bits;
return ret;
};
auto gbsm=[&ptr, &offset](int bits) {
int ret = getbits(ptr, offset, bits);
offset += bits;
return ret;
};
wtype = gbum(6);
if(wtype == 1) {
/*int sv = */ (void)gbum(6);
iodnav = gbum(10);
t0c = gbum(14);
af0 = gbsm(31);
af1 = gbsm(21);
af2 = gbsm(6);
sisa = gbum(8);
ai0 = gbum(11);
ai1 = gbsm(11);
ai2 = gbsm(14);
sf1 = gbum(1);
sf2 = gbum(1);
sf3 = gbum(1);
sf4 = gbum(1);
sf5 = gbum(1);
BGDE1E5a = gbsm(10);
e5ahs = gbum(2);
wn = gbum(12);
tow = gbum(20);
e5advs=gbum(1);
}
else if(wtype==2) {
iodnav = gbum(10);
m0 = gbsm(32);
omegadot = gbsm(24);
e = gbum(32);
sqrtA = gbum(32);
omega0 = gbsm(32);
idot = gbsm(14);
wn = gbum(12);
tow = gbum(20);
}
else if(wtype == 3) {
iodnav = gbum(10);
i0 = gbsm(32);
omega = gbsm(32);
deltan = gbsm(16);
cuc = gbsm(16);
cus = gbsm(16);
crc =gbsm(16);
crs = gbsm(16);
t0e = gbum(14);
wn = gbum(12);
tow = gbum(20);
}
else if(wtype == 4) {
iodnav = gbum(10);
cic = gbsm(16);
cis = gbsm(16);
a0 = gbsm(32);
a1 = gbsm(24);
dtLS= gbsm(8);
t0t = gbum(8);
wn0t = gbum(8);
wnLSF = gbum(8);
dn = gbum(3);
dtLSF = gbsm(8);
t0g = gbum(8);
a0g = gbsm(16);
a1g = gbsm(12);
wn0g = gbum(6);
tow = gbum(20);
}
return wtype;
}

View File

@ -40,6 +40,8 @@ struct GalileoMessage : GPSLikeEphemeris
return wtype;
}
int parseFnav(std::basic_string_view<uint8_t> page);
uint8_t sparetime{0};
uint16_t wn{0};
uint32_t tow{0};
@ -63,13 +65,13 @@ struct GalileoMessage : GPSLikeEphemeris
{
}
uint8_t e5bhs{0}, e1bhs{0};
uint8_t e5ahs{0}, e5bhs{0}, e1bhs{0};
uint8_t gpshealth{0};
uint16_t ai0{0};
int16_t ai1{0}, ai2{0};
bool sf1{0}, sf2{0}, sf3{0}, sf4{0}, sf5{0};
int BGDE1E5a{0}, BGDE1E5b{0};
bool e5bdvs{false}, e1bdvs{false};
bool e5advs{false}, e5bdvs{false}, e1bdvs{false};
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
//
@ -224,7 +226,7 @@ struct GalileoMessage : GPSLikeEphemeris
{
int dw = (int)(wn%64) - (int)(wn0g%64);
if(dw > 31)
dw = 31- dw;
dw = dw - 64;
int delta = dw*7*86400 + tow - getT0g(); // NOT ephemeris age tricks
// 2^-35 2^-51 3600

View File

@ -1,4 +1,4 @@
#include <optional>
#include "minicurl.hh"
#include <iostream>
#include "navmon.hh"
@ -45,7 +45,6 @@ public:
std::optional<string> reportState(string_view thing, string_view name, var_t state, const std::string& state_text="");
std::optional<string> getState(string_view thing, string_view name);
std::optional<string> getPrevState(string_view thing, string_view name);
struct State
@ -166,23 +165,7 @@ std::optional<string> StateKeeper::reportState(string_view thing, string_view na
StateKeeper g_sk;
#if 0
static std::string string_replace(const std::string& str, const std::string& match,
const std::string& replacement, unsigned int max_replacements = UINT_MAX)
{
size_t pos = 0;
std::string newstr = str;
unsigned int replacements = 0;
while ((pos = newstr.find(match, pos)) != std::string::npos
&& replacements < max_replacements)
{
newstr.replace(pos, match.length(), replacement);
pos += replacement.length();
replacements++;
}
return newstr;
}
#endif
void sendTweet(const string& tweet)
{
string etweet = tweet;
@ -197,8 +180,8 @@ int main(int argc, char **argv)
{
MiniCurl mc;
MiniCurl::MiniCurlHeaders mch;
// string url="https://galmon.eu/svs.json";
string url="http://[::1]:29599/";
string url="https://galmon.eu/";
// string url="http://[::1]:29599/";
bool doVERSION{false};
CLI::App app(program);
@ -222,6 +205,7 @@ int main(int argc, char **argv)
g_sk.setBoolNames("health", "healthy", "unhealthy");
g_sk.setBoolNames("eph-too-old", "ephemeris fresh", "ephemeris aged");
g_sk.setBoolNames("silent", "observed", "not observed");
g_sk.setBoolNames("osnma", "OFF", "ON");
std::variant<bool, string> tst;
@ -273,7 +257,8 @@ int main(int argc, char **argv)
res = mc.getURL(url+"svs.json");
j = nlohmann::json::parse(res);
bool first=true;
bool first=true;
bool globalOsnma=false;
for(const auto& sv : j) {
if(!sv.count("gnssid") || !sv.count("fullName") || !sv.count("sigid")) {
cout<<"Skipping "<< sv.count("gnssid") <<", "<< sv.count("fullName") <<", " <<sv.count("sigid") <<endl;
@ -295,7 +280,8 @@ int main(int argc, char **argv)
// cout<<"Skipping "<<fullName<<" in loop: "<<sv.count("healthissue")<<", "<<sv.count("eph-age-m") << ", "<<sv.count("perrecv")<<endl;
continue;
}
for(const auto& recv : sv["perrecv"]) {
if(!recv.count("last-seen-s")) {
cout<<"Missing last-seen-s"<<endl;
@ -305,9 +291,11 @@ int main(int argc, char **argv)
numfresh++;
if((int)recv["last-seen-s"] < 3600)
notseen=false;
}
if(sv.count("osnma") && sv["osnma"]==true)
globalOsnma |= 1;
auto healthchange = g_sk.reportState(fullName, "health", sv["healthissue"]!=0);
std::optional<string> tooOldChange;
if(gnssid == 2)
@ -410,6 +398,17 @@ int main(int argc, char **argv)
cout<<humanTimeNow() <<" " << tweet << endl;
}
}
auto osnmachange = g_sk.reportState("global", "osnma", globalOsnma);
if(osnmachange) {
string tweet= "Galileo OSNMA new state: "+*osnmachange;
cout<<humanTimeNow()<< " " <<tweet<<endl;
if(doTweet) {
sendTweet(tweet);
}
}
cout<<".";
cout.flush();
}

View File

@ -110,16 +110,16 @@ static double passedMsec(const Clock::time_point& then, const Clock::time_point&
return std::chrono::duration_cast<std::chrono::microseconds>(now - then).count()/1000.0;
}
#if 0
static double passedMsec(const Clock::time_point& then)
{
return passedMsec(then, Clock::now());
}
#endif
double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
{
auto start = Clock::now();
// auto start = Clock::now();
double y0[6] = {ldexp(eph.x, -11), ldexp(eph.y, -11), ldexp(eph.z, -11),
ldexp(eph.dx, -20), ldexp(eph.dy, -20), ldexp(eph.dz, -20)};
@ -137,7 +137,7 @@ double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
rk4step (A, y0, h);
*p = Point (1E3*y0[0], 1E3*y0[1], 1E3*y0 [2]);
static double total=0;
// static double total=0;
// cout<<"Took: "<<(total+=passedMsec(start))<<" ms" <<endl;
return 0;
}

View File

@ -7,16 +7,21 @@ extern const char* g_gitHash;
using namespace std;
int main(int argc, char** argv)
try
{
string program("gndate");
CLI::App app(program);
string date;
bool doGPSWN{false}, doGALWN{false}, doVERSION{false}, doUTC{false};
int galwn{-1};
bool doProgOutput{false};
bool doGPSWN{false}, doGALWN{false}, doBEIDOUWN{false}, doVERSION{false}, doUTC{false};
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--date,-d", date, "yyyy-mm-dd hh:mm");
app.add_option("--date,-d", date, "yyyy-mm-dd hh:mm[:ss] hh:mm yyyymmdd hhmm");
app.add_option("--date-gal-wn", galwn, "Give data for this Galileo week number");
app.add_flag("--utc,-u", doUTC, "Interpret --date,-d as UTC");
app.add_flag("--gps-wn", doGPSWN, "Print GPS week number");
app.add_flag("--gal-wn", doGALWN, "Print GPS week number");
app.add_flag("--prog-output", doProgOutput, "Modulate some date formats for use as parameters to programs");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
@ -40,6 +45,15 @@ int main(int argc, char** argv)
int wn, tow;
if(galwn >= 0) {
time_t week=utcFromGST(galwn, 0);
if(doProgOutput)
cout<<influxTime(week) << endl;
else
cout<<humanTime(week)<< " - " << humanTime(week+7*86400) << endl;
return 0;
}
if(doGPSWN) {
getGPSDateFromUTC(now, wn, tow);
cout<<wn<<endl;
@ -48,10 +62,20 @@ int main(int argc, char** argv)
getGalDateFromUTC(now, wn, tow);
cout<<wn<<endl;
}
else if(doBEIDOUWN) {
getBeiDouDateFromUTC(now, wn, tow);
cout<<wn<<endl;
}
else {
getGPSDateFromUTC(now, wn, tow);
cout<<"GPS Week Number (non-wrapped): "<< wn << ", tow " << tow << endl;
getGalDateFromUTC(now, wn, tow);
cout<<"Galileo Week Number: "<< wn << ", tow " << tow << endl;
getBeiDouDateFromUTC(now, wn, tow);
cout<<"BeiDou Week Number: "<< wn << ", sow " << tow << endl;
}
}
catch(exception& e) {
cerr<<"Error: "<<e.what()<<endl;
}

4
gps.cc
View File

@ -96,8 +96,12 @@ int GPSState::parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pag
t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
wn0t = getbitu(&cond[0], 7*24 + 16, 8);
dtLS = getbits(&cond[0], 8*24, 8);
wnLSF= getbitu(&cond[0], 8*24 + 8, 8);
dn = getbitu(&cond[0], 8*24 + 16, 8);
dtLSF = getbits(&cond[0], 9*24, 8);
// cerr<<": a0: "<<a0<<", a1: "<<a1<<", t0t: "<< t0t * (1<<12) <<", wn0t: "<< wn0t<<", rough offset: "<<ldexp(a0, -30)<<endl;
// cerr<<"deltaTLS: "<< (int)dtLS<<", post "<< (int)dtLSF<<endl;

View File

@ -14,7 +14,7 @@ function maketable(str, arr)
enter().
append("tr");
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "delta-utc", "sources", "db", "rtcm-eph-delta-cm","rtcm-clock-dclock0", "prres", "elev", "last-seen-s"];
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "osnma", "sources", "db", "rtcm-eph-delta-cm","rtcm-clock-dclock0", "prres", "elev", "last-seen-s"];
// append the header row
thead.append("tr")
@ -61,7 +61,7 @@ function maketable(str, arr)
else if(row["gnssid"] == 6)
img='ext/glo.png';
ret.value = '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
ret.value = '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
// ret.value="";
ret.value += "&nbsp;<a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
}
@ -130,6 +130,13 @@ function maketable(str, arr)
else if(column == "norad") {
ret.value = row["best-tle-norad"];
}
else if(column == "osnma" && row["osnma"] != null) {
if(row["osnma"]==true)
ret.value="ON!";
else
ret.value="off";
}
else if(column == "delta-utc" && row["delta-utc"] != null) {
ret.value = row["delta-utc"]+'<span class="CellComment">a0: '+row["a0"]+'<br/>a1: '+row["a1"]+'<br/>wn0t: ' + row["wn0t"]+'<br/>t0t: '+row["t0t"]+'</span>';
ret.Class = 'CellWithComment';
@ -288,6 +295,8 @@ function updateSats()
let wantIt = false;
if(d3.select("#GalE1").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 1)
wantIt = true;
if(d3.select("#GalE5a").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 6)
wantIt = true;
if(d3.select("#GalE5b").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 5)
wantIt = true;
if(d3.select("#GPSL1CA").property("checked") && arr[n].gnssid==0 && arr[n].sigid == 0)

View File

@ -10,6 +10,7 @@
<div class="centered">
<hr/>
<input type="checkbox" id="GalE1" onclick="updateSats();"> <label for="GalE1">Galileo E1</label> &nbsp;&nbsp;
<input type="checkbox" id="GalE5a" onclick="updateSats();"> <label for="GalE5a">Galileo E5a</label> &nbsp;&nbsp;
<input type="checkbox" id="GalE5b" onclick="updateSats();"> <label for="GalE5b">Galileo E5b</label> &nbsp;&nbsp;
<input type="checkbox" id="BeiDouB1I" onclick="updateSats();"> <label for="BeiDouB1I">BeiDou B1I</label> &nbsp;&nbsp;
<input type="checkbox" id="BeiDouB2I" onclick="updateSats();"> <label for="BeiDouB2I">BeiDou B2I</label> &nbsp;&nbsp;
@ -60,10 +61,11 @@
The official Galileo constellation status can be found on the <a href="https://www.gsc-europa.eu/system-status/Constellation-Information">European GNSS Service Centre page</a>, which also lists "NAGUs", <a href="https://www.gsc-europa.eu/system-status/user-notifications">notifications about outages or changes</a>.
</p>
<p>
Official GLONASS status can be found on <a href="https://www.glonass-iac.ru/en/GLONASS/">this page</a> from the Russian Information and Analysis Center for Positioning, Navigation and Timing.
Official GLONASS status can be found on <a href="https://www.glonass-iac.ru/en/sostavOG/">this page</a> from the Russian Information and Analysis Center for Positioning, Navigation and Timing.
</p>
<p>
Sadly reduced status updates on GPS can be found on <a href="https://www.navcen.uscg.gov/?Do=constellationStatus">this page</a> from the US Department of Homeland Security.
Status updates on GPS can be found on <a
href="https://www.navcen.uscg.gov/gps-constellation">this page</a>.
</p>
<p>
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&amp;ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://berthub.eu/">me</a> if you want access to the Grafana dashboard.<br/>
<table>
<tr style="background: #FFF">
<td style="vertical-align:top">
@ -27,7 +27,7 @@
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -85,7 +85,6 @@ function componentDidMount() {
.text(function(d) { return d + "°"; });
sats.select('g.satellites').remove();
console.log(gnss_position);
let points = sats
.insert("g")
@ -259,7 +258,6 @@ function update()
}
console.log(window.location.href);
var url = new URL(window.location.href);
observer = url.searchParams.get("observer");

View File

@ -6,14 +6,14 @@
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://berthub.eu/">me</a> if you want access to the Grafana dashboard.<br/>
<table id="galileo"></table>
<p>
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States.
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -6,14 +6,14 @@
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://berthub.eu/">me</a> if you want access to the Grafana dashboard.<br/>
<table id="galileo"></table>
<p>
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States.
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -18,7 +18,7 @@
<table id="sbasstale"></table>
<p>
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -75,7 +75,7 @@ function maketable(str, arr)
ret.value = sbas + "&nbsp;";
if(img != "")
ret.value += '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
ret.value += '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
else
ret.value += "";
// ret.value="";

View File

@ -18,7 +18,7 @@
<table id="sbasstale"></table>
<p>
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -6,14 +6,14 @@
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://berthub.eu/">me</a> if you want access to the Grafana dashboard.<br/>
<table id="galileo"></table>
<p>
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States.
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.

View File

@ -131,9 +131,37 @@ Observer and SV measurements:
* ele: calculated elevation for SV from this receiver
* prres: pseudorange residual according to receiver
* qi: 0-7, quality indicator according to receiver
* used: did the receiver use this SV?
* ubx\_jamming
* noise\_per\_ms: the Ublox noisePerMS field
* agccnt: the Ublox automatic gain correction "count"
* jamind: The Ublox jamming indicator
* flag: The Ublox jamming flag field
Fed by separate tool:
SP3 design, tagged by GNSSID, SV:
* sp3\_data:
* x
* y
* z
* clk
* provider
ephemeris, tagged by GNSSID, SV, SIGID:
* active-ephemeris
* all the raw parameters
GDOP/PDOP stats?
* covdop
* lat
* lon
* cov5
* cov10
* cov20
* hdop5
* hdop10
* hdop20
etc

113
navcat.cc
View File

@ -26,7 +26,7 @@ using namespace std;
extern const char* g_gitHash;
// get all stations (numerical) from a directory
vector<uint64_t> getSources(string_view dirname)
{
DIR *dir = opendir(&dirname[0]);
@ -54,8 +54,13 @@ vector<uint64_t> getSources(string_view dirname)
return ret;
}
static bool operator==(const timespec& a, const timespec& b)
{
return a.tv_sec == b.tv_sec && a.tv_nsec && b.tv_nsec;
}
void sendProtobuf(string_view dir, vector<uint64_t> stations, time_t startTime, time_t stopTime=0)
// send protobuf data from the named directories and stations, between start and stoptime
void sendProtobuf(const vector<string>& dirs, vector<uint64_t> stations, time_t startTime, time_t stopTime=0)
{
timespec start;
start.tv_sec = startTime;
@ -66,44 +71,54 @@ void sendProtobuf(string_view dir, vector<uint64_t> stations, time_t startTime,
vector<pair<timespec,string> > rnmms;
for(;;) {
cerr<<"Gathering data"<<endl;
vector<uint64_t> srcs = stations.empty() ? getSources(dir) : stations;
rnmms.clear();
for(const auto& src: srcs) {
string fname = getPath(dir, start.tv_sec, src);
FILE* fp = fopen(fname.c_str(), "r");
if(!fp)
continue;
uint32_t offset= fpos[fname];
if(fseek(fp, offset, SEEK_SET) < 0) {
cerr<<"Error seeking: "<<strerror(errno) <<endl;
fclose(fp);
continue;
}
// cerr <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
uint32_t looked=0;
string msg;
struct timespec ts;
while(getRawNMM(fp, ts, msg, offset)) {
// don't drop data that is only 5 seconds too old
if(make_pair(ts.tv_sec + 5, ts.tv_nsec) >= make_pair(start.tv_sec, start.tv_nsec)) {
rnmms.push_back({ts, msg});
for(const auto& dir : dirs) {
cerr<<"Gathering data from "<<humanTime(start.tv_sec)<<" from "<<dir<<".. ";
vector<uint64_t> srcs = stations.empty() ? getSources(dir) : stations;
int count=0;
for(const auto& src: srcs) {
string fname = getPath(dir, start.tv_sec, src);
FILE* fp = fopen(fname.c_str(), "r");
if(!fp)
continue;
uint32_t offset= fpos[fname];
if(fseek(fp, offset, SEEK_SET) < 0) {
cerr<<"Error seeking: "<<strerror(errno) <<endl;
fclose(fp);
continue;
}
++looked;
}
// cerr<<"Harvested "<<rnmms.size()<<" events out of "<<looked<<endl;
fpos[fname]=offset;
fclose(fp);
}
// cerr <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
string msg;
struct timespec ts;
while(getRawNMM(fp, ts, msg, offset)) {
// don't drop data that is only 5 seconds too old
if(make_pair(ts.tv_sec + 5, ts.tv_nsec) >= make_pair(start.tv_sec, start.tv_nsec)) {
rnmms.push_back({ts, msg});
++count;
}
}
// cerr<<"Harvested "<<rnmms.size()<<" events out of "<<looked<<endl;
cerr<<"Sorting data"<<endl;
fpos[fname]=offset;
fclose(fp);
}
cerr<<" added "<<count<<endl;
}
// cerr<<"Sorting data"<<endl;
sort(rnmms.begin(), rnmms.end(), [](const auto& a, const auto& b)
{
return std::tie(a.first.tv_sec, a.first.tv_nsec)
< std::tie(b.first.tv_sec, b.first.tv_nsec);
});
cerr<<"Sending data"<<endl;
auto newend=unique(rnmms.begin(), rnmms.end());
cerr<<"Removed "<<rnmms.end() - newend <<" duplicates, ";
rnmms.erase(newend, rnmms.end());
cerr<<"sending data"<<endl;
unsigned int count=0;
for(const auto& nmm: rnmms) {
if(nmm.first.tv_sec > stopTime)
break;
@ -113,8 +128,9 @@ void sendProtobuf(string_view dir, vector<uint64_t> stations, time_t startTime,
buf += nmm.second;
//fwrite(buf.c_str(), 1, buf.size(), stdout);
writen2(1, buf.c_str(), buf.size());
++count;
}
cerr<<"Done sending"<<endl;
cerr<<"Done sending " << count<<" messages"<<endl;
if(3600 + start.tv_sec - (start.tv_sec%3600) < stopTime)
start.tv_sec = 3600 + start.tv_sec - (start.tv_sec%3600);
else {
@ -129,14 +145,16 @@ int main(int argc, char** argv)
bool doVERSION{false};
CLI::App app(program);
string beginarg, endarg, storage;
app.add_option("--storage,-s", storage, "Location of storage files");
string beginarg, endarg;
vector<string> storages;
int galwn{-1};
app.add_option("--storage,-s", storages, "Locations of storage files");
vector<uint64_t> stations;
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--begin,-b", beginarg, "Begin time (2020-01-01 00:00, or 12:30)")->required();
app.add_option("--begin,-b", beginarg, "Begin time (2020-01-01 00:00, or 12:30)");
app.add_option("--end,-e", endarg, "End time. Now if omitted");
app.add_option("--stations", stations, "only send data from listed stations");
app.add_option("--gal-wn", galwn, "Galileo week number to report on");
CLI11_PARSE(app, argc, argv);
@ -145,9 +163,19 @@ int main(int argc, char** argv)
exit(0);
}
time_t startTime = parseTime(beginarg);
time_t stopTime = endarg.empty() ? time(0) : parseTime(endarg);
time_t startTime, stopTime;
if(galwn >=0) {
startTime=utcFromGST(galwn, 0);
stopTime=startTime + 7*86400;
}
else if(!beginarg.empty()) {
startTime = parseTime(beginarg);
stopTime = endarg.empty() ? time(0) : parseTime(endarg);
}
else {
cerr<<"No time range specified, use -b or --gal-wn"<<endl;
return 1;
}
cerr<<"Emitting from "<<humanTime(startTime) << " to " << humanTime(stopTime) << endl;
if(!stations.empty()) {
@ -155,7 +183,6 @@ int main(int argc, char** argv)
for(const auto& s : stations)
cerr<<" "<<s;
cerr<<endl;
}
sendProtobuf(storage, stations, startTime, stopTime);
}
sendProtobuf(storages, stations, startTime, stopTime);
}

View File

@ -25,6 +25,7 @@
#include "tle.hh"
#include "sp3.hh"
#include "ubx.hh"
#include <optional>
#include <unistd.h>
#include "sbas.hh"
#include "version.hh"
@ -257,12 +258,20 @@ try
bool doReceptionData{false};
bool doRFData{true};
bool doObserverPosition{false};
bool doObserverDetails{false};
bool doTimeOffsets{false};
bool doVERSION{false};
string rinexfname;
string osnmafname;
app.add_option("--svs", svpairs, "Listen to specified svs. '0' = gps, '2' = Galileo, '2,1' is E01");
app.add_option("--stations", stations, "Listen to specified stations.");
app.add_option("--positions,-p", doObserverPosition, "Print out observer positions (or not)");
app.add_option("--rfdata,-r", doRFData, "Print out RF data (or not)");
app.add_option("--observerdetails,-o", doObserverDetails, "Print out observer detail data (or not)");
app.add_option("--timeoffsets,-t", doTimeOffsets, "Print out timeoffset data (or not)");
app.add_option("--recdata", doReceptionData, "Print out reception data (or not)");
app.add_option("--rinex", rinexfname, "If set, emit ephemerides to this filename");
app.add_option("--osnma", osnmafname, "If set, emit OSNMA CSV to this filename");
app.add_flag("--version", doVERSION, "show program version and copyright");
try {
@ -299,7 +308,18 @@ try
sp3csv<<"timestamp gnssid sv ephAge sp3X sp3Y sp3Z ephX ephY ephZ sp3Clock ephClock distance along clockDelta E speed"<<endl;
// RINEXNavWriter rnw("test.rnx");
std::optional<RINEXNavWriter> rnw;
if(!rinexfname.empty())
rnw = RINEXNavWriter(rinexfname);
std::optional<ofstream> osnmacsv;
if(!osnmafname.empty()) {
osnmacsv = ofstream(osnmafname);
(*osnmacsv)<<"wn,tow,wtype,sv,osnma\n";
}
for(;;) {
char bert[4];
int res = readn2(0, bert, 4);
@ -388,13 +408,26 @@ try
int wtype = gm.parse(inav);
gm.tow = nmm.gi().gnsstow();
bool isnew = gmwtypes[{nmm.gi().gnsssv(), wtype}].tow != gm.tow;
gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm;
static map<int,GalileoMessage> oldEph;
cout << "gal inav wtype "<<wtype<<" for "<<nmm.gi().gnssid()<<","<<nmm.gi().gnsssv()<<","<<nmm.gi().sigid()<<" pbwn "<<nmm.gi().gnsswn()<<" pbtow "<< nmm.gi().gnsstow();
static uint32_t tow;
if(osnmacsv && isnew)
(*osnmacsv)<<nmm.gi().gnsswn()<<","<<gm.tow<<","<<wtype<<","<<nmm.gi().gnsssv()<<","<<makeHexDump(nmm.gi().reserved1())<<endl;
if(wtype >=1 && wtype <= 5) {
if(nmm.gi().has_reserved1()) {
cout<<" res1 "<<makeHexDump(nmm.gi().reserved1());
}
}
if(wtype == 4) {
// 2^-34 2^-46
cout <<" iodnav "<<gm.iodnav <<" af0 "<<gm.af0 <<" af1 "<<gm.af1 <<", scaled: "<<ldexp(1.0*gm.af0, 19-34)<<", "<<ldexp(1.0*gm.af1, 38-46);
cout << " t0g " << gm.t0g <<" a0g " << gm.a0g <<" a1g " << gm.a1g <<" WN0g " << gm.wn0g;
if(tow && oldgm4s.count(nmm.gi().gnsssv()) && oldgm4s[nmm.gi().gnsssv()].iodnav != gm.iodnav) {
auto& oldgm4 = oldgm4s[nmm.gi().gnsssv()];
@ -453,7 +486,9 @@ try
if(!oldEph[sv].sqrtA)
oldEph[sv] = gm;
else if(oldEph[sv].iodnav != gm.iodnav) {
// rnw.emitEphemeris(sid, gm);
if(rnw)
rnw->emitEphemeris(sid, gm);
// gm.toJSON();
cout<<" disco! "<< oldEph[sv].iodnav << " - > "<<gm.iodnav <<", "<< (gm.getT0e() - oldEph[sv].getT0e())/3600.0 <<" hours-jump insta-age "<<ephAge(gm.tow, gm.getT0e())/3600.0<<" hours";
Point oldPoint, newPoint;
@ -498,7 +533,9 @@ try
}
if(wtype == 6) {
cout<<" a0 " << gm.a0 <<" a1 " << gm.a1 <<" t0t "<<gm.t0t << " dtLS "<<(int)gm.dtLS;
cout <<" wnLSF "<< (unsigned int)gm.wnLSF<<" dn " << (unsigned int)gm.dn<< " dtLSF "<<(int)gm.dtLSF<<endl;
}
// if(wtype < 7)
// gm = GalileoMessage{};
@ -535,7 +572,7 @@ try
if(gm.alma3.e1bhs != 0) {
cout <<" gm.tow "<<gm.tow<<" gmwtypes.tow "<< gmwtypes[{sv,9}].tow <<" ";
}
cout<<" "<<gmwtypes[{sv,9}].alma3.svid <<" af0 "<<gm.alma3.af0<<" af1 "<< gm.alma3.af1 <<" e5bhs "<< gm.alma3.e5bhs<<" e1bhs "<< gm.alma3.e1bhs <<" a0g " << gm.a0g <<" a1g "<< gm.a1g <<" t0g "<<gm.t0g <<" wn0g "<<gm.wn0g <<" delta-gps "<< gm.getGPSOffset(gm.tow, gm.wn).first<<"ns";
cout<<" alma3.sv "<<gmwtypes[{sv,9}].alma3.svid <<" af0 "<<gm.alma3.af0<<" af1 "<< gm.alma3.af1 <<" e5bhs "<< gm.alma3.e5bhs<<" e1bhs "<< gm.alma3.e1bhs <<" a0g " << gm.a0g <<" a1g "<< gm.a1g <<" t0g "<<gm.t0g <<" wn0g "<<gm.wn0g <<" delta-gps "<< gm.getGPSOffset(gm.tow, gm.wn).first<<"ns";
int dw = (int)(gm.wn%64) - (int)(gm.wn0g);
if(dw > 31)
@ -546,6 +583,55 @@ try
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GalileoCnavType) {
basic_string<uint8_t> cnav((uint8_t*)nmm.gc().contents().c_str(), nmm.gc().contents().size());
int sv = nmm.gc().gnsssv();
if(!svfilter.check(2, sv, nmm.gc().sigid()))
continue;
etstamp();
cout << "C/NAV for " << nmm.gc().gnssid()<<","<<nmm.gc().gnsssv()<<","<<nmm.gc().sigid() <<": header ";
cout<<fmt::sprintf("%02x%02x%02x (status %d, MT %d, MID %d, MS %d, PID %d) rest ",
getbitu(cnav.c_str(), 14, 8),
getbitu(cnav.c_str(), 22, 8),
getbitu(cnav.c_str(), 30, 8),
getbitu(cnav.c_str(), 14+0, 2), // status
getbitu(cnav.c_str(), 14+4, 2), // MT
getbitu(cnav.c_str(), 14+6, 5), // MID
getbitu(cnav.c_str(), 14+11, 5), // MIS
getbitu(cnav.c_str(), 14+16, 8) // PID
);
for(int n=0; n < 51; ++n)
cout << fmt::sprintf("%02x ", getbitu(cnav.c_str(), 38 +n *8, 8));
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GalileoFnavType) {
basic_string<uint8_t> fnav((uint8_t*)nmm.gf().contents().c_str(), nmm.gf().contents().size());
int sv = nmm.gf().gnsssv();
if(!svfilter.check(2, sv, nmm.gf().sigid()))
continue;
etstamp();
GalileoMessage gm;
gm.parseFnav(fnav);
cout<<"gal F/NAV wtype "<< (int)gm.wtype << " for "<<nmm.gf().gnssid()<<","<<nmm.gf().gnsssv()<<","<<nmm.gf().sigid();
if(gm.wtype ==1 || gm.wtype == 2 || gm.wtype == 3 || gm.wtype == 4)
cout<<" iodnav " <<gm.iodnav <<" tow " << gm.tow;
if(gm.wtype == 1) {
cout <<" af0 "<<gm.af0 << " af1 "<<gm.af1 <<" af2 "<< (int)gm.af2;
}
if(gm.wtype == 2) {
cout <<" sqrtA "<<gm.sqrtA;
}
if(gm.wtype == 3) {
cout <<" t0e "<<gm.t0e;
}
if(gm.wtype == 4) {
cout <<" dtLS "<<(int)gm.dtLS <<" wnLSF "<< (unsigned int)gm.wnLSF<<" dn " << (unsigned int)gm.dn<< " dtLSF "<<(int)gm.dtLSF<<endl;
}
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GPSInavType) {
int sv = nmm.gpsi().gnsssv();
@ -652,7 +738,7 @@ try
cout<<" 2nd-match "<<second.name << " dist "<<second.distance/1000<<" km t0e "<<gs.gpsalma.getT0e() << " t " <<nmm.localutcseconds();
}
if(page == 18)
cout << " dtLS " << (int)gs.dtLS <<" dtLSF "<< (int)gs.dtLSF;
cout << " wnLSF "<< (int)gs.wnLSF <<" dn " << (int)gs.dn << " t0t " << (int)gs.t0t <<" wn0t "<<(int)gs.wn0t<<" dtLS " << (int)gs.dtLS <<" dtLSF "<< (int)gs.dtLSF;
}
else if(frame == 5) {
if(gs.gpsalma.sv <= 24) {
@ -700,19 +786,42 @@ try
}
}
else if(rm.type == 1045 || rm.type == 1046) {
static ofstream af0inavstr("af0inav.csv"), af0fnavstr("af0fnav.csv"), bgdstr("bgdstr.csv");
static bool first{true};
if(first) {
af0inavstr<<"timestamp sv wn t0c af0 af1\n";
af0fnavstr<<"timestamp sv wn t0c af0 af1\n";
first=false;
}
SatID sid;
sid.gnss = 2;
sid.sv = rm.d_sv;
sid.sigid = (rm.type == 1045) ? 5 : 1;
cout<< makeSatIDName(sid)<<" ";
if(rm.type == 1045)
cout<<"F/NAV ";
else
cout <<"I/NAV ";
cout <<" iode " << rm.d_gm.iodnav << " sisa " << (unsigned int) rm.d_gm.sisa << " af0 "<<rm.d_gm.af0 <<" af1 " << rm.d_gm.af1 <<" af2 " << (int) rm.d_gm.af2 << endl;
if(rm.type == 1045) {
af0fnavstr << nmm.localutcseconds()<<" " << rm.d_sv <<" " << rm.d_gm.wn<<" "<< rm.d_gm.t0c << " " << rm.d_gm.af0 << " " << rm.d_gm.af1<<"\n";
cout<<"F/NAV";
}
else {
af0inavstr << nmm.localutcseconds() <<" " << rm.d_sv <<" " << rm.d_gm.wn<<" "<<rm.d_gm.t0c<<" "<< rm.d_gm.af0 << " " << rm.d_gm.af1<<"\n";
bgdstr << nmm.localutcseconds() <<" " << rm.d_sv<<" " <<rm.d_gm.BGDE1E5a <<" " << rm.d_gm.BGDE1E5b << "\n";
cout <<"I/NAV";
}
cout <<" iode " << rm.d_gm.iodnav << " sisa " << (unsigned int) rm.d_gm.sisa << " t0c " << rm.d_gm.t0c << " af0 "<<rm.d_gm.af0 <<" af1 " << rm.d_gm.af1 <<" af2 " << (int) rm.d_gm.af2 << " BGDE1E5a " << rm.d_gm.BGDE1E5a;
if(rm.type == 1046) // I/NAV
cout <<" BGDE1E5b "<< rm.d_gm.BGDE1E5b;
cout<<endl;
}
else if(rm.type == 1059 || rm.type==1242) {
cout<<"\n";
for(const auto& dcb : rm.d_dcbs) {
cout<<" "<<makeSatIDName(dcb.first)<<": "<<dcb.second<<" meters\n";
}
cout<<endl;
}
else {
cout<<" len " << nmm.rm().contents().size() << endl;
@ -781,8 +890,6 @@ try
cout<<" best-tle-match "<<match.name <<" dist "<<match.distance /1000<<" km";
cout<<" norad " <<match.norad <<" int-desig " << match.internat;
cout<<" 2nd-match "<<second.name << " dist "<<second.distance/1000<<" km";
}
else if((fraid == 4 && 1<= pageno && pageno <= 24) ||
(fraid == 5 && 1<= pageno && pageno <= 6) ||
@ -813,7 +920,7 @@ try
cout<<" WNa "<<getbitu(&cond[0], beidouBitconv(190), 8)<<" t0a "<<getbitu(&cond[0], beidouBitconv(198), 8);
}
else if(bm.fraid == 5 && pageno==10) {
cout <<" dTLS "<< (int)bm.deltaTLS;
cout <<" dTLS "<< (int)bm.deltaTLS << " dTLSF " << (int) bm.deltaTLSF <<" wnLSF " << (unsigned int)bm.wnLSF <<" dn "<<(unsigned int) bm.dn<<endl;
}
else if(bm.fraid == 5 && pageno==24) {
int AmID= getbitu(&cond[0], beidouBitconv(216), 2);
@ -1014,17 +1121,19 @@ try
}
else if(nmm.type() == NavMonMessage::ObserverDetailsType) {
etstamp();
cout<<"vendor "<<nmm.od().vendor()<<" hwversion " <<nmm.od().hwversion()<<" modules "<<nmm.od().modules()<<" swversion "<<nmm.od().swversion();
cout<<" serial "<<nmm.od().serialno();
if(nmm.od().has_owner())
cout<<" owner "<<nmm.od().owner();
if(nmm.od().has_clockoffsetdriftns())
cout<<" drift "<<nmm.od().clockoffsetdriftns();
if(nmm.od().has_clockaccuracyns())
cout<<" clock-accuracy "<<nmm.od().clockaccuracyns();
cout<<endl;
if(doObserverDetails) {
etstamp();
cout<<"vendor "<<nmm.od().vendor()<<" hwversion " <<nmm.od().hwversion()<<" modules "<<nmm.od().modules()<<" swversion "<<nmm.od().swversion();
cout<<" serial "<<nmm.od().serialno();
if(nmm.od().has_owner())
cout<<" owner "<<nmm.od().owner();
if(nmm.od().has_clockoffsetdriftns())
cout<<" drift "<<nmm.od().clockoffsetdriftns();
if(nmm.od().has_clockaccuracyns())
cout<<" clock-accuracy "<<nmm.od().clockaccuracyns();
cout<<endl;
}
}
else if(nmm.type() == NavMonMessage::UbloxJammingStatsType) {
etstamp();
@ -1195,12 +1304,14 @@ try
cout<< nmm.sr().gnsssv() << " beacon "<<hexstring <<" code "<<(int)nmm.sr().code()<<" params "<< makeHexDump(nmm.sr().params()) <<endl;
}
else if(nmm.type() == NavMonMessage::TimeOffsetType) {
etstamp();
cout<<" got a time-offset message with "<< nmm.to().offsets().size()<<" offsets: ";
for(const auto& o : nmm.to().offsets()) {
cout << "gnssid "<<o.gnssid()<<" offset " << o.offsetns() << " +- "<<o.tacc()<<" ("<<o.valid()<<") , ";
if(doTimeOffsets) {
etstamp();
cout<<" got a time-offset message with "<< nmm.to().offsets().size()<<" offsets: ";
for(const auto& o : nmm.to().offsets()) {
cout << "gnssid "<<o.gnssid()<<" offset " << o.offsetns() << " +- "<<o.tacc()<<" ("<<o.valid()<<") , ";
}
cout<<endl;
}
cout<<endl;
}
else {

172
navmerge.cc 100644
View File

@ -0,0 +1,172 @@
#include "sclasses.hh"
#include <map>
#include "navmon.hh"
#include "navmon.pb.h"
#include <thread>
#include "nmmsender.hh"
#include "CLI/CLI.hpp"
#include "version.hh"
using namespace std;
static char program[]="navmerge";
/* ubxtool/rtcmtool/septool deliver data to one of several `navrecv` instances
This means we need a 'merge' tool.
The merge tool should be able to stream data from multiple `navnexus` instances
(that correspond to the `navrecv` instances)
Currently, `navnexus` is really simple - it will send you a feed from x hours back, where
you don't get to pick x.
The simplest navmerge implementation does nothing but connect to a few navnexus instances
and it mixes them together.
Every message "should" only appear on one of the upstreams, but you never know.
On initial connection, the different navnexuses may start up from a different time, currently.
Let us state that This Should Not Happen.
On initial connect, a navnexus might take dozens of seconds before it starts coughing up data.
Initial goal for navmerge is: only make sure realtime works.
Every upstream has a thread that loops trying to connect
If a new message comes in, it is stored in a shared data structure
If a new connect is made, set a "don't send" marker for a whole minute
There is a sender thread that periodically polls this data structure
Any data that is older than the previous high-water mark gets sent out & removed from structure
However, transmission stops 10 seconds before realtime
If a "don't send" marker is set, we don't do a thing
*/
multimap<pair<uint64_t, uint64_t>, string> g_buffer;
std::mutex g_mut;
void recvSession(ComboAddress upstream)
{
for(;;) {
try {
Socket sock(upstream.sin4.sin_family, SOCK_STREAM);
cerr<<"Connecting to "<< upstream.toStringWithPort()<<" to source data..";
SConnectWithTimeout(sock, upstream, 5);
cerr<<" done"<<endl;
for(int count=0;;++count) {
string part=SRead(sock, 4);
if(part.empty()) {
cerr<<"EOF from "<<upstream.toStringWithPort()<<endl;
break;
}
if(part != "bert") {
cerr << "Message "<<count<<", wrong magic from "<<upstream.toStringWithPort()<<": "<<makeHexDump(part)<<endl;
break;
}
if(!count)
cerr<<"Receiving messages from "<<upstream.toStringWithPort()<<endl;
string out=part;
part = SRead(sock, 2);
out += part;
uint16_t len;
memcpy(&len, part.c_str(), 2);
len = htons(len);
part = SRead(sock, len); // XXXXX ???
if(part.size() != len) {
cerr<<"Mismatch, "<<part.size()<<", len "<<len<<endl;
// XX AND THEN WHAT??
}
out += part;
NavMonMessage nmm;
nmm.ParseFromString(part);
// writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
// do something with the message
std::lock_guard<std::mutex> mut(g_mut);
g_buffer.insert({{nmm.localutcseconds(), nmm.localutcnanoseconds()}, part});
}
}
catch(std::exception& e) {
cerr<<"Error in receiving thread: "<<e.what()<<endl;
sleep(1);
}
}
cerr<<"Thread for "<<upstream.toStringWithPort()<< " exiting"<<endl;
}
int main(int argc, char** argv)
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
vector<string> destinations;
vector<string> sources;
bool doVERSION{false}, doSTDOUT{false};
CLI::App app(program);
app.add_option("--source", sources, "Connect to these IP address:port to source protobuf");
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_flag("--stdout", doSTDOUT, "Emit output to stdout");
CLI11_PARSE(app, argc, argv);
if(doVERSION) {
showVersion(program, g_gitHash);
exit(0);
}
signal(SIGPIPE, SIG_IGN);
NMMSender ns;
ns.d_debug = true;
for(const auto& s : destinations) {
auto res=resolveName(s, true, true);
if(res.empty()) {
cerr<<"Unable to resolve '"<<s<<"' as destination for data, exiting"<<endl;
exit(EXIT_FAILURE);
}
ns.addDestination(s); // ComboAddress(s, 29603));
}
if(doSTDOUT)
ns.addDestination(1);
for(const auto& s : sources) {
ComboAddress oneaddr(s, 29601);
std::thread one(recvSession, oneaddr);
one.detach();
}
time_t start=time(0);
for(;;) {
sleep(1);
vector<string> tosend;
{
std::lock_guard<std::mutex> mut(g_mut);
time_t now = time(0);
if(now - start < 30) {
cerr<<"Have "<<g_buffer.size()<<" messages"<<endl;
continue;
}
for(auto iter = g_buffer.begin(); iter != g_buffer.end(); ) {
if(iter->first.first > (uint64_t)now - 5)
break;
tosend.push_back(iter->second);
iter = g_buffer.erase(iter);
}
}
// cerr<<"Have "<<tosend.size()<<" messages to send, "<<g_buffer.size()<<" left in queue"<<endl;
std::string buf;
for(const auto& m : tosend) {
ns.emitNMM(m);
}
}
}

View File

@ -159,6 +159,18 @@ std::string humanTimeShort(time_t t)
return buffer;
}
// influx ascii time format, in UTC
std::string influxTime(time_t t)
{
struct tm tm={0};
gmtime_r(&t, &tm);
char buffer[80];
std::string fmt = "%Y-%m-%d %H:%M:%S";
strftime(buffer, sizeof(buffer), fmt.c_str(), &tm);
return buffer;
}
std::string humanTime(time_t t, uint32_t nanoseconds)
{
@ -280,22 +292,33 @@ std::string makeSatPartialName(const SatID& satid)
return fmt::sprintf("%c%02d", getGNSSChar(satid.gnss), satid.sv);
}
int g_dtLS{18}, g_dtLSBeidou{4};
void getGPSDateFromUTC(time_t t, int& wn, int& tow)
{
t -= 315964800;
t += 18;
t += 18; // XXXXXX LEAP SECOND
wn = t/(7*86400);
tow = t%(7*86400);
}
void getGalDateFromUTC(time_t t, int& wn, int& tow)
{
t -= 935280000;
t += 18;
t += 18; // XXXXXXX LEAP SECOND
wn = t/(7*86400);
tow = t%(7*86400);
}
int g_dtLS{18}, g_dtLSBeidou{4};
void getBeiDouDateFromUTC(time_t t, int&wn, int& sow)
{
// Week number count started from zero at 00:00:00 on Jan. 1, 2006 of BDT
t -= 1136070000;
t+= g_dtLSBeidou;
wn = t/(7*86400);
sow = t%(7*86400);
}
uint64_t utcFromGST(int wn, int tow)
{
return (935280000 + wn * 7*86400 + tow - g_dtLS);
@ -324,6 +347,19 @@ string makeHexDump(const string& str)
return ret;
}
string makeHexDump(const basic_string<uint8_t>& str)
{
char tmp[5];
string ret;
ret.reserve((int)(str.size()*2.2));
for(string::size_type n=0;n<str.size();++n) {
snprintf(tmp, sizeof(tmp), "%02x ", (unsigned char)str[n]);
ret+=tmp;
}
return ret;
}
std::string sbasName(int prn)
{
string sbas;
@ -375,7 +411,7 @@ time_t parseTime(std::string_view in)
{
time_t now=time(0);
vector<string> formats({"%Y-%m-%d %H:%M", "%Y%m%d %H%M", "%H:%M", "%H%M"});
vector<string> formats({"%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S", "%Y%m%d %H%M", "%H:%M", "%H%M"});
for(const auto& f : formats) {
struct tm tm;
memset(&tm, 0, sizeof(tm));
@ -392,7 +428,24 @@ time_t parseTime(std::string_view in)
string err="Can only parse on";
for(const auto& f : formats)
err += " "+ f;
err += " '"+ f+"'";
throw runtime_error(err);
}
std::string string_replace(const std::string& str, const std::string& match,
const std::string& replacement, unsigned int max_replacements)
{
size_t pos = 0;
std::string newstr = str;
unsigned int replacements = 0;
while ((pos = newstr.find(match, pos)) != std::string::npos
&& replacements < max_replacements)
{
newstr.replace(pos, match.length(), replacement);
pos += replacement.length();
replacements++;
}
return newstr;
}

View File

@ -5,7 +5,7 @@
#include <string>
#include <tuple>
#include <mutex>
#include <limits.h>
extern const char* g_gitHash;
@ -18,6 +18,8 @@ std::string humanTimeNow();
std::string humanTime(time_t t);
std::string humanTimeShort(time_t t);
std::string humanTime(time_t t, uint32_t nanoseconds);
// influx ascii time format, in UTC
std::string influxTime(time_t t);
struct SatID
{
uint32_t gnss{255}; // these could all be 'int16_t' but leads to howling numbers of warnings with protobuf
@ -78,9 +80,12 @@ double utcFromGPS(int wn, double tow);
void getGPSDateFromUTC(time_t t, int& wn, int& tow);
void getGalDateFromUTC(time_t t, int& wn, int& tow);
void getBeiDouDateFromUTC(time_t t, int&wn, int& sow);
std::string makeHexDump(const std::string& str);
std::string makeHexDump(const std::basic_string<uint8_t>& str);
size_t writen2(int fd, const void *buf, size_t count);
void unixDie(const std::string& reason);
time_t parseTime(std::string_view in);
std::string string_replace(const std::string& str, const std::string& match,
const std::string& replacement, unsigned int max_replacements = UINT_MAX);

View File

@ -18,6 +18,8 @@ message NavMonMessage {
GPSCnavType = 14;
RTCMMessageType = 15;
TimeOffsetType = 16;
GalileoFnavType = 17;
GalileoCnavType = 18;
}
required uint64 sourceID = 1;
@ -33,7 +35,31 @@ message NavMonMessage {
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
optional uint32 sigid = 6;
optional uint32 sigid = 6;
optional bytes reserved1 = 7;
optional bytes reserved2 = 8;
optional bytes sar = 9;
optional bytes spare = 10;
optional bytes crc = 11;
}
message GalileoFnav {
required uint32 gnssWN =1;
required uint32 gnssTOW =2; // INTEGERS!
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
required uint32 sigid = 6;
}
message GalileoCnav {
required uint32 gnssWN =1;
required uint32 gnssTOW =2; // INTEGERS!
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
required uint32 sigid = 6;
}
message GPSInav {
@ -217,4 +243,6 @@ message NavMonMessage {
optional GPSCnav gpsc = 18;
optional RTCMMessage rm = 19;
optional TimeOffsetMessage to = 20;
optional GalileoFnav gf=21;
optional GalileoCnav gc=22;
}

View File

@ -88,7 +88,7 @@ try
close(fd);
continue;
}
cout <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
// cout <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
NavMonMessage nmm;
uint32_t looked=0;
@ -106,15 +106,15 @@ try
fpos[fname]=offset;
close(fd);
}
cout<<"Sorting.. ";
cout.flush();
// cout<<"Sorting.. ";
// cout.flush();
sort(rnmms.begin(), rnmms.end(), [](const auto& a, const auto& b)
{
return std::tie(a.first.tv_sec, a.first.tv_nsec)
< std::tie(b.first.tv_sec, b.first.tv_nsec);
});
cout<<"Sending.. ";
cout.flush();
// cout<<"Sending.. ";
// cout.flush();
for(const auto& nmm: rnmms) {
std::string buf="bert";
uint16_t len = htons(nmm.second.size());
@ -122,7 +122,7 @@ try
buf += nmm.second;
SWriten(clientfd, buf);
}
cout<<"Done"<<endl;
// cout<<"Done"<<endl;
if(3600 + start.tv_sec - (start.tv_sec % 3600) < time(0))
start.tv_sec = 3600 + start.tv_sec - (start.tv_sec % 3600);
else {

View File

@ -37,7 +37,7 @@
#include "gpscnav.hh"
#include "rtcm.hh"
#include "version.hh"
//#include "nequick.hh"
static char program[]="navparse";
@ -45,7 +45,7 @@ using namespace std;
extern const char* g_gitHash;
struct ObserverPosition
struct ObserverFacts
{
Point pos;
double groundSpeed{-1};
@ -65,7 +65,7 @@ struct ObserverPosition
string remark;
time_t lastSeen{0};
};
std::map<int, ObserverPosition> g_srcpos;
std::map<int, ObserverFacts> g_srcpos;
struct SBASAndReceiverStatus
{
@ -263,9 +263,6 @@ void SVStat::reportNewEphemeris(const SatID& id, InfluxPusher& idb)
if(gnss==0) { // GPS
const auto& eg = ephgpsmsg;
idb.addValue(id, "ephemeris-actual", {
{"iod", eg.getIOD()}}, satUTCTime(id));
idb.addValue(id, "ephemeris-actual", {
{"iod", eg.getIOD()},
@ -295,10 +292,6 @@ void SVStat::reportNewEphemeris(const SatID& id, InfluxPusher& idb)
if(gnss==2) {
const auto& eg = ephgalmsg;
idb.addValue(id, "ephemeris-actual", {
{"iod", eg.getIOD()}}, satUTCTime(id));
idb.addValue(id, "ephemeris-actual", {
{"iod", eg.getIOD()},
@ -568,6 +561,25 @@ try {
}
sats.push_back(sat);
}
vector<SatID> aux{{2, 14, 1}, {2,18,1}};
for(const auto& id : aux) {
if(!g_svstats.count(id))
continue;
const auto& svstat = g_svstats[id];
if(svstat.completeIOD() && svstat.galmsg.sisa == 255) {
continue;
}
if(svstat.galmsg.e1bhs || svstat.galmsg.e1bdvs) {
continue;
}
Point sat;
getCoordinates(tow, svstat.galmsg, &sat);
sats.push_back(sat);
}
// cout<<endl;
auto cov = emitCoverage(sats);
int cells=0;
@ -629,10 +641,11 @@ try
string localAddress("127.0.0.1:29599");
string htmlDir("./html");
string influxDBName("null");
bool doGalileoReportSpeedup{false};
bool doLogRFData{false};
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_flag("--log-rf-data", doLogRFData, "store per station RF/correlator data");
app.add_flag("--gal-report-speedup", doGalileoReportSpeedup, "skip debugging data, glonass, beidou, SBAS");
app.add_option("--bind,-b", localAddress, "Address to bind to");
app.add_option("--html", htmlDir, "Where to source the HTML & JavaScript");
app.add_option("--influxdb", influxDBName, "Name of influxdb database");
@ -651,7 +664,7 @@ try
// feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW );
// g_tles.parseFile("active.txt");
// g_tles.parseFile("active.txt");
g_tles.parseFile("galileo.txt");
g_tles.parseFile("glo-ops.txt");
@ -1524,6 +1537,9 @@ try
}
}
if(s.first.gnss == 2) {
if(s.second.osnmaTime >= 0 && ephAge(s.second.galmsg.tow, s.second.osnmaTime) < 60)
item["osnma"] = s.second.osnma;
auto galileoalma = g_galileoalmakeeper.get();
if(auto iter = galileoalma.find(s.first.sv); iter != galileoalma.end()) {
Point almapos;
@ -1836,7 +1852,7 @@ try
if(!lastCovSyncPoint)
holdOffTime = nmm.localutcseconds() + 600;
if(nmm.localutcseconds() > holdOffTime)
if((time_t)nmm.localutcseconds() > holdOffTime)
storeCoverageStats(idb, nmm.localutcseconds());
lastCovSyncPoint = nmm.localutcseconds() / lastCovInterval;
}
@ -1847,6 +1863,63 @@ try
storeSelfStats(idb, nmm.localutcseconds());
lastSelfstatSyncPoint = nmm.localutcseconds() / lastSelfstatInterval;
}
#if 0
constexpr auto lastIonoInterval = 3600;
static time_t lastIonoSyncPoint;
if(nmm.localutcseconds() / lastIonoInterval > (unsigned int)lastIonoSyncPoint) {
// go over all satellites
NeQuickInst nqi;
// cerr<<"Looking at all sats"<<endl;
for(const auto& s : g_svstats) {
if(s.first.gnss!=2 || !s.second.completeIOD())
continue;
Point sat;
s.second.getCoordinates(s.second.tow(), &sat);
for(const auto& pr : s.second.perrecv) {
// cerr<<"Looking at "<<s.first.sv<<", "<<pr.second.db<<" "<<nmm.localutcseconds()<<", "<<pr.second.t<<" perrecv "<<pr.first<<" count " << g_srcpos.count(pr.first)<<endl;
if(g_srcpos.count(pr.first) && (pr.second.db > 0 || (nmm.localutcseconds() - pr.second.t < 120))) {
// cerr<<"Doing it -> "<< s.second.galmsg.ai0 <<" " <<s.second.galmsg.ai1<<" " << s.second.galmsg.ai2<< endl;
const auto& sp = g_srcpos[pr.first];
try {
// cerr<<"Obs "<<pr.first<<" pos: "<<sp.pos.x<<", "<<sp.pos.y<<", "<<sp.pos.z<<endl;
// cerr<<"Sat " <<s.first.sv<<" pos: "<<sat.x<<", "<<sat.y<<", "<<sat.z<<endl;
// auto obs = ecefToWGS84Deg(sp.pos.x, sp.pos.y, sp.pos.z);
// cerr<<"Observer height: "<<get<2>(obs)<<" meters, long "<<get<0>(obs)<<", lat "<<get<1>(obs)<<endl;
// auto satdegs = ecefToWGS84Deg(sat.x, sat.y, sat.z);
// cerr<<"Satellite height: "<<get<2>(satdegs)<<" meters, long "<<get<0>(satdegs)<<", lat "<<get<1>(satdegs)<< " -> elevation "<<getElevationDeg(sat, sp.pos)<<" deg"<<endl;
if(getElevationDeg(sat, sp.pos) < 0) // below the horizon
continue;
if(sp.pos.x==0 || isnan(sat.x)) {
// cerr<<"Fake position, skipping"<<endl;
continue;
}
double meters = (40.3e16/(1575420000.0*1575420000.0)) *
nqi.getTecu(nmm.localutcseconds(),
s.second.galmsg.ai0,
s.second.galmsg.ai1,
s.second.galmsg.ai2, sp.pos, sat);
idb.addValue(s.first, "nequick",
{
{"meters", meters}
}, nmm.localutcseconds() + nmm.localutcnanoseconds()/1000000000.0, pr.first);
// cerr<<"Meters: "<<meters<<endl;
}
catch(std::exception& e) {
cerr<<"Exception during NeQuick: "<<e.what()<<endl;
}
}
}
}
// cerr<<"Done"<<endl;
lastIonoSyncPoint = nmm.localutcseconds() / lastIonoInterval;
}
#endif
if(nmm.type() == NavMonMessage::ReceptionDataType) {
@ -1877,12 +1950,12 @@ try
Point sat{0,0,0};
//cout<<"Got recdata for "<<id.gnss<<","<<id.sv<<","<<id.sigid<<": count="<<g_svstats.count(id)<<endl;
if(g_svstats[id].completeIOD() && (id.gnss != 6 || !(random() % 16))) { // glonass is too slow
if(g_svstats[id].completeIOD() && !(random() % 16)) {
g_svstats[id].getCoordinates(g_svstats[id].tow(), &sat);
}
if(sat.x != 0 && g_srcpos[nmm.sourceid()].pos.x != 0) {
if(doLogRFData && !(random() % 4))
if(doLogRFData && !(random() % 16))
idb.addValue(id, "recdata",
{
{"db", nmm.rd().db()},
@ -1904,31 +1977,6 @@ try
else
sigid = 1; // default to E1B
SatID id={2,(uint32_t)sv,(uint32_t)sigid};
/*
struct DedupKey
{
SatID id;
int wn;
int tow;
basic_string<uint8_t> contents;
bool operator<(const DedupKey& rhs) const
{
return tie(id, wn, tow, contents) <
tie(rhs.id, rhs.wn, rhs.tow, rhs.contents);
}
};
static set<DedupKey> s_dedup;
DedupKey dk{id, (int)nmm.gi().gnsswn(), (int)nmm.gi().gnsstow(), inav};
if(s_dedup.insert(dk).second == false) {
// cout<<"Dedup"<<endl;
continue;
}
if(s_dedup.size() > 10000)
s_dedup.clear();
*/
// XXX conversion, may be vital
// g_svstats[id].wn = nmm.gi().gnsswn();
auto& svstat = g_svstats[id];
svstat.gnss = id.gnss;
@ -1937,7 +1985,6 @@ try
auto& gm = svstat.galmsg;
unsigned int wtype = gm.parse(inav);
if(wtype == 5 && svstat.galmsgTyped.count(5)) {
const auto& old5gm = svstat.galmsgTyped[5];
if(make_tuple(old5gm.e5bhs, old5gm.e1bhs, old5gm.e5bdvs, old5gm.e1bdvs) !=
@ -1949,6 +1996,15 @@ try
svstat.galmsgTyped[wtype] = gm;
if(wtype == 1 || wtype == 2 || wtype == 3 || wtype == 4) {
if(nmm.gi().has_reserved1()) {
static string off;
if(off.empty())
off.append(5, (char)0);
svstat.osnma = nmm.gi().reserved1() != off;
if(svstat.osnma) // eventually this will become too much but ok for now
idb.addValue(id, "osnma", {{"wtype", wtype}, {"field", makeHexDump(nmm.gi().reserved1())}}, satUTCTime(id));
svstat.osnmaTime = gm.tow;
}
idb.addValue(id, "ephemeris", {{"iod-live", svstat.galmsg.iodnav},
{"eph-age", ephAge(gm.tow, gm.getT0e())}}, satUTCTime(id));
@ -1959,34 +2015,27 @@ try
if(w > 1 && svstat.galmsgTyped[w-1].iodnav != svstat.galmsgTyped[w].iodnav)
break;
}
if(w==5) {
if(w==5) { // have complete new ephemeris
if(svstat.ephgalmsg.iodnav != svstat.galmsgTyped[1].iodnav) {
svstat.oldephgalmsg = svstat.ephgalmsg;
svstat.ephgalmsg = svstat.galmsgTyped[wtype];
svstat.reportNewEphemeris(id, idb);
}
}
}
// XXX conversion possibly vital
// g_svstats[id].tow = nmm.gi().gnsstow();
// g_svstats[id].perrecv[nmm.sourceid()].wn = nmm.gi().gnsswn();
// g_svstats[id].perrecv[nmm.sourceid()].tow = nmm.gi().gnsstow();
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
svstat.perrecv[nmm.sourceid()].t = nmm.localutcseconds();
if(wtype >=1 && wtype <= 4) { // ephemeris
if(wtype == 3) {
idb.addValue(id, "sisa", {{"value", g_svstats[id].galmsg.sisa}}, satUTCTime(id));
idb.addValue(id, "sisa", {{"value", svstat.galmsg.sisa}}, satUTCTime(id));
}
else if(wtype == 4) {
idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow()).first},
{"t0c", g_svstats[id].galmsg.t0c*60}, // getT0c()??
{"af0", g_svstats[id].galmsg.af0},
{"af1", g_svstats[id].galmsg.af1},
{"af2", g_svstats[id].galmsg.af2}}, satUTCTime(id));
{"t0c", svstat.galmsg.t0c*60}, // getT0c()??
{"af0", svstat.galmsg.af0},
{"af1", svstat.galmsg.af1},
{"af2", svstat.galmsg.af2}}, satUTCTime(id));
if(oldgm.af0 && oldgm.t0c != svstat.galmsg.t0c) {
@ -1994,35 +2043,47 @@ try
auto newOffset = svstat.galmsg.getAtomicOffset(svstat.tow());
svstat.timeDisco = oldOffset.first - newOffset.first;
if(fabs(svstat.timeDisco) < 10000)
idb.addValue(id, "clock_jump_ns", {{"value", svstat.timeDisco}}, satUTCTime(id));
idb.addValue(id, "clock_jump_ns", {
{"jump", svstat.timeDisco},
{"duration", ephAge(svstat.galmsg.t0c * 60, oldgm.t0c * 60)},
{"old-af0", oldgm.af0},
{"old-af1", oldgm.af1},
{"old-af2", oldgm.af2},
{"old-t0c", oldgm.t0c * 60},
{"new-af0", svstat.galmsg.af0},
{"new-af1", svstat.galmsg.af1},
{"new-af2", svstat.galmsg.af2},
{"new-t0c", svstat.galmsg.t0c * 60}
}, satUTCTime(id));
}
}
}
else if(wtype == 5) {
idb.addValue(id, "iono", {
{"ai0", g_svstats[id].galmsg.ai0},
{"ai1", g_svstats[id].galmsg.ai1},
{"ai2", g_svstats[id].galmsg.ai2},
{"sf1", g_svstats[id].galmsg.sf1},
{"sf2", g_svstats[id].galmsg.sf2},
{"sf3", g_svstats[id].galmsg.sf3},
{"sf4", g_svstats[id].galmsg.sf4},
{"sf5", g_svstats[id].galmsg.sf5}}, satUTCTime(id));
{"ai0", svstat.galmsg.ai0},
{"ai1", svstat.galmsg.ai1},
{"ai2", svstat.galmsg.ai2},
{"sf1", svstat.galmsg.sf1},
{"sf2", svstat.galmsg.sf2},
{"sf3", svstat.galmsg.sf3},
{"sf4", svstat.galmsg.sf4},
{"sf5", svstat.galmsg.sf5}}, satUTCTime(id));
idb.addValue(id, "galbgd", {
{"BGDE1E5a", g_svstats[id].galmsg.BGDE1E5a},
{"BGDE1E5b", g_svstats[id].galmsg.BGDE1E5b}}, satUTCTime(id));
{"BGDE1E5a", svstat.galmsg.BGDE1E5a},
{"BGDE1E5b", svstat.galmsg.BGDE1E5b}}, satUTCTime(id));
idb.addValue(id, "galhealth", {
{"e1bhs", g_svstats[id].galmsg.e1bhs},
{"e5bhs", g_svstats[id].galmsg.e5bhs},
{"e5bdvs", g_svstats[id].galmsg.e5bdvs},
{"e1bdvs", g_svstats[id].galmsg.e1bdvs}}, satUTCTime(id));
{"e1bhs", svstat.galmsg.e1bhs},
{"e5bhs", svstat.galmsg.e5bhs},
{"e5bdvs", svstat.galmsg.e5bdvs},
{"e1bdvs", svstat.galmsg.e1bdvs}}, satUTCTime(id));
}
else if(wtype == 6) { // GST-UTC
const auto& sv = g_svstats[id];
const auto& sv = svstat;
g_GSTUTCOffset = sv.galmsg.getUTCOffset(sv.tow(), sv.wn()).first;
idb.addValue(id, "utcoffset", {
{"a0", sv.galmsg.a0},
@ -2061,26 +2122,146 @@ try
g_galileoalma[gm.alma3.svid] = gm.alma3;
}
g_GSTGPSOffset = gm.getGPSOffset(gm.tow, gm.wn).first;
idb.addValue(id, "gpsoffset", {{"a0g", g_svstats[id].galmsg.a0g},
{"a1g", g_svstats[id].galmsg.a1g},
{"t0g", g_svstats[id].galmsg.t0g},
{"wn0g", g_svstats[id].galmsg.wn0g},
idb.addValue(id, "gpsoffset", {{"a0g", svstat.galmsg.a0g},
{"a1g", svstat.galmsg.a1g},
{"t0g", svstat.galmsg.t0g},
{"wn0g", svstat.galmsg.wn0g},
{"delta", g_GSTGPSOffset}
}, satUTCTime(id));
}
}
else if(nmm.type() == NavMonMessage::GalileoCnavType) {
SatID id={2,(uint32_t) nmm.gc().gnsssv(), 8}; // E6
idb.addValue(id, "galcnav", {{"msg", makeHexDump(nmm.gc().contents())}},
nmm.localutcseconds());
// ... no idea what this contains
}
else if(nmm.type() == NavMonMessage::GalileoFnavType) {
basic_string<uint8_t> fnav((uint8_t*)nmm.gf().contents().c_str(), nmm.gf().contents().size());
int sv = nmm.gf().gnsssv();
SatID id={2,(uint32_t)sv,6}; // E5a
auto& svstat = g_svstats[id];
svstat.gnss = id.gnss;
auto oldgm = svstat.galmsg;
// CONVERSION XXX
#if 0
for(auto& ent : g_svstats) {
// fmt::printf("%2d\t", ent.first);
id=ent.first;
if(ent.second.completeIOD() && ent.second.prevIOD.first >= 0) {
auto& gm = svstat.galmsg;
unsigned int wtype = gm.parseFnav(fnav);
ent.second.clearPrev();
if(wtype == 1 && svstat.galmsgTyped.count(1)) {
const auto& old5gm = svstat.galmsgTyped[1];
if(make_tuple(old5gm.e5ahs, old5gm.e1bhs, old5gm.e5advs, old5gm.e1bdvs) !=
make_tuple(gm.e5ahs, gm.e1bhs, gm.e5advs, gm.e1bdvs)) {
cout<<humanTime(id.gnss, svstat.wn(), svstat.tow())<<" src "<<nmm.sourceid()<<" Galileo "<<id.sv <<" sigid "<<id.sigid<<" change in health: ["<<humanBhs(old5gm.e5ahs)<<", "<<humanBhs(old5gm.e1bhs)<<", "<<(int)old5gm.e5advs <<", " << (int)old5gm.e1bdvs<<"] -> ["<< humanBhs(gm.e5ahs)<<", "<< humanBhs(gm.e1bhs)<<", "<< (int)gm.e5advs <<", " << (int)gm.e1bdvs<<"], lastseen "<<ephAge(old5gm.tow, gm.tow)/3600.0 <<" hours"<<endl;
}
}
svstat.galmsgTyped[wtype] = gm;
if(wtype == 1 || wtype == 2 || wtype == 3 || wtype == 4) {
idb.addValue(id, "ephemeris", {{"iod-live", svstat.galmsg.iodnav},
{"eph-age", ephAge(gm.tow, gm.getT0e())}}, satUTCTime(id));
int w = 1;
for(; w < 5; ++w) {
if(!svstat.galmsgTyped.count(w))
break;
if(w > 1 && svstat.galmsgTyped[w-1].iodnav != svstat.galmsgTyped[w].iodnav)
break;
}
if(w==5) { // have complete new ephemeris
if(svstat.ephgalmsg.iodnav != svstat.galmsgTyped[2].iodnav) {
// cout<<"New F/NAV ephemeris for "<<makeSatIDName(id)<<" iod " << svstat.galmsgTyped[1].iodnav << " t0e " << svstat.galmsgTyped[3].t0e << " af0 "<< gm.af0 <<" iod1 "<<svstat.galmsgTyped[1].iodnav;
/*
cout<<" iod2 "<<svstat.galmsgTyped[2].iodnav;
cout<<" iod3 "<<svstat.galmsgTyped[3].iodnav;
cout<<" iod4 "<<svstat.galmsgTyped[4].iodnav << endl;
*/
svstat.oldephgalmsg = svstat.ephgalmsg;
svstat.ephgalmsg = svstat.galmsg;
svstat.reportNewEphemeris(id, idb);
}
}
#endif
}
svstat.perrecv[nmm.sourceid()].t = nmm.localutcseconds();
if(wtype >=1 && wtype <= 4) { // ephemeris
if(wtype == 1) {
idb.addValue(id, "sisa", {{"value", svstat.galmsg.sisa}}, satUTCTime(id));
idb.addValue(id, "galbgd", {
{"BGDE1E5a", svstat.galmsg.BGDE1E5a},
}, satUTCTime(id));
idb.addValue(id, "galhealth", {
{"e5ahs", svstat.galmsg.e5bhs},
{"e5advs", svstat.galmsg.e5bdvs}
}, satUTCTime(id));
idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow()).first},
{"t0c", svstat.galmsg.t0c*60}, // getT0c()??
{"af0", svstat.galmsg.af0},
{"af1", svstat.galmsg.af1},
{"af2", svstat.galmsg.af2}}, satUTCTime(id));
if(oldgm.af0 && oldgm.t0c != svstat.galmsg.t0c) {
auto oldOffset = oldgm.getAtomicOffset(svstat.tow());
auto newOffset = svstat.galmsg.getAtomicOffset(svstat.tow());
svstat.timeDisco = oldOffset.first - newOffset.first;
if(fabs(svstat.timeDisco) < 10000)
idb.addValue(id, "clock_jump_ns", {
{"jump", svstat.timeDisco},
{"duration", ephAge(svstat.galmsg.t0c * 60, oldgm.t0c * 60)},
{"old-af0", oldgm.af0},
{"old-af1", oldgm.af1},
{"old-af2", oldgm.af2},
{"old-t0c", oldgm.t0c * 60},
{"new-af0", svstat.galmsg.af0},
{"new-af1", svstat.galmsg.af1},
{"new-af2", svstat.galmsg.af2},
{"new-t0c", svstat.galmsg.t0c * 60}
}, satUTCTime(id));
}
idb.addValue(id, "iono", {
{"ai0", svstat.galmsg.ai0},
{"ai1", svstat.galmsg.ai1},
{"ai2", svstat.galmsg.ai2},
{"sf1", svstat.galmsg.sf1},
{"sf2", svstat.galmsg.sf2},
{"sf3", svstat.galmsg.sf3},
{"sf4", svstat.galmsg.sf4},
{"sf5", svstat.galmsg.sf5}}, satUTCTime(id));
}
}
else if(wtype == 4) {
const auto& sv = g_svstats[id];
g_GSTUTCOffset = sv.galmsg.getUTCOffset(sv.tow(), sv.wn()).first;
idb.addValue(id, "utcoffset", {
{"a0", sv.galmsg.a0},
{"a1", sv.galmsg.a1},
{"t0t", sv.galmsg.t0t},
{"delta", g_GSTUTCOffset}
},
satUTCTime(id));
g_dtLS = sv.galmsg.dtLS;
g_GSTGPSOffset = gm.getGPSOffset(gm.tow, gm.wn).first;
idb.addValue(id, "gpsoffset", {{"a0g", svstat.galmsg.a0g},
{"a1g", svstat.galmsg.a1g},
{"t0g", svstat.galmsg.t0g},
{"wn0g", svstat.galmsg.wn0g},
{"delta", g_GSTGPSOffset}
}, satUTCTime(id));
}
}
else if(nmm.type() == NavMonMessage::ObserverPositionType) {
g_srcpos[nmm.sourceid()].lastSeen = nmm.localutcseconds();
@ -2288,7 +2469,8 @@ try
}
else if(nmm.type()== NavMonMessage::DebuggingType) {
// continue; // speedup
if(doGalileoReportSpeedup)
continue; // speedup
auto ret = parseTrkMeas(basic_string<uint8_t>((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size()));
for(const auto& tss : ret) {
@ -2382,8 +2564,22 @@ try
auto oldOffset = getGPSAtomicOffset(gm.tow, oldgm);
auto newOffset = getGPSAtomicOffset(gm.tow, gm);
svstat.timeDisco = oldOffset.first - newOffset.first;
if(fabs(svstat.timeDisco) < 10000)
idb.addValue(id, "clock_jump_ns", {{"value", svstat.timeDisco}}, satUTCTime(id));
if(fabs(svstat.timeDisco) < 10000) {
idb.addValue(id, "clock_jump_ns", {
{"jump", svstat.timeDisco},
{"duration", ephAge(gm.t0c * 16, oldgm.t0c * 16)},
{"old-af0", oldgm.af0},
{"old-af1", oldgm.af1},
{"old-af2", oldgm.af2},
{"old-t0c", oldgm.t0c * 16},
{"new-af0", gm.af0},
{"new-af1", gm.af1},
{"new-af2", gm.af2},
{"new-t0c", gm.t0c * 16}
}, satUTCTime(id));
}
}
}
else if(frame==2) {
@ -2435,7 +2631,8 @@ try
if(rm.type == 1057 || rm.type == 1240) {
for(const auto& ed : rm.d_ephs) {
auto iter = g_svstats.find(ed.id);
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod)
// XXX NAVCAST ONLY
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod && nmm.sourceid()==302)
iter->second.rtcmEphDelta = ed;
idb.addValue(ed.id, "rtcm-eph-correction", {
@ -2461,7 +2658,7 @@ try
for(const auto& cd : rm.d_clocks) {
auto iter = g_svstats.find(cd.id);
if(iter != g_svstats.end())
if(iter != g_svstats.end() && nmm.sourceid()==302) /// XXX wrong
iter->second.rtcmClockDelta = cd;
idb.addValue(cd.id, "rtcm-clock-correction", {
@ -2478,10 +2675,18 @@ try
}
}
else if(rm.type == 1059 || rm.type == 1242) {
for(const auto& dcb : rm.d_dcbs) {
idb.addValue(dcb.first, "rtcm-dcb", {
{"value", dcb.second}},
nmm.localutcseconds(),
nmm.sourceid());
}
}
else if(rm.type == 1060 || rm.type == 1243) {
for(const auto& ed : rm.d_ephs) {
auto iter = g_svstats.find(ed.id);
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod)
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod && nmm.sourceid()==302)
iter->second.rtcmEphDelta = ed;
idb.addValue(ed.id, "rtcm-eph-correction", {
@ -2515,7 +2720,7 @@ try
id.sv = rm.d_sv;
id.sigid = 6; // seems reasonable for E5a
static map<pair<int, int>, int> lastT0e;
static map<pair<int, int>, unsigned int> lastT0e;
pair<int, int> key(nmm.sourceid(), rm.d_sv);
if(!lastT0e.count(key) || lastT0e[key] != eg.t0e) {
@ -2549,7 +2754,7 @@ try
for(const auto& cd : rm.d_clocks) {
auto iter = g_svstats.find(cd.id);
if(iter != g_svstats.end())
if(iter != g_svstats.end() && nmm.sourceid()==302)
iter->second.rtcmClockDelta = cd;
idb.addValue(cd.id, "rtcm-clock-correction", {
@ -2588,7 +2793,9 @@ try
}
else if(nmm.type()== NavMonMessage::BeidouInavTypeD1) {
// continue; // XXX speedup
if(doGalileoReportSpeedup)
continue; // speedup
try {
SatID id{nmm.bid1().gnssid(), nmm.bid1().gnsssv(), nmm.bid1().sigid()};
@ -2623,7 +2830,7 @@ try
auto newOffset = bm.getAtomicOffset(bm.sow);
svstat.timeDisco = oldOffset.first - newOffset.first;
if(fabs(svstat.timeDisco) < 10000)
idb.addValue(id, "clock_jump_ns", {{"value", svstat.timeDisco}}, satUTCTime(id));
idb.addValue(id, "clock_jump_ns", {{"jump", svstat.timeDisco}}, satUTCTime(id));
}
svstat.lastBeidouMessage1 = bm;
}
@ -2687,7 +2894,9 @@ try
*/
}
else if(nmm.type()== NavMonMessage::GlonassInavType) {
// continue; // XXX speedup
if(doGalileoReportSpeedup)
continue; // speedup
SatID id{nmm.gloi().gnssid(), nmm.gloi().gnsssv(), nmm.gloi().sigid()};
auto& svstat = g_svstats[id];
svstat.gnss = id.gnss;
@ -2719,7 +2928,7 @@ try
if(oldgm.taun && oldgm.taun != gm.taun) {
if(gm.getGloTime() - oldgm.getGloTime() < 300) {
svstat.timeDisco = gm.getTaunNS() - oldgm.getTaunNS();
idb.addValue(id, "clock_jump_ns", {{"value", svstat.timeDisco}}, satUTCTime(id));
idb.addValue(id, "clock_jump_ns", {{"jump", svstat.timeDisco}}, satUTCTime(id));
}
}
}
@ -2754,7 +2963,9 @@ try
// cout<<"GLONASS R"<<id.second<<" str "<<strno<<endl;
}
else if(nmm.type() == NavMonMessage::SBASMessageType) {
// continue; // XXX speedup
if(doGalileoReportSpeedup)
continue; // speedup
auto& sb = g_sbas[nmm.sbm().gnsssv()];
sb.perrecv[nmm.sourceid()].last_seen = nmm.localutcseconds();
@ -2901,3 +3112,33 @@ catch(std::exception& e)
{
cerr<<"Exiting because of fatal error "<<e.what()<<endl;
}
// dedup techniques
#if 0
/*
struct DedupKey
{
SatID id;
int wn;
int tow;
basic_string<uint8_t> contents;
bool operator<(const DedupKey& rhs) const
{
return tie(id, wn, tow, contents) <
tie(rhs.id, rhs.wn, rhs.tow, rhs.contents);
}
};
static set<DedupKey> s_dedup;
DedupKey dk{id, (int)nmm.gf().gnsswn(), (int)nmm.gf().gnsstow(), inav};
if(s_dedup.insert(dk).second == false) {
// cout<<"Dedup"<<endl;
continue;
}
if(s_dedup.size() > 10000)
s_dedup.clear();
*/
// XXX conversion, may be vital
// g_svstats[id].wn = nmm.gf().gnsswn();
#endif

View File

@ -49,6 +49,8 @@ struct SVStat
GalileoMessage ephgalmsg, galmsg, oldephgalmsg;
// internal
map<int, GalileoMessage> galmsgTyped;
bool osnma{false};
time_t osnmaTime{0};
// Glonass
GlonassMessage ephglomsg, glonassMessage, oldephglomsg;

View File

@ -16,6 +16,7 @@
#include "version.hh"
#include <netinet/tcp.h>
#include "navmon.hh"
#include <mutex>
static char program[]="navrecv";
@ -140,8 +141,94 @@ void writeToDisk(time_t s, uint64_t sourceid, std::string_view message)
}
}
// This is used to report clients, so we can log them
// The idea is that cleanup runs from the Sentinel which, when destroyed, will remove the entry
struct ClientKeeper
{
struct ClientStatus
{
bool oldProtocol;
time_t lastMessage;
int station;
uint64_t messages{0};
};
struct Sentinel
{
Sentinel(ClientKeeper* parent, const ComboAddress& us) : d_parent(parent), d_us(us)
{
}
Sentinel(Sentinel&& s)
{
// cerr<<"Moved!"<<endl;
d_parent = s.d_parent;
d_us = s.d_us;
s.d_parent=0;
}
~Sentinel()
{
// cerr<<"Destructor"<<endl;
if(d_parent) {
d_parent->remove(d_us);
}
else
; //cerr<<" but we were moved already!\n";
}
void update(int station, bool oldProtocol)
{
time_t now = time(0);
std::lock_guard<std::mutex> l(d_parent->d_mut);
ClientStatus& cs = d_parent->d_clients[d_us];
cs.station = station;
cs.lastMessage = now;
cs.messages++;
cs.oldProtocol = oldProtocol;
}
ClientKeeper* d_parent;
ComboAddress d_us;
};
Sentinel reportClient(const ComboAddress& client)
{
Sentinel s2(this, client);
std::lock_guard<std::mutex> l(d_mut);
d_clients[client];
return s2;
}
void remove(const ComboAddress& client)
{
std::lock_guard<std::mutex> l(d_mut);
d_clients.erase(client);
}
void dump()
{
std::lock_guard<std::mutex> l(d_mut);
string format("{:<50}{:<5}{:<10}{:<10}{:<10}\n");
ofstream out("clients.bak");
time_t now=time(0);
out<< fmt::format(format, "IP Address", "ID", "Protocol", "Messages", "Age");
for(const auto& c : d_clients) {
out << fmt::format(format, c.first.toStringWithPort(), c.second.station, c.second.oldProtocol ? "Old" : "New", c.second.messages, now-c.second.lastMessage);
}
out.close();
unlink("clients.txt");
rename("clients.bak", "clients.txt");
}
map<ComboAddress, ClientStatus> d_clients;
std::mutex d_mut;
};
ClientKeeper g_ckeeper;
// note that this moves the socket
void recvSession2(Socket&& uns, ComboAddress client)
void recvSession2(Socket&& uns, ComboAddress client, ClientKeeper::Sentinel& sentinel)
{
string secret = SRead(uns, 8); // ignored for now
cerr << "Entering compressed session for "<<client.toStringWithPort()<<endl;
@ -177,6 +264,7 @@ void recvSession2(Socket&& uns, ComboAddress client)
memcpy(&denum, num.c_str(), 4);
denum = htonl(denum);
// cerr<<"Received message "<<denum<< " "<<nmm.localutcseconds()<<" " << nmm.localutcnanoseconds()/1000000000.0<<endl;
sentinel.update(nmm.sourceid(), false);
writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
if(first) {
@ -201,6 +289,9 @@ void recvSession(int s, ComboAddress client)
SSetsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 1); // saves file descriptors
cerr<<"Receiving messages from "<<client.toStringWithPort()<<endl;
bool first=true;
ClientKeeper::Sentinel sentinel=g_ckeeper.reportClient(client);
for(int count=0;;++count) {
string part=SRead(sock, 4);
if(part.empty()) {
@ -209,7 +300,7 @@ void recvSession(int s, ComboAddress client)
}
if(part != "bert") {
if(part == "RNIE")
return recvSession2(std::move(sock), client); // protocol v2, socket is moved cuz cleanup is special
return recvSession2(std::move(sock), client, sentinel); // protocol v2, socket is moved cuz cleanup is special
cerr << "Message "<<count<<", wrong magic from "<<client.toStringWithPort()<<": "<<makeHexDump(part)<<endl;
break;
}
@ -225,6 +316,7 @@ void recvSession(int s, ComboAddress client)
part = SRead(s, len);
if(part.size() != len) {
cerr<<"Mismatch, "<<part.size()<<", len "<<len<<endl;
// XX AND THEN WHAT??
}
out += part;
@ -234,6 +326,7 @@ void recvSession(int s, ComboAddress client)
cerr<<"\tstation: "<<nmm.sourceid() << endl;
first=false;
}
sentinel.update(nmm.sourceid(), true);
writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
}
}
@ -285,8 +378,10 @@ int main(int argc, char** argv)
thread recvThread(recvListener, std::move(receiver), recvaddr);
recvThread.detach();
sleep(5);
for(;;) {
sleep(1);
g_ckeeper.dump();
sleep(10);
}
}

View File

@ -175,16 +175,22 @@ void NMMSender::sendTCPThread(Destination* d)
void NMMSender::emitNMM(const NavMonMessage& nmm)
{
string out;
nmm.SerializeToString(& out);
emitNMM(out);
}
void NMMSender::emitNMM(const std::string& out)
{
for(auto& d : d_dests) {
d->emitNMM(nmm, d_compress);
d->emitNMM(out, d_compress);
}
}
void NMMSender::Destination::emitNMM(const NavMonMessage& nmm, bool compressed)
void NMMSender::Destination::emitNMM(const std::string& out, bool compressed)
{
string out;
nmm.SerializeToString(& out);
string msg;
if(dst.empty() || !compressed)
msg="bert";

View File

@ -16,7 +16,7 @@ class NMMSender
std::deque<std::string> queue;
std::mutex mut;
void emitNMM(const NavMonMessage& nmm, bool compress);
void emitNMM(const std::string& out, bool compress);
};
public:
@ -45,6 +45,7 @@ public:
void sendTCPThread(Destination* d);
void emitNMM(const NavMonMessage& nmm);
void emitNMM(const std::string& out);
bool d_debug{false};
bool d_compress{false}; // set BEFORE launch
bool d_pleaseQuit{false};

View File

@ -84,6 +84,8 @@ private:
struct IntervalStat
{
std::optional<int> unhealthy;
std::optional<int> dataunhealthy;
std::optional<int> osnma;
std::optional<int> sisa;
bool ripe{false};
bool expired{false};
@ -95,6 +97,7 @@ struct IntervalStat
map<SatID, map<time_t,IntervalStat>> g_stats;
int main(int argc, char **argv)
try
{
MiniCurl mc;
MiniCurl::MiniCurlHeaders mch;
@ -111,15 +114,19 @@ int main(int argc, char **argv)
string sp3src("default");
int gnssid=2;
int rtcmsrc=300;
int galwn=-1;
string influxserver="http://127.0.0.1:8086";
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--period,-p", periodarg, "period over which to report (1h, 1w)");
app.add_option("--begin,-b", beginarg, "Beginning");
app.add_option("--end,-e", endarg, "End");
app.add_option("--gal-wn", galwn, "Galileo week number to report on");
app.add_option("--sp3src", sp3src, "Identifier of SP3 source");
app.add_option("--rtcmsrc", rtcmsrc, "Identifier of RTCM source");
app.add_option("--sigid,-s", sigid, "Signal identifier. 1 or 5 for Galileo.");
app.add_option("--gnssid,-g", gnssid, "gnssid, 0 GPS, 2 Galileo");
app.add_option("--influxdb", influxDBName, "Name of influxdb database");
app.add_option("--influxserver", influxserver, "Address of influx server");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
@ -131,7 +138,11 @@ int main(int argc, char **argv)
exit(0);
}
if(beginarg.empty() && endarg.empty())
if(galwn>= 0) {
time_t w = utcFromGST(galwn, 0);
period = "time >= '"+influxTime(w)+"' and time < '"+influxTime(w+7*86400) +"'";
}
else if(beginarg.empty() && endarg.empty())
period = "time > now() - "+periodarg;
else {
period = "time > '"+ beginarg +"' and time <= '" + endarg +"'";
@ -142,13 +153,16 @@ int main(int argc, char **argv)
// auto res = mc.getURL(url + mc.urlEncode("select distinct(value) from sisa where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
string url="http://127.0.0.1:8086/query?db="+influxDBName+"&epoch=s&q=";
if(influxserver.find("http"))
influxserver="http://"+influxserver;
if(influxserver.empty() || influxserver[influxserver.size()-1]!='/')
influxserver+="/";
string url=influxserver+"query?db="+influxDBName+"&epoch=s&q=";
string sisaname = (gnssid==2) ? "sisa" : "gpsura";
string query="select distinct(value) from "+sisaname+" where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)";
cout<<"query: "<<query<<endl;
cout<<"url: "<<(url + mc.urlEncode(query))<<endl;
auto res = mc.getURL(url + mc.urlEncode(query));
auto j = nlohmann::json::parse(res);
@ -174,13 +188,44 @@ int main(int argc, char **argv)
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto healthy = (int)v[1];
g_stats[id][(int)v[0]].unhealthy = healthy; // hngg
}
}
if(gnssid == 2) {
res = mc.getURL(url + mc.urlEncode("select distinct(e1bdvs) from galhealth where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto dhealthy = (int)v[1]; // if true, "working without guarantee"
g_stats[id][(int)v[0]].dataunhealthy = dhealthy;
}
}
}
res = mc.getURL(url + mc.urlEncode("select count(\"field\") from osnma where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto osnma = (int)v[1];
if(!g_stats[id][(int)v[0]].osnma)
g_stats[id][(int)v[0]].osnma = osnma;
else
(*g_stats[id][(int)v[0]].osnma) += osnma;
}
}
res = mc.getURL(url + mc.urlEncode("select max(\"eph-age\") from ephemeris where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
@ -503,13 +548,51 @@ int main(int argc, char **argv)
}
}
/////
string dishesQuery = "select iod,sv from \"ephemeris-actual\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and iod < 128";
cout<<"dishesquery: "<<dishesQuery<<endl;
res = mc.getURL(url + mc.urlEncode(dishesQuery));
cout<<res<<endl;
j = nlohmann::json::parse(res);
map<time_t, set<int>> dishcount;
set<int> totsvs;
for(const auto& sv : j["results"][0]["series"]) {
for(const auto& v : sv["values"]) {
try {
int sv = (unsigned int)std::stoi((string)v[2]);
int t = (int)v[0];
// t &= (~31);
dishcount[t].insert(sv);
totsvs.insert(sv);
}
catch(exception& e) {
cerr<<"error: "<<e.what()<<endl;
continue;
}
}
}
map<time_t, unsigned int> maxcounts;
for(const auto& dc : dishcount) {
auto& bin = maxcounts[dc.first - (dc.first % 3600)];
if(bin < dc.second.size())
bin = dc.second.size();
cout << dc.first<<" "<<humanTimeShort(dc.first) <<", " << fmt::sprintf("%2d", dc.second.size())<<": ";
for(const auto& n : totsvs) {
if(dc.second.count(n))
cout<<fmt::sprintf("%2d ", n);
else
cout<<" ";
}
cout<<"\n";
}
ofstream hrcounts("hrcounts.csv");
hrcounts<<"timestamp,dishcount\n";
for(const auto& mc: maxcounts)
hrcounts<<mc.first<<","<<mc.second<<"\n";
/////////////////////
g_stats.erase({2,14,1});
g_stats.erase({2,18,1});
g_stats.erase({2,14,5});
g_stats.erase({2,18,5});
/*
g_stats[{2,19,1}];
*/
@ -568,7 +651,10 @@ int main(int argc, char **argv)
else if(*i.second.unhealthy==3)
testing++;
else {
if(i.second.sisa) {
if(i.second.dataunhealthy && *i.second.dataunhealthy) { // this is 'working without guarantee'
unhealthy++;
}
else if(i.second.sisa) {
if(*i.second.sisa == 255)
napa++;
else
@ -637,6 +723,9 @@ int main(int argc, char **argv)
cout<<endl;
}
cout<<"------------------------------------------------------------------------------------------"<<endl;
cout<<fmt::sprintf("Tot: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa, %6.2f%% ripe, %6.2f%% expired",
100.0*(totunobserved)/maxintervals/g_stats.size(),
@ -647,7 +736,7 @@ int main(int argc, char **argv)
100.0*totripe/maxintervals/g_stats.size(),
100.0*totexpired/maxintervals/g_stats.size());
texstream<<fmt::sprintf("Tot & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%%\\\\",
texstream<<fmt::sprintf("\\hline\nTot & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%%\\\\",
100.0*(totunobserved)/maxintervals/g_stats.size(),
100.0*totunhealthy/maxintervals/g_stats.size(),
100.0*tothealthy/maxintervals/g_stats.size(),
@ -664,5 +753,9 @@ int main(int argc, char **argv)
cout<<endl;
}
catch(exception& e)
{
cerr<<"Fatal error: "<<e.what()<<endl;
return EXIT_FAILURE;
}

View File

@ -156,6 +156,9 @@ G02 2019 12 16 00 00 00-3.670863807201E-04-7.389644451905E-12 0.000000000000E+00
for(int n=1 ; n < 7; ++n) {
if(!gzgets(d_fp, line, sizeof(line)))
return false;
if(n==1) {
entry.iodnav = getRINEXValue(line, 4);
}
if(n==3) {
double toe = getRINEXValue(line, 4);
entry.toe = toe;

View File

@ -18,6 +18,7 @@ struct RINEXEntry
int health;
int toe;
int tow;
int iodnav;
double af0, af1, af2;
double clkflags;
double BGDE1E5a, BGDE1E5b;

View File

@ -9,6 +9,7 @@ struct Value
optional<int> af0Inav;
optional<int> af0Fnav;
int af1;
int iod;
optional<int> BGDE1E5a;
optional<int> BGDE1E5b;
};
@ -30,6 +31,7 @@ int main(int argc, char** argv)
s.af1 = rint(ldexp(e.af1,46));
s.BGDE1E5a = rint(ldexp(e.BGDE1E5a,32));
s.BGDE1E5b = rint(ldexp(e.BGDE1E5b,32));
s.iod = e.iodnav;
}
else {
s.af0Fnav = rint(ldexp(e.af0,34));
@ -41,10 +43,10 @@ int main(int argc, char** argv)
}
}
cout<<"timestamp sv af0fnav af0inav af1 bgde1e5a bgde1e5b\n";
cout<<"timestamp sv iod af0fnav af0inav af1 bgde1e5a bgde1e5b\n";
for(const auto& s : satmap) {
if(s.second.af0Fnav.has_value() && s.second.af0Inav.has_value() && s.second.BGDE1E5a.has_value() && s.second.BGDE1E5b.has_value())
cout << s.first.first<<" " <<s.first.second<<" " <<
cout << s.first.first<<" " <<s.first.second<<" " << s.second.iod<<" "<<
*s.second.af0Fnav << " " << *s.second.af0Inav <<" " << s.second.af1<<" " <<*s.second.BGDE1E5a <<" " << *s.second.BGDE1E5b << "\n";
}
}

View File

@ -85,8 +85,6 @@ auto worker(HanderOuter<string>* ho)
int main(int argc, char** argv)
{
ifstream filefile(argv[1]);
string fname;
deque<string> files;

54
rtcm.cc
View File

@ -6,7 +6,8 @@ using namespace std;
void RTCMMessage::parse(const std::string& str)
{
memset(&d_gm, 0, sizeof(d_gm));
d_gm={};
// memset(&d_gm, 0, sizeof(d_gm));
auto gbu=[&str](int offset, int bits) {
return getbitu((const unsigned char*)str.c_str(), offset, bits);
};
@ -262,7 +263,56 @@ DF 385: Full seconds since the beginning of the GPS week
setbitu(rtcm->buff,i, 1,osdvs2 ); i+= 1; /* E1 DVS */
#endif
}
else if(type == 1059 || type == 1242) { // GPS/Galileo bias
int off = 0;
int msgnum = gbum(off, 12);
int gpstime = gbum(off, 20);
int uinterval = gbum(off, 4);
int mmi = gbum(off, 1);
int iodssr = gbum(off, 4);
int ssrprov = gbum(off, 16);
int ssrsol = gbum(off, 4);
int numsats = gbum(off, 6);
// cout <<"msgnum "<<msgnum<<" gpstime " << gpstime<<" numsats "<< numsats<<endl;
d_dcbs.clear();
for(int n=0; n < numsats; ++n) {
int gpsid = gbum(off, 6);
int numdcbs = gbum(off, 5);
// cout<<" "<< (type==1059 ? "G" : "E") <<gpsid<<" has "<<numdcbs <<" DCBs\n";
SatID id;
id.gnss = (type==1059 ? 0 : 2); // GPS or Galileo
id.sv = gpsid;
for(int m = 0 ; m < numdcbs; ++m) {
int sig = gbum(off, 5);
id.sigid = sig;
int dcb = gbsm(off, 14); // 0.01 meter
d_dcbs[id] = 0.01*dcb;
// cout<<" sig "<<sig <<" dcb " << dcb*0.01 << "\n";
/*
Indicator to specify the GPS signal and tracking mode:
0 - L1 C/A
1- L1 P
2- L1 Z-tracking and similar (AS on)
3 - Reserved
4 - Reserved
5 - L2 C/A
6 - L2 L1(C/A)+(P2-P1) (semi-codeless)
7 - L2 L2C (M)
8 - L2 L2C (L)
9 - L2 L2C (M+L)
10 - L2 P
11 - L2 Z-tracking and similar (AS on)
12 - Reserved
13 - Reserved
14 - L5 I
15 - L5 Q
>15 - Reserved.
*/
}
}
}
}

View File

@ -4,6 +4,7 @@
#include "navmon.hh"
#include <vector>
#include "galileo.hh"
#include <map>
struct RTCMFrame
{
@ -52,6 +53,7 @@ struct RTCMMessage
std::vector<EphemerisDelta> d_ephs;
std::vector<ClockDelta> d_clocks;
std::map<SatID, double> d_dcbs;
GalileoMessage d_gm;
int d_sv;
};

603
septool.cc 100644
View File

@ -0,0 +1,603 @@
#include <string>
#include "navmon.hh"
#include <iostream>
#include <string.h>
#include <time.h>
#include "bits.hh"
#include <sys/time.h>
#include <arpa/inet.h>
#include "galileo.hh"
#include "nmmsender.hh"
#include "CLI/CLI.hpp"
#include "swrappers.hh"
#include "sclasses.hh"
#include "version.hh"
using namespace std;
static int sepsig2ubx(int sep)
{
if(sep == 1) // GPS L1P
return 0;
else if(sep == 2) // GPS L2P
return 4;
else if(sep== 4) // GPS L5
return 7; // ??
else if(sep == 17)
return 1; // Galileo E1
else if(sep == 19)
return 8; // Galileo E6
else if(sep == 20)
return 6; // Galileo E5a
else if(sep==21)
return 5; // Galileo E5b
else if(sep==22)
return 6; // Galileo "AltBoc"
else if(sep==0)
return 0; // GPS L1
else if(sep==3)
return 4; // GPS L2c
else throw runtime_error("Asked to convert unknown Septentrio signal id "+to_string(sep));
return -1;
}
struct SEPMessage
{
SEPMessage(const std::basic_string<uint8_t>& str) : d_store(str) {}
uint16_t getID() // includes revision
{
return d_store[4] + 256*d_store[5];
}
uint16_t getIDBare() // includes revision
{
return getID() & 0xFFF;
}
std::basic_string<uint8_t> getPayload()
{
return d_store.substr(8);
}
std::basic_string<uint8_t> d_store;
};
/* format:
01 23 45 67
$@ CRC blk-id len [len-8 bytes]
|
multiple of four
bits 0-12 of blk-id are the type
*/
std::pair<SEPMessage, struct timeval> getSEPMessage(int fd, double* timeout)
{
uint8_t marker[2]={0};
bool hadskip=false;
for(;;) {
marker[0] = marker[1];
int res = readn2Timeout(fd, marker+1, 1, timeout);
if(res < 0) {
cerr<<"Readn2Timeout failed: "<<strerror(errno)<<endl;
throw EofException();
}
if(marker[0]=='$' && marker[1]=='@') { // bingo
if(hadskip) {
cerr<<"\n";
hadskip=false;
}
struct timeval tv;
gettimeofday(&tv, 0);
basic_string<uint8_t> msg;
msg.append(marker, 2); // 0,1
uint8_t b[6];
readn2Timeout(fd, b, 6, timeout);
msg.append(b, 6); // crc id len
// 0,1 = crc, 2-3 = marker, 4, 5
// uint16_t blkid = htons(b[2] + 256*b[3]);
uint16_t len = b[4] + 256*b[5];
// cerr<<"Got message of type "<<getbitu((uint8_t*)&blkid, 0, 12)<<", revision "<<
// getbitu((uint8_t*)&blkid, 12, 4)<<" ("<<ntohs(blkid)<<"), len= "<<len<<endl;
uint8_t buffer[len-8];
res=readn2Timeout(fd, buffer, len-8, timeout);
msg.append(buffer, len - 8); // checksum
return make_pair(SEPMessage(msg), tv);
}
else if(marker[1] != '$') {
hadskip=true;
cerr<<".";
}
}
}
static char program[]="septool";
uint16_t g_srcid{0};
int main(int argc, char** argv)
try
{
time_t starttime=time(0);
GOOGLE_PROTOBUF_VERIFY_VERSION;
vector<string> destinations;
g_dtLS = 18;
bool doVERSION{false}, doSTDOUT{false};
CLI::App app(program);
string sourceaddr;
bool quiet{false};
app.add_option("--source", sourceaddr, "Connect to this IP address:port to source SBF (otherwise stdin)");
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
app.add_option("--station", g_srcid, "Station id")->required();
app.add_option("--quiet", quiet, "Don't emit noise");
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_flag("--stdout", doSTDOUT, "Emit output to stdout");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
return app.exit(e);
}
if(doVERSION) {
showVersion(program, g_gitHash);
exit(0);
}
signal(SIGPIPE, SIG_IGN);
NMMSender ns;
ns.d_debug = true;
for(const auto& s : destinations) {
auto res=resolveName(s, true, true);
if(res.empty()) {
cerr<<"Unable to resolve '"<<s<<"' as destination for data, exiting"<<endl;
exit(EXIT_FAILURE);
}
ns.addDestination(s); // ComboAddress(s, 29603));
}
if(doSTDOUT)
ns.addDestination(1);
int srcfd=0;
if(!sourceaddr.empty()) {
ComboAddress src(sourceaddr);
srcfd = socket(src.sin4.sin_family, SOCK_STREAM, 0);
if(srcfd < 0)
unixDie("making socket for SBF connection");
cerr<<"Connecting to "<< src.toStringWithPort()<<" to source data..";
SConnectWithTimeout(srcfd, src, 5);
cerr<<" done"<<endl;
}
ns.d_compress = true;
ns.launch();
cerr<<"Station "<<g_srcid<<endl;
for(;;) {
double to=1000;
auto res = getSEPMessage(srcfd, &to);
if(!quiet)
cerr<<res.first.getID()<<" - " <<res.first.getIDBare() << endl;
if(res.first.getID() == 4023) { // I/NAV
auto str = res.first.getPayload();
struct SEPInav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[32];
} __attribute__((packed));
SEPInav si;
memcpy(&si, str.c_str(), sizeof(si));
// cerr<<"tow "<<si.towMsec /1000<<" wn "<<si.wn <<" sv " << (int) si.sv - 70<<" ";
if(!si.crcPassed) {
cerr<<"I/NAV CRC error, skipping"<<endl;
continue;
}
int sigid = si.src & 31;
std::string inav((char*)si.navBits, 32);
// cerr<<makeHexDump(inav)<<endl;
// byte order adjustment
std::basic_string<uint8_t> payload;
for(unsigned int i = 0 ; i < 8; ++i) {
payload.append(1, si.navBits[4 * i + 3]);
payload.append(1, si.navBits[4 * i + 2]);
payload.append(1, si.navBits[4 * i + 1]);
payload.append(1, si.navBits[4 * i + 0]);
}
basic_string<uint8_t> inav2;
// copy in the even page
for(int n = 0 ; n < 14; ++n)
inav2.append(1, getbitu(payload.c_str(), 2 + n*8, 8));
// odd page
for(int n = 0 ; n < 2; ++n)
inav2.append(1, getbitu(payload.c_str(), 116 + n*8, 8));
// cerr<<makeHexDump(inav2) << endl;
basic_string<uint8_t> reserved1;
for(int n=0; n < 5 ; ++n)
reserved1.append(1, getbitu(payload.c_str(), 116 + 16 + n*8, 8));
NavMonMessage nmm;
double t = utcFromGST(si.wn - 1024, si.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoInavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds(0); // yeah XXX
nmm.mutable_gi()->set_gnsswn(si.wn - 1024);
nmm.mutable_gi()->set_gnsstow(si.towMsec/1000.0);
nmm.mutable_gi()->set_gnssid(2);
nmm.mutable_gi()->set_gnsssv(si.sv - 70);
nmm.mutable_gi()->set_contents((const char*)&inav2[0], inav2.size());
nmm.mutable_gi()->set_sigid(sepsig2ubx(sigid));
nmm.mutable_gi()->set_reserved1((const char*)&reserved1[0], reserved1.size());
ns.emitNMM( nmm);
}
else if(res.first.getID() == 5914) {
// current time
}
else if(res.first.getID() == 4026) {
// GLONASS
}
else if(res.first.getID() == 4017) { // GPS-CA
}
else if(res.first.getID() == 4018) { // GPS-L2C
}
else if(res.first.getID() == 4019) { // GPS raw L5
}
else if(res.first.getID() == 4093) { // navic raw
}
else if(res.first.getID() == 4022) { // F/NAV
auto str = res.first.getPayload();
struct SEPFnav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[32];
} __attribute__((packed));
SEPFnav sf;
memcpy(&sf, str.c_str(), sizeof(sf));
int sigid = sf.src & 31;
// cerr<<"tow "<<sf.towMsec /1000<<" wn "<<sf.wn <<" sv " << (int) sf.sv - 70<<" sigid " << sigid <<" ";
if(!sf.crcPassed) {
cerr<<"F/NAV CRC error, skipping"<<endl;
continue;
}
std::string fnav((char*)sf.navBits, 32);
// byte order adjustment
std::basic_string<uint8_t> payload;
for(unsigned int i = 0 ; i < 8; ++i) {
payload.append(1, sf.navBits[4 * i + 3]);
payload.append(1, sf.navBits[4 * i + 2]);
payload.append(1, sf.navBits[4 * i + 1]);
payload.append(1, sf.navBits[4 * i + 0]);
}
NavMonMessage nmm;
double t = utcFromGST(sf.wn - 1024, sf.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoFnavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds(0); // yeah XXX
nmm.mutable_gf()->set_gnsswn(sf.wn - 1024);
nmm.mutable_gf()->set_gnsstow(sf.towMsec/1000.0);
nmm.mutable_gf()->set_gnssid(2);
nmm.mutable_gf()->set_gnsssv(sf.sv - 70);
nmm.mutable_gf()->set_contents((const char*)&payload[0], payload.size());
nmm.mutable_gf()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM( nmm);
}
else if(res.first.getIDBare() == 4047) {
// BDSRaw
}
else if(res.first.getIDBare() == 4006) {
auto str = res.first.getPayload();
struct PVTCartesian
{
uint32_t towMsec;
uint16_t wn;
uint8_t mode, error;
double x, y, z;
float undulation;
float vx, vy, vz;
} __attribute__((packed));
PVTCartesian pc;
memcpy(&pc, str.c_str(), sizeof(pc));
NavMonMessage nmm;
nmm.set_type(NavMonMessage::ObserverPositionType);
nmm.set_localutcseconds(utcFromGST(pc.wn - 1024, pc.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_sourceid(g_srcid);
nmm.mutable_op()->set_x(pc.x);
nmm.mutable_op()->set_y(pc.y);
nmm.mutable_op()->set_z(pc.z);
nmm.mutable_op()->set_acc(3.14);
ns.emitNMM( nmm);
{
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(pc.wn - 1024, pc.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ObserverDetailsType);
nmm.mutable_od()->set_vendor("Septentrio");
nmm.mutable_od()->set_hwversion("Mosaic");
nmm.mutable_od()->set_swversion("");
nmm.mutable_od()->set_serialno("3060601");
nmm.mutable_od()->set_modules("");
nmm.mutable_od()->set_clockoffsetns(0);
nmm.mutable_od()->set_clockoffsetdriftns(0);
nmm.mutable_od()->set_clockaccuracyns(0);
nmm.mutable_od()->set_freqaccuracyps(0);
nmm.mutable_od()->set_owner("Septentrio");
nmm.mutable_od()->set_remark("");
nmm.mutable_od()->set_recvgithash(g_gitHash);
nmm.mutable_od()->set_uptime(time(0) - starttime);
ns.emitNMM( nmm);
}
}
else if(res.first.getIDBare() == 4024) { // GALRawCNAV
struct SEPCnav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[64];
} __attribute__((packed));
SEPCnav sc;
auto str = res.first.getPayload();
memcpy(&sc, str.c_str(), sizeof(sc));
int sigid = sc.src & 31;
// cerr<<"C/NAV tow "<<sc.towMsec /1000<<" wn "<<sc.wn <<" sv " << (int) sc.sv - 70<<" sigid " << sigid <<" ";
if(!sc.crcPassed) {
cerr<<"C/NAV CRC error, skipping"<<endl;
continue;
}
std::string cnav((char*)sc.navBits, 64);
// byte order adjustment
std::basic_string<uint8_t> payload;
for(unsigned int i = 0 ; i < 16; ++i) {
payload.append(1, sc.navBits[4 * i + 3]);
payload.append(1, sc.navBits[4 * i + 2]);
payload.append(1, sc.navBits[4 * i + 1]);
payload.append(1, sc.navBits[4 * i + 0]);
}
unsigned char crc_buff[58]={0};
unsigned int i;
for (i=0; i< 462;i++)
setbitu(crc_buff, 2+i, 1,getbitu(payload.c_str(),i, 1));
int calccrc=rtk_crc24q(crc_buff,58);
int realcrc= getbitu(payload.c_str(), 14+448, 24);
if (calccrc != realcrc) {
cerr << "CRC mismatch, " << calccrc << " != " << realcrc <<endl;
}
// else
// cerr<<"Checksum Correct: "<<calccrc << " = " << realcrc<<endl;
NavMonMessage nmm;
double t = utcFromGST(sc.wn - 1024, sc.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoCnavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds(0); // yeah XXX
nmm.mutable_gc()->set_gnsswn(sc.wn - 1024);
nmm.mutable_gc()->set_gnsstow(sc.towMsec/1000.0);
nmm.mutable_gc()->set_gnssid(2);
nmm.mutable_gc()->set_gnsssv(sc.sv - 70);
nmm.mutable_gc()->set_contents((const char*)&payload[0], payload.size());
nmm.mutable_gc()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM( nmm);
}
else if(res.first.getIDBare() == 4027) {
auto str = res.first.getPayload();
struct MeasEpoch
{
uint32_t towMsec;
uint16_t wn;
uint8_t n1;
uint8_t sb1len;
uint8_t sb2len;
uint8_t commonFlags;
uint8_t clkJumps;
uint8_t res1;
} __attribute__((packed));
MeasEpoch me;
memcpy(&me, str.c_str(), sizeof(me));
// cerr<<"Got "<<(int)me.n1<<" signal statuses, block1 "<<(int)me.sb1len<<", block2 "<<(int)me.sb2len<<endl;
struct Block1
{
uint8_t rxchannel, type, sv, misc; // misc contains 4 bits of codeMSB
uint32_t codeLSB;
int32_t doppler;
uint16_t carrierLSB;
int8_t carrierMSB;
uint8_t cn0;
uint16_t lockTime;
uint8_t obsinfo;
uint8_t n2;
} __attribute__((packed));
struct Block2
{
uint8_t type, locktime, cn0, offsetMSB;
int8_t carrierMSB;
uint8_t obsinfo;
uint16_t codeoffsetLSB;
uint16_t carrierLSB;
uint16_t dopplerOffsetLSB;
} __attribute__((packed));
int pos = sizeof(me);
for(int n = 0 ; n < me.n1; ++n) {
Block1 b1;
memcpy(&b1, str.c_str() + pos, sizeof(b1));
uint8_t sigid = b1.type & 31;
// cerr<<"sv "<<(int)b1.sv<<" sigid "<< (int)sigid <<" cn0 ";
double db;
if(sigid==1 || sigid ==2)
db = b1.cn0 *0.25;
else db = b1.cn0 * 0.25 + 10;
// cerr<<" "<<db;
// cerr<<" n2 "<< (int)b1.n2;
// cerr<<endl;
pos += me.sb1len;
if(b1.sv <= 36 || (b1.sv > 70 && b1.sv <= 106)) {
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(me.wn - 1024, me.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ReceptionDataType);
nmm.mutable_rd()->set_gnssid(b1.sv > 70 ? 2 : 0 );
nmm.mutable_rd()->set_gnsssv(b1.sv <= 37 ? b1.sv : b1.sv - 70);
nmm.mutable_rd()->set_db(db);
nmm.mutable_rd()->set_el(0);
nmm.mutable_rd()->set_azi(0);
nmm.mutable_rd()->set_prres(-1);
nmm.mutable_rd()->set_qi(7);
try {
nmm.mutable_rd()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM(nmm);
}
catch(...){}
/*
LSB of the pseudorange. The pseudorange expressed in meters
is computed as follows:
PR type1 [m] = ( CodeMSB *4294967296+ CodeLSB )*0.001
codeMSB hides in bits 0-3 of misc.
*/
}
for(int m = 0 ; m < b1.n2; ++m) {
Block2 b2;
memcpy(&b2, str.c_str() + pos, sizeof(b2));
pos += me.sb2len;
sigid = b2.type & 31;
// cerr<<"\t sigid "<<(int)sigid<<" cn0 ";
if(sigid==1 || sigid ==2)
db= b2.cn0 *0.25;
else db = b2.cn0 * 0.25 + 10;
// cerr<<db;
// cerr<<endl;
if(b1.sv <= 36 || (b1.sv > 70 && b1.sv <= 106)) {
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(me.wn - 1024, me.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ReceptionDataType);
nmm.mutable_rd()->set_gnssid(b1.sv > 70 ? 2 : 0 );
nmm.mutable_rd()->set_gnsssv(b1.sv <= 37 ? b1.sv : b1.sv - 70);
nmm.mutable_rd()->set_db(db);
nmm.mutable_rd()->set_el(0);
nmm.mutable_rd()->set_azi(0);
nmm.mutable_rd()->set_prres(0);
nmm.mutable_rd()->set_qi(7);
try {
nmm.mutable_rd()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM(nmm);
}catch(...){} // might be unknown signal type
}
}
}
}
else {
if(!quiet)
cerr<<"Unknown message "<<res.first.getID() << " / " <<res.first.getIDBare()<<" ("<<res.first.d_store.size()<<" bytes)"<<endl;
}
}
}
catch(std::exception& e)
{
cerr<<"Fatal: "<<e.what()<<endl;
}
catch(EofException& e)
{
}
/*
NAVBits contains the 234 bits of an I/NAV navigation page (in nominal
or alert mode). Note that the I/NAV page is transmitted as two sub-pages
(the so-called even and odd pages) of duration 1 second each (120 bits
each).
In this block, the even and odd pages are concatenated, even page
first and odd page last. The 6 tails bits at the end of the even page are
removed (hence a total of 234 bits). If the even and odd pages have been
received from two different carriers (E5b and L1), bit 5 of the Source
field is set.
*/

29
ubx.cc
View File

@ -3,6 +3,7 @@
#include "fmt/format.h"
#include "fmt/printf.h"
#include "bits.hh"
#include "navmon.hh"
using namespace std;
uint16_t calcUbxChecksum(uint8_t ubxClass, uint8_t ubxType, std::basic_string_view<uint8_t> str)
@ -60,7 +61,12 @@ std::basic_string<uint8_t> buildUbxMessage(uint8_t ubxClass, uint8_t ubxType, st
return msg;
}
basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg)
basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg,
basic_string<uint8_t>& reserved1,
basic_string<uint8_t>& reserved2,
basic_string<uint8_t>& sar,
basic_string<uint8_t>& spare,
basic_string<uint8_t>& crc)
{
// byte order adjustment
std::basic_string<uint8_t> payload;
@ -74,10 +80,14 @@ basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg)
for (i=0,j= 4;i<15;i++,j+=8) setbitu(crc_buff,j,8,getbitu(payload.c_str() ,i*8,8));
for (i=0,j=118;i<11;i++,j+=8) setbitu(crc_buff,j,8,getbitu(payload.c_str()+16,i*8,8));
if (rtk_crc24q(crc_buff,25) != getbitu(payload.c_str()+16,82,24)) {
cout << "CRC mismatch, " << rtk_crc24q(crc_buff, 25) << " != " << getbitu(payload.c_str()+16,82,24) <<endl;
cerr << "CRC mismatch, " << rtk_crc24q(crc_buff, 25) << " != " << getbitu(payload.c_str()+16,82,24) <<endl;
throw CRCMismatch();
}
crc.clear();
for(i=0; i < 3; ++i)
crc.append(1, getbitu(payload.c_str()+16,82+i*8,8));
std::basic_string<uint8_t> inav;
for (i=0,j=2; i<14; i++, j+=8)
@ -85,6 +95,21 @@ basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg)
for (i=0,j=2; i< 2; i++, j+=8)
inav.append(1, (unsigned char)getbitu(payload.c_str()+16,j,8));
reserved1.clear();
for(i=0, j=18; i < 5 ; i++, j+=8)
reserved1.append(1, (unsigned char)getbitu(payload.c_str()+16, j, 8));
// cerr<<"reserved1: "<<makeHexDump(reserved1)<<endl;
sar.clear();
for(i=0, j=58; i < 3 ; i++, j+=8) // you get 24 bits
sar.append(1, (unsigned char)getbitu(payload.c_str()+16, j, 8));
spare.clear();
spare.append(1, (unsigned char)getbitu(payload.c_str()+16, 80, 2));
reserved2.clear();
reserved2.append(1, (unsigned char)getbitu(payload.c_str()+16, 106, 8));
return inav;
}

8
ubx.hh
View File

@ -6,7 +6,13 @@ std::basic_string<uint8_t> buildUbxMessage(uint8_t ubxClass, uint8_t ubxType, st
std::basic_string<uint8_t> buildUbxMessage(uint8_t ubxClass, uint8_t ubxType, const std::initializer_list<uint8_t>& str);
std::basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg);
std::basic_string<uint8_t> getInavFromSFRBXMsg(std::basic_string_view<uint8_t> msg,
std::basic_string<uint8_t>& reserved1,
std::basic_string<uint8_t>& reserved2,
std::basic_string<uint8_t>& sar,
std::basic_string<uint8_t>& spare,
std::basic_string<uint8_t>& crc);
std::basic_string<uint8_t> getGPSFromSFRBXMsg(std::basic_string_view<uint8_t> msg);
std::basic_string<uint8_t> getGlonassFromSFRBXMsg(std::basic_string_view<uint8_t> msg);
std::basic_string<uint8_t> getBeidouFromSFRBXMsg(std::basic_string_view<uint8_t> msg);

View File

@ -496,7 +496,7 @@ struct TIMEGPS
// ubxtool device srcid
int main(int argc, char** argv)
{
time_t starttime=time(0);
auto starttime = std::chrono::steady_clock::now();
GOOGLE_PROTOBUF_VERIFY_VERSION;
CLI::App app(program);
@ -927,7 +927,7 @@ int main(int argc, char** argv)
enableUBXMessageOnPort(fd, 0x02, 0x59, ubxport); // UBX-RXM-RLM
}
if (doDEBUG) { cerr<<humanTimeNow()<<" Enabling UBX-MON-HW"<<endl; } // SAR
if (doDEBUG) { cerr<<humanTimeNow()<<" Enabling UBX-MON-HW"<<endl; }
enableUBXMessageOnPort(fd, 0x0A, 0x09, ubxport, 16); // UBX-MON-HW
@ -1385,7 +1385,8 @@ int main(int argc, char** argv)
ns.emitNMM( nmm);
}
else if(id.first ==2) { // GALILEO
auto inav = getInavFromSFRBXMsg(payload);
basic_string<uint8_t> reserved1, reserved2, sar, spare, crc;
auto inav = getInavFromSFRBXMsg(payload, reserved1, reserved2, sar, spare, crc);
unsigned int wtype = getbitu(&inav[0], 0, 6);
uint32_t satTOW;
@ -1466,6 +1467,11 @@ int main(int argc, char** argv)
nmm.mutable_gi()->set_gnsssv(id.second);
nmm.mutable_gi()->set_sigid(sigid);
nmm.mutable_gi()->set_contents((const char*)&inav[0], inav.size());
nmm.mutable_gi()->set_reserved1((const char*)&reserved1[0], reserved1.size());
nmm.mutable_gi()->set_reserved2((const char*)&reserved2[0], reserved2.size());
nmm.mutable_gi()->set_sar((const char*) &sar[0], sar.size());
nmm.mutable_gi()->set_crc((const char*) &crc[0], crc.size());
nmm.mutable_gi()->set_spare((const char*)&spare[0], spare.size());
ns.emitNMM( nmm);
}
@ -1697,7 +1703,7 @@ int main(int argc, char** argv)
nmm.mutable_od()->set_owner(owner);
nmm.mutable_od()->set_remark(remark);
nmm.mutable_od()->set_recvgithash(g_gitHash);
nmm.mutable_od()->set_uptime(time(0) - starttime);
nmm.mutable_od()->set_uptime(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now()-starttime).count());
ns.emitNMM( nmm);

View File

@ -149,17 +149,21 @@ void ZStdReader::worker()
for(;;) {
input.pos=0;
input.size=read(d_sourcefd, (char*)input.src, inputcapacity);
if(input.size <= 0) {
int ret = read(d_sourcefd, (char*)input.src, inputcapacity);
if(ret <= 0) {
cerr<<"Got EOF on input fd "<<d_sourcefd<<", terminating thread"<<endl;
break;
}
input.size = ret; // this is unsigned, so we need 'ret' to see the error
while(input.pos != input.size) {
output.pos=0;
output.size=outputcapacity;
ZSTD_decompressStream(z, &output, &input);
int res = ZSTD_decompressStream(z, &output, &input);
if(ZSTD_isError(res)) {
cerr<<"Error decompressing ZSTD data"<<endl;
break;
}
int res;
res = writen(d_writepipe, output.dst, output.pos);
if(!res) // we are history
break;