Merge branch 'master' into armhf
commit
ac9f8576b3
|
@ -12,7 +12,7 @@ RUN sed -i "s%http://archive.ubuntu.com/ubuntu/%${APT_URL}%" /etc/apt/sources.li
|
|||
# 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 \
|
||||
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
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ RUN set -ex && \
|
|||
g++ \
|
||||
git \
|
||||
libprotobuf-dev \
|
||||
libzstd-dev \
|
||||
make \
|
||||
protobuf-compiler \
|
||||
&& \
|
||||
|
|
|
@ -23,6 +23,8 @@ RUN set -ex && \
|
|||
git \
|
||||
make \
|
||||
protobuf-dev \
|
||||
zstd-dev \
|
||||
zstd-static \
|
||||
&& \
|
||||
:
|
||||
|
||||
|
|
66
Makefile
66
Makefile
|
@ -1,6 +1,6 @@
|
|||
CFLAGS = -O3 -Wall -ggdb
|
||||
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O2 -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O2 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
-Iext/fmt-6.1.2/include/ -Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
|
||||
-I/usr/local/opt/openssl/include/ \
|
||||
-Iext/sgp4/libsgp4/ \
|
||||
|
@ -8,6 +8,13 @@ CXXFLAGS:= -std=gnu++17 -Wall -O2 -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
|
|||
|
||||
# CXXFLAGS += -Wno-delete-non-virtual-dtor
|
||||
|
||||
# If unset, create a variable for the path or binary to use as "install" for debuild.
|
||||
INSTALL ?= install
|
||||
# If unset, create a variable with the path used by "make install"
|
||||
prefix ?= /usr/local/ubxtool
|
||||
# If unset, create a variable for a path underneath $prefix that stores html files
|
||||
htdocs ?= /share/package
|
||||
|
||||
ifneq (,$(wildcard ubxsec.c))
|
||||
EXTRADEP = ubxsec.o
|
||||
else ifneq (,$(wildcard ubxsec.o))
|
||||
|
@ -18,7 +25,7 @@ endif
|
|||
CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
|
||||
|
||||
PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter \
|
||||
galmonmon rinreport
|
||||
galmonmon rinreport rtcmtool
|
||||
|
||||
EXTRA_PROGRAMS = ubxtool.static ubxtool.nodeps
|
||||
|
||||
|
@ -28,6 +35,13 @@ all: navmon.pb.cc $(PROGRAMS)
|
|||
|
||||
-include *.d
|
||||
|
||||
navmon.pb.h: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
navmon.pb.cc: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
|
||||
H2OPP=ext/powerblog/h2o-pp.o
|
||||
SIMPLESOCKETS=ext/powerblog/ext/simplesocket/swrappers.o ext/powerblog/ext/simplesocket/sclasses.o ext/powerblog/ext/simplesocket/comboaddress.o
|
||||
|
||||
|
@ -35,20 +49,48 @@ clean:
|
|||
rm -f *~ *.o *.d ext/*/*.o ext/*/*.d $(PROGRAMS) $(EXTRA_PROGRAMS) navmon.pb.h navmon.pb.cc $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) $(H2OPP) $(SIMPLESOCKETS)
|
||||
rm -f ext/fmt-6.1.2/src/format.[do] ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
|
||||
|
||||
help2man:
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/share/man/man1
|
||||
HELP2MAN_DESCRIPTION=Open-source GNSS Monitoring Project
|
||||
$(foreach binaryfile,$(PROGRAMS),help2man -N -n "$(HELP2MAN_DESCRIPTION)" ./$(binaryfile) | gzip > $(DESTDIR)$(prefix)/share/man/man1/$(binaryfile).1.gz;)
|
||||
@echo until these binaries support --help and --version remove the broken output
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/testrunner.1.gz
|
||||
|
||||
install: $(PROGRAMS) help2man
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/bin
|
||||
$(foreach binaryfile,$(PROGRAMS),$(INSTALL) -s -m 755 -D ./$(binaryfile) $(DESTDIR)$(prefix)/bin/$(binaryfile);)
|
||||
@echo "using cp instead of install because recursive directories of ascii"
|
||||
mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon
|
||||
cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/
|
||||
|
||||
download-debian-package:
|
||||
apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
|
||||
echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
apt-get update && apt-get install -y galmon
|
||||
|
||||
download-raspbian-package:
|
||||
apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
|
||||
echo "deb https://ota.bike/raspbian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
apt-get update && apt-get install -y galmon
|
||||
|
||||
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
|
||||
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
|
||||
$(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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
|
||||
|
||||
tracker: tracker.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
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
|
||||
|
||||
|
||||
galmonmon: galmonmon.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
|
||||
$(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 ${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 ${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
|
||||
|
@ -62,8 +104,8 @@ navcat: navcat.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmo
|
|||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
|
||||
navrecv: navrecv.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
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
|
||||
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
@ -71,20 +113,20 @@ tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) gith
|
|||
rinreport: rinreport.o rinex.o githash.o navmon.o ext/fmt-6.1.2/src/format.o ephemeris.o osen.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread
|
||||
|
||||
rtcmtool: rtcmtool.o navmon.pb.o githash.o ext/fmt-6.1.2/src/format.o bits.o nmmsender.o $(SIMPLESOCKETS) navmon.o rtcm.o zstdwrap.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lprotobuf -lzstd
|
||||
|
||||
navmon.pb.cc: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
UBXTOOL_DEPS = 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
|
||||
UBXTOOL_DEPS = 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
|
||||
|
||||
ubxtool: $(UBXTOOL_DEPS)
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd
|
||||
# Static build with musl on alpine and clang
|
||||
ubxtool.static: $(UBXTOOL_DEPS)
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lstdc++ -static
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lstdc++ -static
|
||||
# Static linking of `glibc` is non-trivial, so glibc is kept dynamically linked
|
||||
ubxtool.nodeps: $(UBXTOOL_DEPS)
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib /usr/lib/*-linux-*/libprotobuf.a -pthread -static-libgcc -static-libstdc++
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib /usr/lib/*-linux-*/libprotobuf.a -pthread /usr/lib/*-linux-*/libzstd.a -static-libgcc -static-libstdc++
|
||||
|
||||
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
|
||||
|
|
|
@ -6,7 +6,8 @@ Here are some ground rules to follow once you have locally tested your galmon st
|
|||
|
||||
* Please ask bert@hubertnet.nl or [ahu on
|
||||
IRC](https://webchat.oftc.net/?channels=galileo) or
|
||||
[@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) for a station ID.
|
||||
[@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) for a station ID and receiver
|
||||
hostname.
|
||||
* Please do not randomly pick a station ID!! Project data quality and integrity
|
||||
depends on your cooperation.
|
||||
* The rule is: one station ID per receiver. Do not under any circumstances
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# .deb Package Overview
|
||||
|
||||
2020-01-20: Initial Commit
|
||||
|
||||
## Important Information
|
||||
|
||||
This section is a step-by-step tutorial.
|
||||
|
||||
### Basic Installation
|
||||
|
||||
Pick either debian (almost everything) or raspbian (special armv6 build for older models) and create the source file.
|
||||
```sh
|
||||
echo "deb https://ota.bike/raspbian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
```
|
||||
|
||||
Install the file used to ensure the software is verified.
|
||||
```sh
|
||||
apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
|
||||
```
|
||||
|
||||
Update your package list and install galmon. Then create a configuration file and start the daemon.
|
||||
If you have a typical device using the onboard USB at /dev/ttyACM0, drop the directory element
|
||||
and refer to ttyACM0 in both the default variable file and the unit name.
|
||||
```sh
|
||||
apt-get update && apt-get install -y galmon
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM0
|
||||
systemctl enable --now ubxtool@ttyACM0
|
||||
```
|
||||
|
||||
Alternate or multiple devices just repeats that, updating the device name:
|
||||
```sh
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM3
|
||||
systemctl enable --now ubxtool@ttyACM3
|
||||
```
|
||||
Both the ubxtool-ttyXYZn file and /dev/ttyXYZn device must exist for the unit file conditions to pass.
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
Armbian and Raspbian have apt-daily timers enabled by default.
|
||||
However, most configurations for unattended installs require customization.
|
||||
|
||||
A simple timer is included that will apply any galmon upgrades every three days:
|
||||
```sh
|
||||
systemctl enable --now galmon-upgrade.timer
|
||||
```
|
||||
|
||||
You can perform an immediate update by hand:
|
||||
```sh
|
||||
apt-get update && apt-get -y install galmon && systemctl restart ubxtool@*
|
||||
```
|
||||
|
||||
## Reference Information
|
||||
|
||||
You can stop reading here if your interest was limited to installing a compiled package.
|
||||
|
||||
### One time steps for bootstrapping package build on a fresh git repo
|
||||
|
||||
Run debmake in the source directory. It tries to autocreate 90% of everything you need in the debian folder.
|
||||
Key files: copyright, changelog, control, and anything else that looks interesting to cat. Once they exist, we're done.
|
||||
Refer to the manual's [tutorial](https://www.debian.org/doc/manuals/debmake-doc/ch04.en.html).
|
||||
|
||||
### One time steps for creating package-specific files and scripts
|
||||
|
||||
Inside the debian directory are files that begin with galmon, the name of the package as defined in the control file.
|
||||
- galmon.postinst: this script is run after installation to verify a system account exists.
|
||||
- galmon.default: this file is installed as /etc/default/galmon
|
||||
- galmon.ubxtool@.service: this unit file uses %i as a reference to the device for computers with multiple inputs.
|
||||
|
||||
### How to build the package locally
|
||||
|
||||
In short you need to set some variables, refer to profile-debuild.sh in the debian/ directory.
|
||||
After that, use debuild to install the package. Signing of the end result may fail and creating
|
||||
GPG key pairs is beyond the scope of this document but just make sure you match the email in the changelog.
|
||||
```sh
|
||||
apt-get install -y build-essential devscripts lintian diffutils patch patchutils
|
||||
git clone $flags galmon.git ; cd galmon
|
||||
./make-githash.h
|
||||
# create and source variables in /etc/profile.d/debuild.sh
|
||||
debuild
|
||||
dpkg -i ../*.deb
|
||||
```
|
||||
|
||||
### Future maintenance considerations
|
||||
|
||||
The githash.h files cannot change after the debuild process has started.
|
||||
For now, the Makefile used by debuild does not run that script and
|
||||
the files must be created before starting the debuild process.
|
||||
|
||||
### Real World Build Results in January 2020
|
||||
|
||||
Avoid compiling on arm6 computers, it is slow. The arm6 used in cheap Raspberry Pi models is an expensive model to support
|
||||
relative to the much faster arm7 and arm8 computers available. Compiling approaches 90 minutes at O3.
|
||||
|
||||
The arm7 and arm8 both compile in 20 to 30 minutes at -j1 -O3 but the 64 bit arm8 has approximately
|
||||
double the RAM requirements during compilation. To avoid swapping, increasing compile time 150%,
|
||||
use hardware with at least 1GB of RAM. The NanoPi Neo2 and NanoPi ZeroPi models with 512MB of RAM
|
||||
are perfect clients, but the OrangePi PCs with 1GB of RAM and the Allwinner H3 or H5 are
|
||||
better suited for smooth building. For comparison, a VM on a low-end AMD Ryzen 3 2200G builds the package at -j1 in about two minutes.
|
||||
|
||||
These are fast multi-core computers but we turn off parallel compiles because of limited RAM.
|
||||
Limiting optimizations to -O0 cuts the compile time in half approximately.
|
||||
|
||||
### Why do this?
|
||||
|
||||
Convenience, uniformity, and scalability:
|
||||
Hand-compiling software is fun, but vendor package management solutions
|
||||
exist to give us reliable unattended installations for free.
|
||||
|
||||
### Signing key
|
||||
|
||||
GPG Public Key [86E7F51C04FBAAB0](debian/86E7F51C04FBAAB0.asc)
|
73
README.md
73
README.md
|
@ -79,7 +79,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
|
||||
libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
|
||||
git clone https://github.com/ahupowerdns/galmon.git --recursive
|
||||
cd galmon
|
||||
make
|
||||
|
@ -157,7 +157,7 @@ To see what is going on, try:
|
|||
To distribute data to a remote `navrecv`, use:
|
||||
|
||||
```
|
||||
./ubxtool --wait --port /dev/ttyACM0 --galileo --station 255 --dest 127.0.0.1
|
||||
./ubxtool --wait --port /dev/ttyACM0 --galileo --station 255 --destination 127.0.0.1
|
||||
```
|
||||
|
||||
This will send protobuf to 127.0.0.1:29603. You can add as many destinations
|
||||
|
@ -191,11 +191,11 @@ cp ubxtool ubxtool.sh /usr/local/ubxtool/
|
|||
cp ubxtool.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
Then collect the server IP address (SERVER-IP) and a station number
|
||||
(STATION-NUMBER) as described in [Operator.md], and run:
|
||||
Then please reach out as indicated in [Operator.md] to obtain your
|
||||
station ID and the receiver hostname and run:
|
||||
|
||||
```
|
||||
echo SERVER-IP > /usr/local/ubxtool/destination
|
||||
echo RECEIVER-NAME > /usr/local/ubxtool/destination
|
||||
echo STATION-NUMBER > /usr/local/ubxtool/station
|
||||
```
|
||||
|
||||
|
@ -286,23 +286,56 @@ The software can interpret SP3 files, good sources:
|
|||
* 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.
|
||||
'navdump ' on collected protobuf, and it will output 'sp3.csv' with fit data.
|
||||
|
||||
RTCM
|
||||
----
|
||||
RTCM is the Radio Technical Commission for Maritime Services, and
|
||||
confusingly, also the name of a protocol. This project can parse RTCM 10403.1
|
||||
messages, and currently processes State Space Representation (SSR) messages,
|
||||
specifically types 1057/1240 (GPS/Galileo Orbit corrections to broadcast
|
||||
ephemeris) and 1058/1241 (GPS/Galileo Clock corrections to broadcast
|
||||
ephemeris).
|
||||
|
||||
RTCM messages need to be converted to protobuf format, and the `rtcmtool` is
|
||||
provided for this purpose.
|
||||
|
||||
RTCM is frequently transmitted over the internet using 'ntrip', a typical
|
||||
commandline to process RTCM in our project is:
|
||||
```
|
||||
$ ntripclient ntrip:CLKA0_DEU1/user:password@navcast.spaceopal.com:2101 | ./rtcmtool --station x --destination y
|
||||
```
|
||||
|
||||
User and password can be obtained from https://spaceopal.com/navcast/ - the
|
||||
Galileo operating company.
|
||||
|
||||
There are many other sources of RTCM but currently not many offer the SSR
|
||||
messages we can use.
|
||||
|
||||
Tooling
|
||||
-------
|
||||
|
||||
* ubxtool: Configure and use a Ublox chip to gather messages, and send as
|
||||
protobuf to standard output or a remote server (with buffering).
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report"
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
* rtcmtool: accepts RTCM messages on standard input (for example coming
|
||||
from ntripclient) and transmits them as protobuf messages, either to
|
||||
stdout or to a navrecv server. This is the equivalent of 'ubxtool'
|
||||
except for submitting RTCM messages.
|
||||
|
||||
Big TODO
|
||||
--------
|
||||
|
||||
* Dual goals: completeness, liveness, not the same
|
||||
For forensics, great if the packet is there
|
||||
For display, not that bad if we missed a message
|
||||
* In general, consider refeed strategy
|
||||
Raw serial
|
||||
Protobuf
|
||||
Influxdb
|
||||
".csv files"
|
||||
* Delivery needs to be bit more stateful (queue)
|
||||
|
||||
* Semantics definition for output of Navnexus
|
||||
"we'll never surprise you with old data"
|
||||
|
||||
Global coverage (via volunteers)
|
||||
--------------------------------
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
Architecture
|
||||
------------
|
||||
|
||||
The galmon project consists of several components. Core of what we do are
|
||||
protobuf messages that are (mostly) device independent and contain satellite
|
||||
data and metadata.
|
||||
|
||||
All protobuf messages are 'NavMonMessages', of which there are 15 types.
|
||||
These messages are:
|
||||
|
||||
* Navigation messages from satellites, various types
|
||||
* Device / software metadata
|
||||
* Reception strength details
|
||||
* Doppler, carrier phase data
|
||||
* RTCM messages
|
||||
|
||||
For every supported receiver type, there is a tool to convert the device
|
||||
specific protocol to these protobuf messages. Currently we only have
|
||||
'ubxtool' that does this for many u-blox devices. In addition, 'rtcmtool'
|
||||
converts a RTCM to protobuf.
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
Core components:
|
||||
* ubxtool/rtcmtool: generate protobuf messages, transmit them over network
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
|
||||
This offers a complete suite for:
|
||||
|
||||
* generating protobuf messages and transmitting them
|
||||
* reception & long-term storage
|
||||
* serving protobuf messages
|
||||
* analysing them for display & storing statistics for analysis
|
||||
|
||||
|
||||
Non-core tools:
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report", based on the
|
||||
time-series database filled by navparse
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
|
||||
Transmission details
|
||||
--------------------
|
||||
Messages are sent as protobuf messages with an intervening magic value and a
|
||||
length field.
|
||||
|
||||
Both rtcmtool and ubxtool can send data over TCP/IP, but also to standard
|
||||
output. This standard output mode makes it possible to pipe the output of
|
||||
these tools straight into navdump or navparse.
|
||||
|
||||
Storage details
|
||||
---------------
|
||||
Storage is very simplistic but robust. For every receiver, for every hour,
|
||||
there is a file with protobuf messages. That's it. The goal is for the
|
||||
receiver to never ever go down.
|
||||
|
||||
navnexus and navcat can consume data from this simplistic storage and serve
|
||||
it over TCP/IP or to standard output.
|
||||
|
|
@ -17,7 +17,7 @@ int beidouBitconv(int their);
|
|||
C05 (58.75E)
|
||||
*/
|
||||
|
||||
struct BeidouMessage
|
||||
struct BeidouMessage : GPSLikeEphemeris
|
||||
{
|
||||
uint8_t strtype;
|
||||
|
||||
|
@ -118,7 +118,11 @@ struct BeidouMessage
|
|||
double getCrc() const { return ldexp(crc, -6); } // meters
|
||||
double getCrs() const { return ldexp(crs, -6); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void parse2(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF4kzeMBEAClLiZy741lfdoZHvlCbU1k4o9CglhVAPnSqTo7tH0AkdXmp/sG
|
||||
Sxk20miLlNNaxTYhHLyWIMhLoR3ogAJSoUsbMo4cpC5owdai06Q2nINTGiD1Vwak
|
||||
MmSStQl/aG01G1u9Ei/FVSr/K6Nd8u+dvUoU6tFSoXtXXaRB5pkyLOaZXq6UTwhw
|
||||
tI5f+uH2d8kgkoGNWl/jevn7K5AqCWmklfzYFCeogXemcNMdisrm2RzYzay70Qo+
|
||||
3qKHzQUN5S6Ne2yYKYN1UacGg+38+zhoSNGy8fB2XfCf8mbW2NnnZBdPEmpu1yos
|
||||
uvjj/oauZlLgkx/ZzK4PzmWqpe+aEiV7Z8lPO3Y2U/cwkU56aMIFQq2DU4FgQUdn
|
||||
6J+tLeMPh0sbIfYeOfF5IdFi85PdD4w7C8hmxu7aHl/B/GCnOFYAsSChpf4bpgWB
|
||||
G1Pne56evHeSNYXQCdE4rjyYGqB/F+EWn/NYcOdKSkOxxE5bL/aU/uqPss5E54nt
|
||||
z+qsN9ynGNWsnkPQECulM3svS3/nffnmgFSuDZyON4Y8/LFIkPizBiqOtrVEAKdK
|
||||
CqKz+A2fgSamM5IbJbCSkXWMcU6W0Sz0DzSpMbVf5kKER7WeDoeWr6yUHqAkPyHz
|
||||
bx/YLWwrAjuuN00/MuIl8XXHWBHsfRqIrqLSBbie5mbOv2MloBEdrvZqvwARAQAB
|
||||
tDRHYWxtb24gRGViaWFuIFByaW1hcnkgU2lnbmluZyBLZXkgPGRlYmlhbkBnYWxt
|
||||
b24uZXU+iQJUBBMBCAA+FiEEbe3qslh61wzGNEF6huf1HAT7qrAFAl4kzeMCGwMF
|
||||
CRaOagAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQhuf1HAT7qrCwxxAAj2fs
|
||||
9qcWKJSCPPpDNU6gzjuqFj2fUmvihf0Ki9LbBLMt5pA5q4RJLCMj/iSz31s8Cm8P
|
||||
+oWX3ZFxWzG5ZiWHWhpmgSYIPbfEwniwskLJXfZSEr+kcLoVnZsbWOxwLCmdanZl
|
||||
wWRNyFW4waRJaCf+jsPPoR5LEh5d0ustpP/2EtbP52OLY865bfr18SUhPllrX0ia
|
||||
L3zSCMC/S7H45xzOdSneYrYNA1W199hY/4lBafh7t8a07C2CFMqN/jaUJOFHQIzS
|
||||
VIsVHvpVQlpslxcPGOOZsh5tJwEIKuepHm6nqsZbKMuO6Qzi5hXfwsI3cjD+NZY8
|
||||
Hg2H0IDXfXKWD/mcxeAjgc3lzzchGZ56EOMN75Fzl/HLCBu6pYuTbOmwG9whnaYv
|
||||
VbNhDSzC+x39o4O2nMsfixOe4tx2eK04nzWKIEfYeIJZc+9xfJW16hbmErFx1VWR
|
||||
x2Pi55hbOCEeOVxnhc50rqFN+tXiLvVzZaLN7H/S7Bqk2qCA/b6HjxY/cZ+KyQSo
|
||||
gMc2Mz5/dwROBEuFryjpFzTOFRfnu8krZAaHim1TlDCNFI4+NO5xu1AkFpepYphQ
|
||||
+7qWjLPqXqHH54C79Qs5ey+q+iEh3ulK3u5yeyvL9+rjaPJOQxd3yTfcstHPpeoG
|
||||
ePCEhrpLvkfukrMhfgg8R4gtkST2LWQNrvSCnFa5Ag0EXiTN4wEQAMGv94CKgsE/
|
||||
H6V0YPxNFTfUFgZ+Rbse+DpGBUHSc2Bnp79ks9zq5zN6rRD8oztA2nyZAB39JJGq
|
||||
Y55yRRaQx/JqB1+WzSQywIdaAEt/D+PVfukfzjkNQ9/nELfBtx4W2xG5xiowzUqf
|
||||
XbWJvEmfOhKFYZrVayhVbc0DwZc1lBrO+1FX/z7NqyjgTkUusKIpjTjxl6afa79x
|
||||
8B8O8vcpwZO5I6LIVf75W5YcY0vJ0tXqYqF5/JOkWtRTGCcZQvovXsPg/LQtRz39
|
||||
61DuA0J02bCv/dUOPfwtk/PY4UzvIWmYH+xvfuJ4txX71VZVDVeSIMp7Iv9LVQL/
|
||||
pefdyIf4RTya30qHTLLJmXhQNTS7fE2EFoOfi9xlP8CE+vEnf5gxUsLQ98aoIaWu
|
||||
+DpZelPTUpEJ45RNFxizmhv+X/d1WHyFS5bU53guxDSrFOKNKilCEqO5ukxYhMSY
|
||||
va0REZQh4iwuPFO3R4D1NM3HSJa2YmgVp8tHNJBTQImYTCd+NfY/wLlGFfgrjOLh
|
||||
XkIO2mHzANiCmoPcc4yoXGpsAr8N8yVe5sOKsbUvfMpisJv500BJA33ONQU60wWk
|
||||
S1g/RVn/dzJffSC+WOgxLkLAGaA0clrmJVmD/mlYhv8P/Ptwh0zAnkZsWk+NI15T
|
||||
EW2efOEj5QBg5PdQFyxJuOQXs7B+vRF9ABEBAAGJAjwEGAEIACYWIQRt7eqyWHrX
|
||||
DMY0QXqG5/UcBPuqsAUCXiTN4wIbDAUJFo5qAAAKCRCG5/UcBPuqsAvVD/9v/91W
|
||||
QyRLH+0EU/1vJM9pw/oNAPmLWB1sVy8n9Fk0z6sPtZbDTpP9+t0K+ZHiGOoa0B2I
|
||||
cGhd/VWTpZo6IfaqEjPkXyC9iVRyazlJIdkzwl9QOEqqPhO75IxwXN7LuBV6Vx12
|
||||
3iDPh651EpN2NdbxmtMVymsWBjgfjKmTK8ye3r513F4I7Rp1EZOSNvmPutr3QaqT
|
||||
3lAqX9S6bMsRhQA0kDL7wtk8V++tIjko70/y/I/nrY3IQD61CTLkjOSw1Uj5FUGR
|
||||
Y61Yl+ANai4R9R/AkTuRinzpolFyPpOM9bGJ5xzG+O29eWH7fq68fk/c4t/uQu2g
|
||||
rULrNGBVTCkka0p6uNjjFUn+sJzkibNucHYtS1DEiV4SXZ+mANohLjaocVr7MZ6p
|
||||
nhAAKIYD8EWi8IwXXQXgUBNqr6qQB5yFnPcAhR5aCLFgh+RCMqVVXzZ7FO4oc0Fh
|
||||
IaVijpzACjLCZWTo++W9T0RPP/2KQpij2ZIOtB80hF8dfeWZeP0/OEaPkjUZEHla
|
||||
y5kwSAI6zkqnRtZRJpXPyvum8H6ACniMUHjRmtoFtNrlLZMM2i1Q4Qf8B4eqga6r
|
||||
+ehY3JUxfk3BtV0Y8DgKCBXSd3N2YvVn623Z546o/Bxio8Db+gkprgAW+5F3RRin
|
||||
iK7xxd0ZLzHgO0ASOgAKLbglZ2T+80NSMbWUbrkCDQReJM5qARAAuenyMXbUjin5
|
||||
2bntoz2I6b5dLMQlqRiggksMX1x/FFAO0cOaXxNtNpTzblN5Y2uZC6T+i5fnfXQ9
|
||||
yWkXIBLCXaaqziLx5wCg7rYsnS2/HYTVtd0ptYzqjKO++YAjHGcNnhUWdTJFRRMe
|
||||
CrYpRWtCH2YhdNPdfvsabLmF3LqI2GZp6x30q2oLxqLZFVA10d+AFCXEI5OcR9OW
|
||||
1N4okodA5HE9o3e+wYnRbJJUW4Lmxc8BFli7eqLNIhoI//tx276EKBhatBM6nAlZ
|
||||
AQJuEoQk08RnXPaMewC9HHjZQBjdM+ej3gl9PeYj6ptqcTqreN4AuRtcvvOgS80b
|
||||
TwixWYpDe58XUnT9JUF/C+n4AP+JdvSZHtK0FG9eNbLjcCSarsvOV98CkHthidJz
|
||||
mfUjP2txPZVxoB2CxXfUjYZ4Ib981CmdPON+HN/wtDpsWj5mOKeER9dWdXwr1d/a
|
||||
eRYZviTXEHMnEB/LnWzNx01RmyIUhw13fGr+6KBU3E4AogKwp/xFgoccvNR0LfSA
|
||||
HhNCRUPCh85bg9yTVroPmYeW6u0UWHflY6mVid08QDYFkHWGkGrEwsslpu9FpSBu
|
||||
J9lAuzVGlfWK2mFA8Lq8NmpuzJLUURYnXDfaQB2SxOjf9hjV7Z7GbNa95HBHZzQa
|
||||
vOExLxEUzspwR9d0isytL4Ytz9v3QpUAEQEAAYkEcgQYAQgAJhYhBG3t6rJYetcM
|
||||
xjRBeobn9RwE+6qwBQJeJM5qAhsCBQkFxJAAAkAJEIbn9RwE+6qwwXQgBBkBCAAd
|
||||
FiEEbws8jxbNPEKXXCt7wPFsNLeCUjAFAl4kzmoACgkQwPFsNLeCUjCbyRAAttzx
|
||||
zxca+YOlki8VQSwr+vktRBtwKRkzr3LCSJ7gY1SQeeJ9wQDXlBeNLxN1ev8+Xhce
|
||||
JNMv39gG3mfUUwnq+nlOA92mY1ZNWgdtN3ZUcJeQSTL+Xs2NC6tC/lMTBTm4kPlz
|
||||
c5f7GIPRURfoNFnytHuPvIEL+i1iAQfsLxCWHUWlJcsvbiFhv3mTKHhGVgpQlLDo
|
||||
kww1tJzBg+GfvBVNgUGoTBHLx6s1lbcODF6jrumYBQHYL93FwXBTKXeTcPdwypzt
|
||||
SHRioPh8aJ+EVfmt6n0LWMmNOAZVD0Ps5jKaq8ymrYABdS8wgrVZ6TRTDuAfmZwr
|
||||
4SY8gFqDLYEqXbl9iZZ3FFc8mWuBs7eqR5dxkVULgyjuYSdeppIhL2uuZmtsjAuJ
|
||||
8ewzuTGWTmrmZCDhD9AwGyqsrOOtqejgsE85yKsPiADUFHq7JNSIRfsb+HBU9j/a
|
||||
yi0fIwW4bzhL4xXrlsx1HtH2obCGyfeO4WXdZOCqYBfC21jzyJPY+KO7I79BBANd
|
||||
HamkAy1971SiPKvjFPtY93Fp8dKWywc6tq6oe8SMGvKk0SZ+OXSMeBomZWjbdjR7
|
||||
CUiyf/va5+v5QKLzApRGrhvBxgwAxgvK4zfLoKOC4WZQl8lYhgFYGZcFYNclcGvE
|
||||
wy9SsCpIIOT1z8NUl2K68qC/7A5eWU4MHfGkDaX8/g/+MdflD32Ldmwiv9lptDMm
|
||||
CtzE/uD/J902v7BuQeXX+pdHkgWeK8zTAJkIPEMB1kNFIoKVcGoic0msB+cN4RYy
|
||||
VZN56+kkhMRP+0m+xjPKyZ687VF88j8mEo/ybFxhwh58LydKNBqkdNeY9UbTT5bv
|
||||
WUlHWO5qClsCbRIPwjvsTgZk9qjAWrWbIFivXpPcC8z7NDCFVD5JTBxnkBbcXyvP
|
||||
W68+BvknNJ2Ilnww2aNNp5zmzGeDLSV28MTtUJXb3w6cXfeOazqcydgHqPO1gyTq
|
||||
oarCkgPEQlyLey/3qDmsmUskHQ8c81bQKPyGKH4dLiz0qdayRB/GICEIN7O+uk7/
|
||||
GdSem/sTXa/k9IpOm2eimdKG0y9C0k2kQb/IAr4cYMpbtN/gsyOr/UhqkzjawRCx
|
||||
wgHjKnXTNYtKcxuCpBpaPLQPFOE6IjG6Acpkh+LEdIYrY4hZmlaQqE3rXcye94rr
|
||||
F8ga+2XPNAwVH9zngrk1gZrTZmPkNkTG94kTfFwuAEmFWd6Dyf4IBoMacdpUyLTV
|
||||
99zduIWc4SOdzEawU0JdD50dkSlHDJsoNamhHe60MELbLOQjN43n3KqKHjIpgZcd
|
||||
2k2OFa/4uOXYoj3KmWxTKCV+BIcMuQfaaLJLHd1Uar59DExIatvw0aVWuj2M70tu
|
||||
J7VNNdmxD1RF3zsMFsPTQWY=
|
||||
=fqVo
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1,9 @@
|
|||
galmon for Debian
|
||||
|
||||
This directory contains files created by debmake and
|
||||
required by debuild to create Debian packages.
|
||||
|
||||
For further instruction, review the Guide for Debian Maintainers
|
||||
by Osamu Aoki: https:/www.debian.org/doc/manuals/debmake-doc/
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Sun, 19 Jan 2020 22:52:16 +0000
|
|
@ -0,0 +1,5 @@
|
|||
galmon (0.20191231-1) stable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Tue, 31 Dec 2019 00:00:00 +0000
|
|
@ -0,0 +1 @@
|
|||
11
|
|
@ -0,0 +1,24 @@
|
|||
Source: galmon
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Patrick Tudor <debian@ptudor.net>
|
||||
Build-Depends: debhelper (>=11~)
|
||||
Standards-Version: 4.3.0
|
||||
Homepage: https://github.com/ahupowerdns/galmon/
|
||||
|
||||
Package: galmon
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Conflicts: gpsd-clients
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: galmon GNSS Monitoring Project software
|
||||
88
|
||||
88
|
||||
88
|
||||
,adPPYb,d8 ,adPPYYba, 88 88,dPYba,,adPYba, ,adPPYba, 8b,dPPYba,
|
||||
a8" `Y88 "" `Y8 88 88P' "88" "8a a8" "8a 88P' `"8a
|
||||
8b 88 ,adPPPPP88 88 88 88 88 8b d8 88 88
|
||||
"8a, ,d88 88, ,88 88 88 88 88 "8a, ,a8" 88 88
|
||||
`"YbbdP"Y8 `"8bbdP"Y8 88 88 88 88 `"YbbdP"' 88 88
|
||||
aa, ,88
|
||||
"Y8bbdP"
|
|
@ -0,0 +1,45 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: galmon
|
||||
Source: https://github.com/ahupowerdns/galmon
|
||||
|
||||
Files: *
|
||||
Copyright: AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
License: GPL-3
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/GPL-3'.
|
||||
|
||||
Files: minicurl.cc
|
||||
minicurl.hh
|
||||
Copyright: 2018-2019 powerdns.com bv
|
||||
License: Expat
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# INSTRUCTIONS:
|
||||
# (Pre-condition: "dmesg | tail" reports your GPS device appears on /dev/ttyACM0.)
|
||||
# Copy this file from /etc/default/galmon to /etc/default/ubxtool-ttyACM0
|
||||
# Please choose your constellations, update the owner and remark, and set your station and destination (ipv6 supported)
|
||||
# After customization, start the daemon:
|
||||
# systemctl enable --now ubxtool@ttyACM0 && journalctl -fu ubxtool
|
||||
# If you want, enable automatic upgrades at boot and every three days following:
|
||||
# systemctl enable --now galmon-upgrade.timer
|
||||
# If you have several devices, enable multiple services with their unique device names.
|
||||
#
|
||||
# FOR HELP:
|
||||
# Review the Operator.md file: https://github.com/ahupowerdns/galmon/blob/master/Operator.md
|
||||
# Download an IRC client like Textual (Mac) or HexChat (Windows). Join #galileo on the OFTC servers.
|
||||
# Thank you for contributing, it is nice to see data from your location on the map.
|
||||
#
|
||||
#
|
||||
# DO NOT SET THE "PORT" OPTION HERE.
|
||||
# The device is set by an argument to service, see above.
|
||||
#
|
||||
# uBlox M8-series and Aliexpress Specials support ( (gps AND galileo) AND ( beidou OR glonass) ) with optional SBAS.
|
||||
#
|
||||
DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --glonass --sbas --station 65000 --destination ::1"
|
||||
#
|
||||
# uBlox ZED F9T and F9P modules support all four major constellations simultaneously but not SBAS.
|
||||
#
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --glonass --station 65000 --destination ::1"
|
|
@ -0,0 +1,6 @@
|
|||
Building.md
|
||||
influxdb.md
|
||||
Operator.md
|
||||
PACKAGE-DEBIAN.md
|
||||
README.md
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=Upgrade Galmon Software
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the configuration exists
|
||||
ConditionPathExists=/etc/default/galmon
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=no
|
||||
ExecStartPre=+apt-get update
|
||||
ExecStart=+apt-get install -y galmon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Update galmon
|
||||
Documentation=https://github.com/ahupowerdns/galmon
|
||||
|
||||
[Timer]
|
||||
OnBootSec=3min
|
||||
OnUnitActiveSec=3d
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=galmon navnexus (serve recorded data on port 29601)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVNEXUS in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navnexus
|
||||
ExecStart=/usr/bin/navnexus $DAEMON_OPTS_NAVNEXUS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=galmon navrecv (receive data on port 29603)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVRECV in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navrecv
|
||||
ExecStart=/usr/bin/navrecv $DAEMON_OPTS_NAVRECV
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# Here are daemon options used by the navrecv and navnexus services
|
||||
DAEMON_OPTS_NAVNEXUS="--storage /var/lib/galmon/storage --bind ::1"
|
||||
DAEMON_OPTS_NAVRECV="--bind ::1 --storage /var/lib/galmon/storage"
|
||||
#
|
||||
DAEMON_OPTS_NAVPARSE="--bind [::1]:10000 --html /usr/share/package/galmon/html --influxdb galileo"
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
# ptudor 20200120
|
||||
set -e
|
||||
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
setup_user() {
|
||||
|
||||
if getent group ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool group exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system group"
|
||||
addgroup --system ubxtool
|
||||
fi
|
||||
|
||||
if getent passwd ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool user exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system user"
|
||||
adduser --system ubxtool && adduser ubxtool ubxtool
|
||||
echo "galmon: adding ubxtool user to dialout group"
|
||||
adduser ubxtool dialout
|
||||
fi
|
||||
}
|
||||
|
||||
restart_ubxtool_daemon() {
|
||||
# I feel like this belongs in rules with dh_installsystemd but do not understand how to add the wildcard.
|
||||
if systemctl is-active 'ubxtool@*' > /dev/null ; then
|
||||
echo "galmon: restarting ubxtool."
|
||||
systemctl daemon-reload && systemctl restart 'ubxtool@*'
|
||||
else
|
||||
echo "galmon: ubxtool services are not currently enabled, not restarting."
|
||||
fi
|
||||
}
|
||||
|
||||
print_help_text() {
|
||||
echo "Galmon installation finished. If this is your first time, please:"
|
||||
echo " 1) Create a ubxtool configuration 2) Enable the service"
|
||||
echo " 3) Enable the timer for automatic upgrades if you want"
|
||||
echo "Replace ttyACM0 below with your device listed in /dev"
|
||||
echo "Example: cp /etc/default/galmon /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: vi /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: systemctl enable --now ubxtool@ttyACM0"
|
||||
echo "Example: systemctl enable --now galmon-upgrade.timer"
|
||||
}
|
||||
|
||||
setup_user
|
||||
print_help_text
|
||||
restart_ubxtool_daemon
|
||||
|
||||
#DEBHELPER#
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
shlibs:Depends=libc6 (>= 2.28), libcurl4 (>= 7.16.2), libgcc1 (>= 1:3.5), libh2o-evloop0.13, libncurses6 (>= 6), libprotobuf17, libssl1.1 (>= 1.1.0), libstdc++6 (>= 6), libtinfo6 (>= 6), zlib1g (>= 1:1.1.4)
|
||||
misc:Depends=
|
||||
misc:Pre-Depends=
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=galmon ubxtool on /dev/%I
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that both the device and configuration exist
|
||||
ConditionPathExists=/dev/%i
|
||||
ConditionPathExists=/etc/default/ubxtool-%i
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=-/etc/default/ubxtool-%i
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=ubxtool
|
||||
ExecStart=/usr/bin/ubxtool --port /dev/%i $DAEMON_OPTS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1 @@
|
|||
# You must remove unused comment lines for the released package.
|
|
@ -0,0 +1,17 @@
|
|||
# This file of environment variables is sourced via /etc/profile.d/debuild.sh
|
||||
#
|
||||
# It sets the email and name of the maintainer first.
|
||||
# Next we disable parallel builds for ram conservation, normally enabled.
|
||||
# Finally we add hardening to the build.
|
||||
|
||||
DEBEMAIL="debian@ptudor.net"
|
||||
DEBFULLNAME="Patrick Tudor"
|
||||
|
||||
# "nocheck" here because testrunner runs out of ram on most current arm hardware -pht
|
||||
# manual: dh_auto_test: If the DEB_BUILD_OPTIONS environment variable contains nocheck, no tests will be performed.
|
||||
DEB_BUILD_OPTIONS='parallel=1 nocheck'
|
||||
|
||||
# https://wiki.debian.org/Hardening
|
||||
DEB_BUILD_MAINT_OPTIONS='hardening=+all'
|
||||
|
||||
export DEBEMAIL DEBFULLNAME DEB_BUILD_OPTIONS DEB_BUILD_MAINT_OPTIONS
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/make -f
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install -- prefix=/usr
|
||||
|
||||
# override to install /etc/default/navstar alongside /etc/default/galmon
|
||||
override_dh_installinit:
|
||||
dh_installinit --name=navstar
|
||||
dh_installinit
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.service
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.timer
|
||||
dh_installsystemd --no-enable --no-start --name=navnexus navnexus.service
|
||||
dh_installsystemd --no-enable --no-start --name=navrecv navrecv.service
|
||||
dh_installsystemd --no-enable --no-start --name=ubxtool@ ubxtool@.service
|
|
@ -0,0 +1 @@
|
|||
3.0 (quilt)
|
|
@ -0,0 +1,2 @@
|
|||
#abort-on-upstream-changes
|
||||
#unapply-patches
|
|
@ -0,0 +1 @@
|
|||
version=3
|
31
ephemeris.hh
31
ephemeris.hh
|
@ -2,6 +2,37 @@
|
|||
#include "minivec.hh"
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <stdint.h>
|
||||
struct GPSLikeEphemeris
|
||||
{
|
||||
virtual double getMu() const = 0;
|
||||
virtual double getOmegaE() const = 0;
|
||||
virtual double getE() const = 0;
|
||||
virtual uint32_t getT0e() const = 0;
|
||||
|
||||
virtual double getI0() const = 0;
|
||||
|
||||
virtual double getOmegadot() const = 0;
|
||||
|
||||
virtual double getSqrtA() const = 0;
|
||||
virtual double getOmega0() const = 0;
|
||||
virtual double getOmega() const = 0;
|
||||
|
||||
virtual double getM0() const = 0;
|
||||
virtual double getIdot() const = 0;
|
||||
virtual double getCic() const = 0;
|
||||
virtual double getCis() const = 0;
|
||||
virtual double getCuc() const = 0;
|
||||
virtual double getCus() const = 0;
|
||||
virtual double getCrc() const = 0;
|
||||
virtual double getCrs() const = 0;
|
||||
virtual double getDeltan()const = 0;
|
||||
|
||||
virtual int getIOD() const = 0;
|
||||
// maybe af0, af1, af2?
|
||||
// maybe getUTCOffset? getAtomicOffset etc
|
||||
|
||||
};
|
||||
|
||||
// lat, lon, height (rad, rad, meters)
|
||||
std::tuple<double, double, double> ecefToWGS84(double x, double y, double z);
|
||||
|
|
18
galileo.hh
18
galileo.hh
|
@ -8,7 +8,7 @@
|
|||
|
||||
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn);
|
||||
|
||||
struct GalileoMessage
|
||||
struct GalileoMessage : GPSLikeEphemeris
|
||||
{
|
||||
uint8_t wtype;
|
||||
|
||||
|
@ -41,8 +41,8 @@ struct GalileoMessage
|
|||
}
|
||||
|
||||
uint8_t sparetime{0};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
uint16_t wn{0};
|
||||
uint32_t tow{0};
|
||||
int iodalmanac;
|
||||
int wnalmanac;
|
||||
int t0almanac;
|
||||
|
@ -99,6 +99,11 @@ struct GalileoMessage
|
|||
|
||||
uint16_t iodnav;
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return iodnav;
|
||||
}
|
||||
|
||||
struct Almanac
|
||||
{
|
||||
int svid{-1};
|
||||
|
@ -131,7 +136,10 @@ struct GalileoMessage
|
|||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
} alma1, alma2, alma3;
|
||||
|
||||
|
||||
|
@ -211,7 +219,7 @@ struct GalileoMessage
|
|||
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
// pair of nanosecond, nanosecond/s
|
||||
std::pair<double, double> getGPSOffset(int tow, int wn) const
|
||||
{
|
||||
int dw = (int)(uint8_t)wn - (int)(uint8_t) wn0g;
|
||||
|
|
42
galmonmon.cc
42
galmonmon.cc
|
@ -166,7 +166,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)
|
||||
{
|
||||
|
@ -182,12 +182,14 @@ static std::string string_replace(const std::string& str, const std::string& mat
|
|||
}
|
||||
return newstr;
|
||||
}
|
||||
|
||||
#endif
|
||||
void sendTweet(const string& tweet)
|
||||
{
|
||||
string etweet = tweet;
|
||||
//system((string("twurl -X POST /1.1/statuses/update.json -d \"media_ids=1215649475231997953&status=")+etweet+"\" >> twitter.log").c_str());
|
||||
system((string("twurl -X POST /1.1/statuses/update.json -d \"status=")+etweet+"\" >> twitter.log").c_str());
|
||||
if(system((string("twurl -X POST /1.1/statuses/update.json -d \"status=")+etweet+"\" >> twitter.log").c_str()) < 0) {
|
||||
cout<<"Problem tweeting!"<<endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -247,7 +249,27 @@ int main(int argc, char **argv)
|
|||
cout<<"Galmon behind by " << (time(0) - (long) iter.value()) <<" seconds"<<endl;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
res = mc.getURL(url+"sbas.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
std::optional<string> sbasHealthChange;
|
||||
for(auto iter = j.begin(); iter != j.end(); ++iter) {
|
||||
if(iter.value().count("health")) {
|
||||
string name = sbasName(atoi(iter.key().c_str()));
|
||||
sbasHealthChange = g_sk.reportState(name, "sbas-health", (string)iter.value()["health"]);
|
||||
// cout<<"Setting state for "<< name <<" to "<< (string)iter.value()["health"] << endl;
|
||||
|
||||
|
||||
if(sbasHealthChange) {
|
||||
ostringstream out;
|
||||
out<<"✈️ augmentation system "<<name<<" health changed: "<<*sbasHealthChange;
|
||||
cout<<out.str()<<endl;
|
||||
if(doTweet)
|
||||
sendTweet(out.str());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = mc.getURL(url+"svs.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
|
@ -289,13 +311,13 @@ int main(int argc, char **argv)
|
|||
auto healthchange = g_sk.reportState(fullName, "health", sv["healthissue"]!=0);
|
||||
std::optional<string> tooOldChange;
|
||||
if(gnssid == 2)
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 180, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 105, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
else
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 140, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
|
||||
auto seenChange = g_sk.reportState(fullName, "silent", notseen);
|
||||
|
||||
auto sisaChange = g_sk.reportState(fullName, "sisa", (string)sv["sisa"]);
|
||||
auto sisaChange = g_sk.reportState(fullName, "sisa", (double) sv["sisa-m"], (string)sv["sisa"]);
|
||||
|
||||
double ephdisco = sv.count("latest-disco") ? (double)sv["latest-disco"] : -1.0;
|
||||
auto ephdiscochange = g_sk.reportState(fullName, "eph-disco", ephdisco);
|
||||
|
@ -357,9 +379,13 @@ int main(int argc, char **argv)
|
|||
|
||||
if(sisaChange) {
|
||||
ostringstream tmp;
|
||||
tmp<< " SISA/URA reported ranging accuracy changed, new: "<<*sisaChange<<", old: " << *g_sk.getPrevState(fullName, "sisa");
|
||||
if(tmp.str().find("200 cm") == string::npos || tmp.str().find("282 cm") == string::npos)
|
||||
auto state = g_sk.getFullState(fullName, "sisa");
|
||||
auto prevState = g_sk.getPrevFullState(fullName, "sisa");
|
||||
tmp<< " SISA/URA reported ranging accuracy changed, new: "<<state->text<<", old: " << prevState->text;
|
||||
if(get<double>(state->state) > 3 || get<double>(prevState->state) > 3)
|
||||
out << tmp.str();
|
||||
else
|
||||
cout<<"Not reporting: "<<tmp.str()<<endl;
|
||||
}
|
||||
|
||||
string tweet;
|
||||
|
|
23
glonass.cc
23
glonass.cc
|
@ -1,6 +1,10 @@
|
|||
#include "glonass.hh"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
static const double ae = 6378.136; // km // IERS: 6378136.6
|
||||
static const double mu = 398.6004418E3; // km3/s2 // IERS: 3.986004418
|
||||
|
@ -98,8 +102,25 @@ static void rk4step (const double A[3], double y[6], double h)
|
|||
y[j] = y[j] + h * (k1[j] + 2*(k2[j] + k3[j]) + k4[j]) / 6;
|
||||
}
|
||||
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
static double passedMsec(const Clock::time_point& then, const Clock::time_point& now)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(now - then).count()/1000.0;
|
||||
}
|
||||
|
||||
|
||||
static double passedMsec(const Clock::time_point& then)
|
||||
{
|
||||
return passedMsec(then, Clock::now());
|
||||
}
|
||||
|
||||
|
||||
double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
|
||||
{
|
||||
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)};
|
||||
double A[3] = {ldexp(eph.ddx, -30), ldexp(eph.ddy, -30), ldexp(eph.ddz, -30)};
|
||||
|
@ -116,5 +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;
|
||||
// cout<<"Took: "<<(total+=passedMsec(start))<<" ms" <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
24
glonass.hh
24
glonass.hh
|
@ -53,6 +53,9 @@ struct GlonassMessage
|
|||
double getdY() { return ldexp(dy*1000.0, -20); }
|
||||
double getdZ() { return ldexp(dz*1000.0, -20); }
|
||||
|
||||
// this is there to make doDoppler work, which sadly wants to do
|
||||
// arithmetic to get the age of an ephemeris
|
||||
double getT0e() const { return 0; }
|
||||
|
||||
double getRadius() { return sqrt(getX()*getX() + getY()*getY() + getZ()*getZ()); }
|
||||
|
||||
|
@ -127,7 +130,26 @@ struct GlonassMessage
|
|||
P4 = getbitu(&gstr[0], 85-34, 1);
|
||||
deltaTaun = getbitsglonass(&gstr[0], 85 - 58, 4);
|
||||
}
|
||||
// nanosecond, nanosecond/s pair
|
||||
std::pair<double, double> getUTCOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
|
||||
ret.first = 1000000000.0*ldexp(tauc, -31); // this is Glonass-M
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<double, double> getGPSOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
ret.first = 1000000000.0*ldexp(taugps, -30);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
uint32_t getGloTime() const;
|
||||
|
||||
uint8_t n4{0}; // counting from 1996 ('n4=1'), this is the 4-year plan index we are currently in
|
||||
|
@ -138,7 +160,7 @@ struct GlonassMessage
|
|||
{
|
||||
n4=getbitu(&gstr[0], 85-36, 5);
|
||||
taugps = getbitsglonass(&gstr[0], 85-31, 22);
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32);
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32); // check the NEW ICD
|
||||
l_n = getbitu(&gstr[0], 85 - 9, 1);
|
||||
}
|
||||
|
||||
|
|
119
gps.cc
119
gps.cc
|
@ -13,3 +13,122 @@ std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t
|
|||
|
||||
}
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
int GPSState::parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< wn <<", URA: "<< (int)ura<<", health: "<<
|
||||
// (int)gpshealth <<endl;
|
||||
|
||||
af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
t0c = getbitu(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<t0c*16<<", af2: "<< (int)af2 <<", af1: "<< af1 <<", af0: "<<
|
||||
// af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< t0e << " = "<< 16* t0e <<"s"<<endl;
|
||||
|
||||
e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
}
|
||||
else if(frame == 3) {
|
||||
gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
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);
|
||||
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;
|
||||
return frame; // otherwise pageptr gets overwritten below
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= gpsalma.sv;
|
||||
|
||||
|
||||
gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
191
gps.hh
191
gps.hh
|
@ -5,9 +5,12 @@
|
|||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include "ephemeris.hh"
|
||||
|
||||
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload);
|
||||
|
||||
struct GPSAlmanac
|
||||
|
||||
struct GPSAlmanac : GPSLikeEphemeris
|
||||
{
|
||||
int dataid{-1};
|
||||
int sv;
|
||||
|
@ -29,7 +32,7 @@ struct GPSAlmanac
|
|||
{
|
||||
return ldexp(e, -21);
|
||||
}
|
||||
double getT0e() const
|
||||
uint32_t getT0e() const
|
||||
{
|
||||
return ldexp(t0a, 12);
|
||||
}
|
||||
|
@ -71,30 +74,23 @@ struct GPSAlmanac
|
|||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
|
||||
int getIOD() const { return 0; } // XXX ioda?
|
||||
};
|
||||
|
||||
|
||||
struct GPSState
|
||||
struct GPSState : GPSLikeEphemeris
|
||||
{
|
||||
struct SVIOD
|
||||
{
|
||||
std::bitset<32> words;
|
||||
int gnssid;
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
uint32_t wn{0}, tow{0};
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
|
@ -120,17 +116,12 @@ struct GPSState
|
|||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
|
||||
|
||||
};
|
||||
|
||||
GPSAlmanac gpsalma;
|
||||
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
|
@ -142,27 +133,15 @@ struct GPSState
|
|||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
// 1 2^-31 2^-43 2^-55 16 second
|
||||
int ura, af0, af1, af2, t0c; // GPS parameters that should not be here XXX
|
||||
int ura;
|
||||
|
||||
int gpsiod{-1};
|
||||
|
||||
std::map<int, SVIOD> iods;
|
||||
SVIOD& getEph(int i) { return iods[i]; } // XXXX gps adaptor
|
||||
void checkCompleteAndClean(int iod)
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
if(iods[iod].words[2] && iods[iod].words[3]) {
|
||||
if(iods.size() > 1) {
|
||||
auto tmp = iods[iod];
|
||||
iods.clear();
|
||||
iods[iod] = tmp;
|
||||
}
|
||||
}
|
||||
return gpsiod;
|
||||
}
|
||||
bool isComplete(int iod)
|
||||
{
|
||||
return iods[iod].words[2] && iods[iod].words[3];
|
||||
}
|
||||
|
||||
int parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr=0);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -203,129 +182,3 @@ std::pair<double, double> getGPSUTCOffset(int tow, int wn, const T& eph)
|
|||
|
||||
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
template<typename T>
|
||||
int parseGPSMessage(std::basic_string_view<uint8_t> cond, T& out, uint8_t* pageptr=0)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
out.tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<out.tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
out.wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
out.ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
out.gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< out.wn <<", URA: "<< (int)out.ura<<", health: "<<
|
||||
// (int)out.gpshealth <<endl;
|
||||
|
||||
out.af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
out.af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
out.af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
out.t0c = getbitu(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<out.t0c*16<<", af2: "<< (int)out.af2 <<", af1: "<< out.af1 <<", af0: "<<
|
||||
// out.af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
out.gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[2]=1;
|
||||
eph.t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< eph.t0e << " = "<< 16* eph.t0e <<"s"<<endl;
|
||||
|
||||
eph.e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(eph.e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
eph.sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(eph.sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
eph.crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
eph.deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
eph.m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
eph.cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
eph.cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 3) {
|
||||
out.gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[3]=1;
|
||||
eph.cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
eph.omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
eph.cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
eph.i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
eph.crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
eph.omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
eph.omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
eph.idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
out.a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
out.a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
out.t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
|
||||
out.wn0t = getbitu(&cond[0], 7*24 + 16, 8);
|
||||
out.dtLS = getbits(&cond[0], 8*24, 8);
|
||||
out.dtLSF = getbits(&cond[0], 9*24, 8);
|
||||
|
||||
// cerr<<": a0: "<<out.a0<<", a1: "<<out.a1<<", t0t: "<< out.t0t * (1<<12) <<", wn0t: "<< out.wn0t<<", rough offset: "<<ldexp(out.a0, -30)<<endl;
|
||||
// cerr<<"deltaTLS: "<< (int)out.dtLS<<", post "<< (int)out.dtLSF<<endl;
|
||||
return frame; // otherwise pageptr gets overwritten below
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
out.gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
out.gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= out.gpsalma.sv;
|
||||
|
||||
|
||||
out.gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
out.gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
out.gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
out.gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
out.gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
out.gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
out.gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
out.gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
out.gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
out.gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
out.gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
|
@ -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", "hqsources", "db", "delta_hz_corr","prres", "elev", "last-seen-s"];
|
||||
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "delta-utc", "sources", "hqsources", "db", "rtcm-eph-delta-cm","prres", "elev", "last-seen-s"];
|
||||
|
||||
// append the header row
|
||||
thead.append("tr")
|
||||
|
@ -25,6 +25,8 @@ function maketable(str, arr)
|
|||
.html(function(column) {
|
||||
if(column == "delta_hz_corr")
|
||||
return "ΔHz";
|
||||
if(column == "rtcm-eph-delta-cm")
|
||||
return "Δrtcm";
|
||||
if(column == "delta-gps")
|
||||
return "ΔGPS ns";
|
||||
if(column == "delta-utc")
|
||||
|
@ -60,6 +62,12 @@ function maketable(str, arr)
|
|||
// ret.value="";
|
||||
ret.value += " <a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
|
||||
}
|
||||
else if(column == "rtcm-eph-delta-cm") {
|
||||
if(row[column] != null)
|
||||
ret.value = row[column].toFixed(1)+" cm";
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
else if(column == "aodc/e") {
|
||||
if(row["aodc"] != null && row["aode"] != null)
|
||||
ret.value = row["aodc"]+"/"+row["aode"];
|
||||
|
@ -304,9 +312,24 @@ function update()
|
|||
|
||||
|
||||
d3.json("global.json", function(d) {
|
||||
d3.select('#facts').html("Galileo-UTC offset: <b>"+d["utc-offset-ns"].toFixed(2)+"</b> ns, Galileo-GPS offset: <b>"+d["gps-offset-ns"].toFixed(2)+"</b> ns, GPS UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+"</b>. "+d["leap-seconds"]+"</b> leap seconds");
|
||||
var str="Galileo-UTC offset: <b>"+d["gst-utc-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", Galileo-GPS offset: <b>"+d["gst-gps-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", GPS-UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", BeiDou-UTC offset: <b>"+d["beidou-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-UTC offset: <b>"+d["glonass-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-GPS offset: <b>"+d["glonass-gps-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", "+d["leap-seconds"]+"</b> leap seconds (GPS/Galileo)";
|
||||
|
||||
d3.select('#facts').html(str);
|
||||
lastseen = moment(1000*d["last-seen"]);
|
||||
d3.select("#freshness").html(lastseen.fromNow());
|
||||
|
||||
|
||||
str = d["total-live-receivers"]+" receivers active, tracking ";
|
||||
str += d["total-live-svs"] + " satellites via " ;
|
||||
str += d["total-live-signals"] + " signals." ;
|
||||
d3.select("#allstats").text(str);
|
||||
|
||||
});
|
||||
|
||||
d3.json("svs.json", function(d) {
|
||||
|
|
|
@ -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://berthub.eu/articles/posts/galmon-project/">here</a>. Live observer map <a href="geo">here</a>, status (coverage, DOP) map <a href="geo/coverage.html">here</a>. <b>Experimental Grafana dashboard on <a href="https://public.galmon.eu/">public.galmon.eu</a> (user: guest, password: guest)</b>.<br/>
|
||||
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://berthub.eu/articles/posts/galmon-project/">here</a>. Live observer map <a href="geo">here</a>, status (coverage, DOP) map <a href="geo/coverage.html">here</a>. <b>Experimental Grafana dashboard on <a href="https://public.galmon.eu/">public.galmon.eu</a> (user: guest, password: guest)</b>. SBAS <a href="sbas.html">status</a>, <a href="sbstatus.html">per-satellite</a>. <span id="allstats"></span><br/>
|
||||
<div class="centered">
|
||||
<hr/>
|
||||
<input type="checkbox" id="GalE1" onclick="updateSats();"> <label for="GalE1">Galileo E1</label>
|
||||
|
|
|
@ -137,6 +137,7 @@ function makeTable(str, obj)
|
|||
Object.keys(obj).forEach(function(e) {
|
||||
if(e=="svs") {
|
||||
Object.keys(obj[e]).forEach(function(k) {
|
||||
if(obj[e][k].elev && obj[e][k].azi) {
|
||||
var obj2 ={id: k, elev: obj[e][k].elev.toFixed(1),
|
||||
sigid: obj[e][k].sigid,
|
||||
db: obj[e][k].db, azi: obj[e][k].azi.toFixed(1),
|
||||
|
@ -162,6 +163,7 @@ function makeTable(str, obj)
|
|||
else if(gnssid==6)
|
||||
color="yellow";
|
||||
gnss_position.push([obj[e][k].azi, obj[e][k].elev, k.split("@")[0] , obj[e][k].db/4,4, color]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -215,7 +217,7 @@ function makeTable(str, obj)
|
|||
|
||||
return ret;
|
||||
})}).
|
||||
enter().append("td").text(function(d) {
|
||||
enter().append("td").html(function(d) {
|
||||
return d.value;
|
||||
|
||||
}).attr("align", d=> d.align).style("background-color", d=> d.color);
|
||||
|
|
|
@ -53,7 +53,7 @@ void InfluxPusher::addValueObserver(int src, string_view name, const initializer
|
|||
}
|
||||
|
||||
|
||||
void InfluxPusher::addValue(const SatID& id, string_view name, const initializer_list<pair<const char*, double>>& values, double t, std::optional<int> src)
|
||||
void InfluxPusher::addValue(const SatID& id, string_view name, const initializer_list<pair<const char*, double>>& values, double t, std::optional<int> src, std::optional<string> tag)
|
||||
{
|
||||
if(d_mute)
|
||||
return;
|
||||
|
@ -69,7 +69,7 @@ void InfluxPusher::addValue(const SatID& id, string_view name, const initializer
|
|||
|
||||
string buffer = string(name) +",gnssid="+to_string(id.gnss)+",sv=" +to_string(id.sv)+",sigid="+to_string(id.sigid);
|
||||
if(src)
|
||||
buffer += ",src="+to_string(*src);
|
||||
buffer += ","+*tag+"="+to_string(*src);
|
||||
|
||||
buffer+= " ";
|
||||
bool lefirst=true;
|
||||
|
@ -114,8 +114,14 @@ void InfluxPusher::doSend(const set<std::string>& buffer)
|
|||
infl.open ("infl.txt", std::ofstream::out | std::ofstream::app);
|
||||
infl << newout;
|
||||
*/
|
||||
|
||||
mc.postURL("http://127.0.0.1:8086/write?db="+d_dbname, newout, mch);
|
||||
try {
|
||||
mc.postURL("http://127.0.0.1:8086/write?db="+d_dbname, newout, mch);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
if(strstr(e.what(), "retention"))
|
||||
return;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ struct InfluxPusher
|
|||
{
|
||||
explicit InfluxPusher(std::string_view dbname);
|
||||
void addValueObserver(int src, std::string_view name, const std::initializer_list<std::pair<const char*, double>>& values, double t, std::optional<SatID> satid=std::optional<SatID>());
|
||||
void addValue(const SatID& id, std::string_view name, const std::initializer_list<std::pair<const char*, double>>& values, double t, std::optional<int> src = std::optional<int>());
|
||||
void addValue(const SatID& id, std::string_view name, const std::initializer_list<std::pair<const char*, double>>& values, double t, std::optional<int> src = std::optional<int>(), std::optional<string> tag = std::optional<string>("src"));
|
||||
|
||||
void checkSend();
|
||||
void doSend(const std::set<std::string>& buffer);
|
||||
|
|
|
@ -151,7 +151,7 @@ std::string MiniCurl::postURL(const std::string& str, const std::string& postdat
|
|||
if(res != CURLE_OK || http_code >= 300 ) {
|
||||
cerr<<"Detailed error: "<<d_data<<endl;
|
||||
cerr<<postdata<<endl;
|
||||
throw std::runtime_error("Unable to post URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
|
||||
throw std::runtime_error("Unable to post URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res))+", detail: "+d_data);
|
||||
}
|
||||
|
||||
std::string ret=d_data;
|
||||
|
|
26
navcat.cc
26
navcat.cc
|
@ -26,11 +26,6 @@ using namespace std;
|
|||
|
||||
extern const char* g_gitHash;
|
||||
|
||||
void unixDie(const std::string& str)
|
||||
{
|
||||
throw std::runtime_error(str+string(": ")+string(strerror(errno)));
|
||||
}
|
||||
|
||||
time_t parseTime(std::string_view in)
|
||||
{
|
||||
time_t now=time(0);
|
||||
|
@ -81,27 +76,6 @@ vector<uint64_t> getSources(string_view dirname)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static size_t writen2(int fd, const void *buf, size_t count)
|
||||
{
|
||||
const char *ptr = (char*)buf;
|
||||
const char *eptr = ptr + count;
|
||||
|
||||
ssize_t res;
|
||||
while(ptr != eptr) {
|
||||
res = ::write(fd, ptr, eptr - ptr);
|
||||
if(res < 0) {
|
||||
throw runtime_error("failed in writen2: "+string(strerror(errno)));
|
||||
}
|
||||
else if (res == 0)
|
||||
throw EofException();
|
||||
|
||||
ptr += (size_t) res;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void sendProtobuf(string_view dir, time_t startTime, time_t stopTime=0)
|
||||
{
|
||||
|
|
227
navdump.cc
227
navdump.cc
|
@ -26,10 +26,11 @@
|
|||
#include "sp3.hh"
|
||||
#include "ubx.hh"
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sbas.hh"
|
||||
#include "version.hh"
|
||||
#include "gpscnav.hh"
|
||||
#include "rinex.hh"
|
||||
#include "rtcm.hh"
|
||||
|
||||
static char program[]="navdump";
|
||||
|
||||
|
@ -37,7 +38,7 @@ using namespace std;
|
|||
|
||||
extern const char* g_gitHash;
|
||||
|
||||
Point g_ourpos;
|
||||
map<int, Point> g_srcpos;
|
||||
|
||||
|
||||
vector<SP3Entry> g_sp3s;
|
||||
|
@ -126,19 +127,27 @@ struct SVFilter
|
|||
pos = str.find(',', pos+1);
|
||||
if(pos != string::npos)
|
||||
satid.sigid = atoi(&str[pos+1]);
|
||||
// cout<<"Add to filter "<<satid.gnss<<", "<< satid.sv <<", "<<satid.sigid;
|
||||
d_filter.insert(satid);
|
||||
}
|
||||
bool check(int gnss, int sv, int sigid=-1)
|
||||
{
|
||||
// cout<<"Check filter "<<gnss<<", "<< sv <<", "<<sigid<<endl;
|
||||
if(d_filter.empty())
|
||||
return true;
|
||||
if(d_filter.count({gnss,0,-1})) // gnss match
|
||||
if(d_filter.count({gnss,0,-1})) { // gnss match
|
||||
// cout<<"gnss match"<<endl;
|
||||
return true;
|
||||
if(d_filter.count({gnss,sv,-1})) // gnss, sv match
|
||||
}
|
||||
if(d_filter.count({gnss,sv,-1})) { // gnss, sv match
|
||||
// cout<<"gnss, sv match"<<endl;
|
||||
return true;
|
||||
if(d_filter.count({gnss,sv,sigid})) // gnss, sv match, sigid
|
||||
}
|
||||
if(d_filter.count({gnss,sv,sigid})) { // gnss, sv match, sigid
|
||||
// cout<<"gnss, sv, sigid, match"<<endl;
|
||||
return true;
|
||||
|
||||
}
|
||||
// cout<<"Returning false"<<endl;
|
||||
return false;
|
||||
}
|
||||
set<SatID> d_filter;
|
||||
|
@ -179,7 +188,7 @@ void emitFixState(int src, double iTow, FixStat& fs, int n)
|
|||
|
||||
Point sat;
|
||||
getCoordinates(iTow, s.second.ephemeris, &sat);
|
||||
if(getElevationDeg(sat, g_ourpos) < 20)
|
||||
if(getElevationDeg(sat, g_srcpos[src]) < 20)
|
||||
continue;
|
||||
/*
|
||||
Point sat;
|
||||
|
@ -187,10 +196,10 @@ void emitFixState(int src, double iTow, FixStat& fs, int n)
|
|||
(void)trend;
|
||||
|
||||
getCoordinates(iTow + toffset/1000000000.0 + dt, s.second.ephemeris, &sat);
|
||||
double range = Vector(g_ourpos, sat).length();
|
||||
double range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
|
||||
getCoordinates(iTow + range/299792458.0 + toffset/1000000000.0 + dt, s.second.ephemeris, &sat);
|
||||
range = Vector(g_ourpos, sat).length();
|
||||
range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
*/
|
||||
double range = s.second.ephrange;
|
||||
if(s.second.bestrange1 != -1) {
|
||||
|
@ -214,13 +223,14 @@ void emitFixState(int src, double iTow, FixStat& fs, int n)
|
|||
for(const auto& s : fs.sats) {
|
||||
Point sat;
|
||||
double E=getCoordinates(iTow, s.second.ephemeris, &sat);
|
||||
cout<<""<<s.first.first<<","<<s.first.second<<": "<<s.second.bestrange1-offset<<" "<<s.second.bestrange5-offset<<" " << s.second.ephrange<<", delta1 " << (s.second.bestrange1-offset-s.second.ephrange)<<", delta5 "<< (s.second.bestrange5-offset-s.second.ephrange)<<" dd "<< s.second.bestrange1 - s.second.bestrange5 <<" t0e " << s.second.ephemeris.getT0e()<< " elev " << getElevationDeg(sat, g_ourpos) << " E " << E<< " clock " << s.second.ephemeris.getAtomicOffset(iTow).first/1000000<<"ms doppler1 "<<s.second.doppler1 << " doppler5 " <<s.second.doppler5<<" radvel " <<s.second.radvel<< " frac " << (s.second.bestrange1-offset-s.second.ephrange)/s.second.radvel;
|
||||
cout<<""<<s.first.first<<","<<s.first.second<<": "<<s.second.bestrange1-offset<<" "<<s.second.bestrange5-offset<<" " << s.second.ephrange<<", delta1 " << (s.second.bestrange1-offset-s.second.ephrange)<<", delta5 "<< (s.second.bestrange5-offset-s.second.ephrange)<<" dd "<< s.second.bestrange1 - s.second.bestrange5 <<" t0e " << s.second.ephemeris.getT0e()<< " elev " << getElevationDeg(sat, g_srcpos[src]) << " E " << E<< " clock " << s.second.ephemeris.getAtomicOffset(iTow).first/1000000<<"ms doppler1 "<<s.second.doppler1 << " doppler5 " <<s.second.doppler5<<" radvel " <<s.second.radvel<< " frac " << (s.second.bestrange1-offset-s.second.ephrange)/s.second.radvel;
|
||||
cout<< " fixed "<<((s.second.bestrange1 - offset-s.second.ephrange) + (s.second.ephrange/299792458.0) * s.second.radvel)<< " BGD-ns "<<ldexp(s.second.ephemeris.BGDE1E5b,-32)*1000000000<< endl;
|
||||
}
|
||||
|
||||
fs.sats.clear();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv)
|
||||
try
|
||||
{
|
||||
|
@ -235,13 +245,13 @@ try
|
|||
tles.parseFile("glo-ops.txt");
|
||||
tles.parseFile("gps-ops.txt");
|
||||
tles.parseFile("beidou.txt");
|
||||
/*
|
||||
readSP3s("all.sp3");
|
||||
|
||||
// readSP3s("all.sp3");
|
||||
if(!g_sp3s.empty()) {
|
||||
// sort(g_sp3s.begin(), g_sp3s.end(), [](const auto& a, const auto&b) { return a.t < b.t; });
|
||||
cout<<"Have "<<g_sp3s.size()<<" sp3 entries"<<endl; //, from "<<humanTime(g_sp3s.begin()->t) <<" to "<< humanTime(g_sp3s.rbegin()->t)<<endl;
|
||||
}
|
||||
*/
|
||||
|
||||
vector<string> svpairs;
|
||||
vector<int> stations;
|
||||
bool doReceptionData{false};
|
||||
|
@ -277,6 +287,9 @@ try
|
|||
ofstream iodstream("iodstream.csv");
|
||||
iodstream << "timestamp gnssid sv iodnav t0e age" << endl;
|
||||
|
||||
ofstream ephcsv("eph.csv");
|
||||
ephcsv<<"timestamp gnssid sv old_iod new_iod age insta_age x y z lat lon h"<<endl;
|
||||
|
||||
ofstream csv("delta.csv");
|
||||
csv <<"timestamp gnssid sv tow tle_distance alma_distance utc_dist x y z vx vy vz rad inclination e iod"<<endl;
|
||||
|
||||
|
@ -286,12 +299,7 @@ try
|
|||
|
||||
sp3csv<<"timestamp gnssid sv ephAge sp3X sp3Y sp3Z ephX ephY ephZ sp3Clock ephClock distance along clockDelta E speed"<<endl;
|
||||
|
||||
ofstream loccsv;
|
||||
loccsv.open ("jeff.csv", std::ofstream::out | std::ofstream::app);
|
||||
//loccsv<<"timestamp lat lon altitude accuracy\n";
|
||||
|
||||
// RINEXNavWriter rnw("test.rnx");
|
||||
|
||||
for(;;) {
|
||||
char bert[4];
|
||||
int res = readn2(0, bert, 4);
|
||||
|
@ -441,8 +449,8 @@ try
|
|||
auto newAtomic = gm.getAtomicOffset(gm.tow);
|
||||
cout<<" clock-jump "<<oldAtomic.first - newAtomic.first<<" ns ";
|
||||
// doOrbitDump(2, sv, gm.wn, oldEph[sv], gm, gm.tow - 3*3600, gm.tow + 3*3600);
|
||||
|
||||
|
||||
auto latlonh = ecefToWGS84(newPoint.x, newPoint.y, newPoint.z);
|
||||
ephcsv<<nmm.localutcseconds()<<" "<< 2 <<" " << sv <<" " <<oldEph[sv].iodnav << " "<<gm.iodnav <<" "<< (gm.getT0e() - oldEph[sv].getT0e()) <<" "<<ephAge(gm.tow, gm.getT0e())/3600 << " " <<newPoint.x<<" " << newPoint.y <<" " <<newPoint.z<<" " << 180*get<0>(latlonh)/M_PI<<" " << 180*get<1>(latlonh)/M_PI <<" " <<get<2>(latlonh) << "\n";
|
||||
oldEph[sv]=gm;
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +502,7 @@ try
|
|||
cout<<" best-tle-match "<<match.name <<" distance "<<match.distance /1000<<" km ";
|
||||
cout <<" tle-e "<<match.e <<" eph-e " <<gm.alma1.getE() <<" tle-ran "<<match.ran;
|
||||
cout<<" norad " <<match.norad <<" int-desig " << match.internat;
|
||||
cout<<" ele " << getElevationDeg(satpos, g_ourpos) << " azi " << getAzimuthDeg(satpos, g_ourpos);
|
||||
cout<<" ele " << getElevationDeg(satpos, g_srcpos[nmm.sourceid()]) << " azi " << getAzimuthDeg(satpos, g_srcpos[nmm.sourceid()]);
|
||||
}
|
||||
}
|
||||
else if(wtype == 8 && gm.tow - gmwtypes[{sv,7}].tow < 5 && gmwtypes[{sv,7}].alma1.svid && gm.iodalmanac == gmwtypes[{sv,7}].iodalmanac) {
|
||||
|
@ -526,13 +534,16 @@ try
|
|||
auto cond = getCondensedGPSMessage(std::basic_string<uint8_t>((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size()));
|
||||
struct GPSState gs;
|
||||
static map<int, GPSState> eph;
|
||||
|
||||
static map<int, GPSAlmanac> almas;
|
||||
uint8_t page;
|
||||
static int gpswn;
|
||||
int frame=parseGPSMessage(cond, gs, &page);
|
||||
int frame=gs.parseGPSMessage(cond, &page);
|
||||
cout<<"GPS "<<sv<<"@"<<nmm.gpsi().sigid()<<": "<<gs.tow<<" frame "<<frame<<" ";
|
||||
static map<int, GPSState> oldgs1s;
|
||||
static map<int, GPSState> oldgs2s;
|
||||
if(frame == 1) {
|
||||
static map<int, GPSState> oldgs1s;
|
||||
|
||||
gpswn = gs.wn;
|
||||
cout << "gpshealth = "<<(int)gs.gpshealth<<", wn "<<gs.wn << " t0c "<<gs.t0c << " af0 " << gs.af0 << " af1 " << gs.af1 <<" af2 " << gs.af2;
|
||||
if(auto iter = oldgs1s.find(sv); iter != oldgs1s.end() && iter->second.t0c != gs.t0c) {
|
||||
|
@ -543,21 +554,23 @@ try
|
|||
oldgs1s[sv] = gs;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
parseGPSMessage(cond, eph[sv], &page);
|
||||
cout << "t0e = "<<gs.iods.begin()->second.t0e << " " <<ephAge(gs.tow, gs.iods.begin()->second.t0e) << " iod "<<gs.gpsiod;
|
||||
eph[sv].parseGPSMessage(cond, &page);
|
||||
// gs in frame 2 contains t0e, so legit
|
||||
cout << "t0e = "<<gs.getT0e() << " " <<ephAge(gs.tow, gs.getT0e()) << " iod "<<gs.gpsiod;
|
||||
oldgs2s[sv] = gs;
|
||||
}
|
||||
else if(frame == 3) {
|
||||
parseGPSMessage(cond, eph[sv], &page);
|
||||
eph[sv].parseGPSMessage(cond, &page);
|
||||
cout <<"iod "<<gs.gpsiod;
|
||||
if(eph[sv].isComplete(gs.gpsiod)) {
|
||||
if(eph[sv].gpsiod == oldgs2s[sv].gpsiod) {
|
||||
Point sat;
|
||||
getCoordinates(gs.tow, eph[sv].iods[gs.gpsiod], &sat);
|
||||
getCoordinates(gs.tow, eph[sv], &sat);
|
||||
TLERepo::Match second;
|
||||
auto match = tles.getBestMatch(utcFromGPS(gpswn, gs.tow), sat.x, sat.y, sat.z, &second);
|
||||
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 t0e "<<gs.gpsalma.getT0e() << " t " <<nmm.localutcseconds();
|
||||
cout<<" ele " << getElevationDeg(sat, g_ourpos) << " azi " << getAzimuthDeg(sat, g_ourpos);
|
||||
cout<<" ele " << getElevationDeg(sat, g_srcpos[nmm.sourceid()]) << " azi " << getAzimuthDeg(sat, g_srcpos[nmm.sourceid()]);
|
||||
|
||||
if(almas.count(sv)) {
|
||||
Point almapoint;
|
||||
|
@ -565,9 +578,41 @@ try
|
|||
cout<<" alma-dist " << Vector(sat, almapoint).length();
|
||||
|
||||
Vector speed;
|
||||
getSpeed(gs.tow, eph[sv].iods[gs.gpsiod], &speed);
|
||||
getSpeed(gs.tow, eph[sv], &speed);
|
||||
Point core;
|
||||
csv << nmm.localutcseconds() << " 0 "<< sv <<" " << gs.tow << " " << match.distance <<" " << Vector(sat, almapoint).length() << " " << utcFromGPS(gpswn, gs.tow) - nmm.localutcseconds() << " " << sat.x <<" " << sat.y <<" " << sat.z <<" " <<speed.x <<" " <<speed.y<<" " <<speed.z<< " " << Vector(core, sat).length() << " " << eph[sv].iods[gs.gpsiod].getI0()<<" " << eph[sv].iods[gs.gpsiod].getE() << " " <<gs.gpsiod<<endl;
|
||||
csv << nmm.localutcseconds() << " 0 "<< sv <<" " << gs.tow << " " << match.distance <<" " << Vector(sat, almapoint).length() << " " << utcFromGPS(gpswn, gs.tow) - nmm.localutcseconds() << " " << sat.x <<" " << sat.y <<" " << sat.z <<" " <<speed.x <<" " <<speed.y<<" " <<speed.z<< " " << Vector(core, sat).length() << " " << eph[sv].getI0()<<" " << eph[sv].getE() << " " <<gs.gpsiod<<endl;
|
||||
}
|
||||
|
||||
int start = utcFromGPS(gpswn, (int)gs.tow);
|
||||
cout<<"sp3 start: "<<start<<" wn " << gpswn<<" tow " << gs.tow << endl;
|
||||
|
||||
SP3Entry e{0, sv, start};
|
||||
auto bestSP3 = lower_bound(g_sp3s.begin(), g_sp3s.end(), e, sp3Order);
|
||||
if(bestSP3 != g_sp3s.end() && bestSP3->gnss == e.gnss && bestSP3->sv == sv) {
|
||||
static set<pair<int,int>> haveSeen;
|
||||
if(!haveSeen.count({sv, bestSP3->t})) {
|
||||
haveSeen.insert({sv, bestSP3->t});
|
||||
Point newPoint;
|
||||
double E=getCoordinates(gs.tow + (bestSP3->t - start), eph[sv], &newPoint, false);
|
||||
Point sp3Point(bestSP3->x, bestSP3->y, bestSP3->z);
|
||||
Vector dist(newPoint, sp3Point);
|
||||
|
||||
Vector nspeed;
|
||||
getSpeed(gs.tow + (bestSP3->t - start), eph[sv], &nspeed);
|
||||
Vector speed = nspeed;
|
||||
nspeed.norm();
|
||||
double along = nspeed.inner(dist);
|
||||
|
||||
cout<<"\nsp3 "<<(bestSP3->t - start)<<" G"<<sv<<" "<<humanTime(bestSP3->t)<<" (" << newPoint.x/1000.0 <<", "<<newPoint.y/1000.0<<", "<<newPoint.z/1000.0<< ") (" <<
|
||||
(bestSP3->x/1000.0) <<", " << (bestSP3->y/1000.0) <<", " << (bestSP3->z/1000.0) << ") "<<bestSP3->clockBias << " " << getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first<< " " << dist.length()<< " ";
|
||||
cout << (bestSP3->clockBias - getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first);
|
||||
cout << " " << gs.af0 <<" " << gs.af1;
|
||||
cout << endl;
|
||||
|
||||
sp3csv <<std::fixed<< bestSP3->t << " 0 "<< sv <<" " << ephAge(gs.tow+(bestSP3->t - start), eph[sv].getT0e()) <<" "<<bestSP3->x<<" " << bestSP3->y<<" " <<bestSP3->z <<" " << newPoint.x<<" " <<newPoint.y <<" " <<newPoint.z << " " <<bestSP3->clockBias <<" ";
|
||||
sp3csv << getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first<<" " << dist.length() <<" " << along <<" ";
|
||||
sp3csv << (bestSP3->clockBias - getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first) << " " << E << " " << speed.length()<<endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -604,6 +649,23 @@ try
|
|||
|
||||
cout<<"\n";
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::RTCMMessageType) {
|
||||
etstamp();
|
||||
RTCMMessage rm;
|
||||
rm.parse(nmm.rm().contents());
|
||||
if(rm.type == 1057 || rm.type == 1240) {
|
||||
for(const auto& ed : rm.d_ephs) {
|
||||
cout<<makeSatPartialName(ed.id)<<": iode "<< ed.iod<<" ("<< ed.radial<<", "<<ed.along<<", "<<ed.cross<<") mm -> (";
|
||||
cout<< ed.dradial<<", "<<ed.dalong<<", "<<ed.dcross<< ") mm/s\n";
|
||||
}
|
||||
}
|
||||
else if(rm.type == 1058 || rm.type == 1241) {
|
||||
for(const auto& cd : rm.d_clocks) {
|
||||
cout<<makeSatPartialName(cd.id)<<": dclock0 "<< cd.dclock0 <<" dclock1 " << cd.dclock1 <<" dclock2 "<< cd.dclock2 << endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::GPSCnavType) {
|
||||
int sv = nmm.gpsc().gnsssv();
|
||||
int sigid = nmm.gpsc().sigid();
|
||||
|
@ -631,9 +693,14 @@ try
|
|||
continue;
|
||||
etstamp();
|
||||
|
||||
|
||||
|
||||
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size()));
|
||||
std::basic_string<uint8_t> cond;
|
||||
try {
|
||||
cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size()));
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
cout<<"Parsing error"<<endl;
|
||||
continue;
|
||||
}
|
||||
uint8_t pageno;
|
||||
static map<int, BeidouMessage> bms;
|
||||
auto& bm = bms[sv];
|
||||
|
@ -738,7 +805,7 @@ try
|
|||
else if(strno == 3)
|
||||
cout<<" l_n " << (int)gm.l_n << " z " <<gm.getZ()/1000.0;
|
||||
else if(strno == 4) {
|
||||
cout<<", taun "<<gm.taun <<" NT "<<gm.NT <<" FT " << (int) gm.FT <<" En " << (int)gm.En;
|
||||
cout<<", taun "<<gm.taun <<" NT "<<gm.NT <<" FT " << (int) gm.FT <<" En " << (int)gm.En <<" M " << (int)gm.M ;
|
||||
if(gm.x && gm.y && gm.z) {
|
||||
auto longlat = getLongLat(gm.getX(), gm.getY(), gm.getZ());
|
||||
cout<<" long "<< 180* longlat.first/M_PI <<" lat " << 180*longlat.second/M_PI<<" rad "<<gm.getRadius();
|
||||
|
@ -750,7 +817,7 @@ try
|
|||
}
|
||||
}
|
||||
else if(strno == 5)
|
||||
cout<<", n4 "<< (int)gm.n4 << " l_n " << gm.l_n;
|
||||
cout<<", n4 "<< (int)gm.n4 << " l_n " << gm.l_n <<" tauc "<< gm.tauc << " taugps "<<gm.taugps;
|
||||
else if(strno == 6 || strno ==8 || strno == 10 || strno ==12 ||strno ==14) {
|
||||
cout<<" nA "<< gm.nA <<" CnA " << gm.CnA <<" LambdaNaDeg "<< gm.getLambdaNaDeg() << " e " <<gm.getE() << " i0 "<< 180.0*gm.getI0()/M_PI;
|
||||
}
|
||||
|
@ -760,7 +827,8 @@ try
|
|||
cout<<endl;
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::ObserverPositionType) {
|
||||
g_ourpos = Point(nmm.op().x(), nmm.op().y(), nmm.op().z());
|
||||
g_srcpos[nmm.sourceid()] = Point(nmm.op().x(), nmm.op().y(), nmm.op().z());
|
||||
|
||||
if(!doObserverPosition)
|
||||
continue;
|
||||
etstamp();
|
||||
|
@ -771,8 +839,8 @@ try
|
|||
<<" lat "<< 180*std::get<0>(latlonh)/M_PI
|
||||
<<" elev "<< std::get<2>(latlonh) << " acc "<<nmm.op().acc()<<" m "<<endl;
|
||||
|
||||
loccsv<<std::fixed<<nmm.localutcseconds()+nmm.localutcnanoseconds()/1000000000.0<<" "<<180*std::get<1>(latlonh)/M_PI<<" "<<
|
||||
180*std::get<0>(latlonh)/M_PI<<" "<<std::get<2>(latlonh)<<" "<<nmm.op().acc()<<"\n";
|
||||
//loccsv<<std::fixed<<nmm.localutcseconds()+nmm.localutcnanoseconds()/1000000000.0<<" "<<180*std::get<1>(latlonh)/M_PI<<" "<<
|
||||
// 180*std::get<0>(latlonh)/M_PI<<" "<<std::get<2>(latlonh)<<" "<<nmm.op().acc()<<"\n";
|
||||
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::RFDataType) {
|
||||
|
@ -818,16 +886,16 @@ try
|
|||
static int n;
|
||||
double E=getCoordinates(nmm.rfd().rcvtow(), eph, &sat);
|
||||
|
||||
double range = Vector(g_ourpos, sat).length();
|
||||
double range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
|
||||
double origrange = range;
|
||||
E=getCoordinates(nmm.rfd().rcvtow() - range/299792458.0, eph, &sat);
|
||||
range = Vector(g_ourpos, sat).length();
|
||||
range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
cout << " d "<<origrange-range;
|
||||
origrange=range;
|
||||
/*
|
||||
E=getCoordinates(nmm.rfd().rcvtow() + range/299792458.0 + offset/1000000000.0 - 0.018, eph, &sat);
|
||||
range = Vector(g_ourpos, sat).length();
|
||||
range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
cout << " d "<< 10000.0*(origrange-range);
|
||||
origrange=range;
|
||||
*/
|
||||
|
@ -841,10 +909,10 @@ try
|
|||
rot.y = sat.x * sin(theta) + sat.y * cos(theta);
|
||||
rot.z = sat.z;
|
||||
double oldrange=range;
|
||||
range = Vector(g_ourpos, rot).length(); // second try
|
||||
range = Vector(g_srcpos[nmm.sourceid()], rot).length(); // second try
|
||||
cout<<" rot-shift "<<oldrange-range <<" abs-move "<<Vector(rot, sat).length();
|
||||
*/
|
||||
double rotcor = omegaE * (sat.x*g_ourpos.y - sat.y * g_ourpos.x) / 299792458.0;
|
||||
double rotcor = omegaE * (sat.x*g_srcpos[nmm.sourceid()].y - sat.y * g_srcpos[nmm.sourceid()].x) / 299792458.0;
|
||||
cout<<" rot-shift "<<rotcor;
|
||||
range += rotcor;
|
||||
|
||||
|
@ -874,7 +942,7 @@ try
|
|||
}
|
||||
auto& satstat=fixes[nmm.sourceid()].sats[{(int)nmm.rfd().gnssid(), (int)nmm.rfd().gnsssv()}];
|
||||
satstat.ephrange = range;
|
||||
auto dop = doDoppler(nmm.rfd().rcvtow(), g_ourpos, eph, 1575420000);
|
||||
auto dop = doDoppler(nmm.rfd().rcvtow(), g_srcpos[nmm.sourceid()], eph, 1575420000);
|
||||
satstat.radvel = dop.radvel;
|
||||
if(nmm.rfd().sigid()==1) {
|
||||
satstat.bestrange1 = bestrange;
|
||||
|
@ -910,6 +978,63 @@ try
|
|||
nmm.ujs().agccnt()<<" flags "<<nmm.ujs().flags()<<" jamind "<<
|
||||
nmm.ujs().jamind()<<endl;
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::SBASMessageType) {
|
||||
if(!svfilter.check(1, nmm.sbm().gnsssv(), 0))
|
||||
continue;
|
||||
|
||||
etstamp();
|
||||
basic_string<uint8_t> sbas((uint8_t*)nmm.sbm().contents().c_str(), nmm.sbm().contents().size());
|
||||
cout<<" PRN "<<nmm.sbm().gnsssv()<<" SBAS message type ";
|
||||
|
||||
// Preamble sequence:
|
||||
// 0x53, 0x9a, 0xc6
|
||||
// 83, 154, 198
|
||||
|
||||
// preamble(8), msgtype(6), payload212-95(18)
|
||||
// 5 * payload194-3(32)
|
||||
// payload2-1 parity(24) pad(6)
|
||||
|
||||
|
||||
// cout<< makeHexDump(string((char*)sbas.c_str(), sbas.size())) << endl;
|
||||
int type = getbitu(&sbas[0], 8, 6);
|
||||
cout <<type<<" ";
|
||||
static map<int, SBASState> sbstate;
|
||||
|
||||
if(type == 0) {
|
||||
sbstate[nmm.sbm().gnsssv()].parse0(sbas, nmm.localutcseconds());
|
||||
}
|
||||
else if(type == 1) {
|
||||
sbstate[nmm.sbm().gnsssv()].parse1(sbas, nmm.localutcseconds());
|
||||
}
|
||||
else if(type == 6) {
|
||||
auto integ = sbstate[nmm.sbm().gnsssv()].parse6(sbas, nmm.localutcseconds());
|
||||
cout<<"integrity updated: ";
|
||||
for(const auto& i : integ) {
|
||||
cout<<makeSatPartialName(i.id)<<" corr "<<i.correction <<" udrei "<< i.udrei <<" ";
|
||||
}
|
||||
}
|
||||
else if(type ==7) {
|
||||
sbstate[nmm.sbm().gnsssv()].parse7(sbas, nmm.localutcseconds());
|
||||
cout<<" latency " <<sbstate[nmm.sbm().gnsssv()].d_latency;
|
||||
}
|
||||
else if(type == 24) {
|
||||
auto ret=sbstate[nmm.sbm().gnsssv()].parse24(sbas, nmm.localutcseconds());
|
||||
cout<< " fast";
|
||||
for(const auto& i : ret.first)
|
||||
cout<< " "<<makeSatPartialName(i.id)<<" corr "<< i.correction <<" udrei "<<i.udrei;
|
||||
for(const auto& i : ret.second)
|
||||
cout<< " "<<makeSatPartialName(i.id)<<" dx "<< i.dx <<" dy "<<i.dy<<" dz "<<i.dz<<" dai " <<i.dai;
|
||||
|
||||
}
|
||||
else if(type == 25) {
|
||||
auto ret = sbstate[nmm.sbm().gnsssv()].parse25(sbas, nmm.localutcseconds());
|
||||
for(const auto& i : ret)
|
||||
cout<< " "<<makeSatPartialName(i.id)<<" dx "<< i.dx <<" dy "<<i.dy<<" dz "<<i.dz<<" dai " <<i.dai;
|
||||
|
||||
}
|
||||
|
||||
cout<<endl;
|
||||
}
|
||||
else if(nmm.type() == NavMonMessage::DebuggingType) {
|
||||
|
||||
auto res = parseTrkMeas(basic_string<uint8_t>((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size()));
|
||||
|
@ -932,13 +1057,13 @@ try
|
|||
const auto& eph = galEphemeris[a.sv];
|
||||
Point sat;
|
||||
getCoordinates(rtow, eph, &sat);
|
||||
elevA=getElevationDeg(sat, g_ourpos);
|
||||
elevA=getElevationDeg(sat, g_srcpos[nmm.sourceid()]);
|
||||
}
|
||||
if(galEphemeris.count(b.sv)) {
|
||||
const auto& eph = galEphemeris[b.sv];
|
||||
Point sat;
|
||||
getCoordinates(rtow, eph, &sat);
|
||||
elevB=getElevationDeg(sat, g_ourpos);
|
||||
elevB=getElevationDeg(sat, g_srcpos[nmm.sourceid()]);
|
||||
}
|
||||
return elevB < elevA;
|
||||
|
||||
|
@ -960,14 +1085,14 @@ try
|
|||
Point sat;
|
||||
|
||||
double E=getCoordinates(rtow - clockoffms/1000.0, eph, &sat);
|
||||
double range = Vector(g_ourpos, sat).length();
|
||||
double range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
getCoordinates(rtow - clockoffms/1000.0 - range/299792458.0, eph, &sat);
|
||||
range = Vector(g_ourpos, sat).length();
|
||||
range = Vector(g_srcpos[nmm.sourceid()], sat).length();
|
||||
|
||||
double trmsec = ldexp(maxt - sv.tr, -32) + clockoffms;
|
||||
|
||||
constexpr double omegaE = 2*M_PI /86164.091 ;
|
||||
double rotcor = omegaE * (sat.x*g_ourpos.y - sat.y * g_ourpos.x) / 299792458.0;
|
||||
double rotcor = omegaE * (sat.x*g_srcpos[nmm.sourceid()].y - sat.y * g_srcpos[nmm.sourceid()].x) / 299792458.0;
|
||||
range += rotcor;
|
||||
|
||||
double bgdcor = 299792458.0 *ldexp(eph.BGDE1E5b,-32);
|
||||
|
@ -996,7 +1121,7 @@ try
|
|||
|
||||
cout<<" rotcor "<< rotcor;
|
||||
cout<<" relcor "<<relcor;
|
||||
cout<<" elev " << getElevationDeg(sat, g_ourpos);
|
||||
cout<<" elev " << getElevationDeg(sat, g_srcpos[nmm.sourceid()]);
|
||||
cout<<" bgd-m " << bgdcor;
|
||||
cout<<" clockoff-ms " << clockoffms << endl;
|
||||
|
||||
|
|
35
navmon.cc
35
navmon.cc
|
@ -259,12 +259,14 @@ char getGNSSChar(int id)
|
|||
{
|
||||
if(id==0)
|
||||
return 'G';
|
||||
if(id==2)
|
||||
else if(id==2)
|
||||
return 'E';
|
||||
if(id==3)
|
||||
else if(id==3)
|
||||
return 'C';
|
||||
if(id==6)
|
||||
else if(id==6)
|
||||
return 'R';
|
||||
else if(id==255)
|
||||
return '?';
|
||||
else
|
||||
return '0'+id;
|
||||
}
|
||||
|
@ -324,8 +326,33 @@ std::string sbasName(int prn)
|
|||
sbas ="GAGAN";
|
||||
}
|
||||
else
|
||||
sbas ="SBAS";
|
||||
sbas ="SBAS?";
|
||||
|
||||
sbas+=" " + std::to_string(prn);
|
||||
return sbas;
|
||||
}
|
||||
|
||||
size_t writen2(int fd, const void *buf, size_t count)
|
||||
{
|
||||
const char *ptr = (char*)buf;
|
||||
const char *eptr = ptr + count;
|
||||
|
||||
ssize_t res;
|
||||
while(ptr != eptr) {
|
||||
res = ::write(fd, ptr, eptr - ptr);
|
||||
if(res < 0) {
|
||||
throw runtime_error("failed in writen2: "+string(strerror(errno)));
|
||||
}
|
||||
else if (res == 0)
|
||||
throw EofException();
|
||||
|
||||
ptr += (size_t) res;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void unixDie(const std::string& reason)
|
||||
{
|
||||
throw std::runtime_error(reason+": "+strerror(errno));
|
||||
}
|
||||
|
|
|
@ -77,3 +77,5 @@ double utcFromGST(int wn, double tow);
|
|||
double utcFromGPS(int wn, double tow);
|
||||
|
||||
std::string makeHexDump(const std::string& str);
|
||||
size_t writen2(int fd, const void *buf, size_t count);
|
||||
void unixDie(const std::string& reason);
|
||||
|
|
|
@ -16,6 +16,7 @@ message NavMonMessage {
|
|||
UbloxJammingStatsType = 12;
|
||||
SBASMessageType = 13;
|
||||
GPSCnavType = 14;
|
||||
RTCMMessageType = 15;
|
||||
}
|
||||
|
||||
required uint64 sourceID = 1;
|
||||
|
@ -171,6 +172,11 @@ message NavMonMessage {
|
|||
required uint32 sigid = 6;
|
||||
}
|
||||
|
||||
message RTCMMessage {
|
||||
required bytes contents =5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
optional GalileoInav gi=5;
|
||||
optional ReceptionData rd=6;
|
||||
|
@ -185,5 +191,6 @@ message NavMonMessage {
|
|||
optional ObserverDetails od = 15;
|
||||
optional UbloxJammingStats ujs = 16;
|
||||
optional SBASMessage sbm = 17;
|
||||
optional GPSCnav gpsc=18;
|
||||
optional GPSCnav gpsc = 18;
|
||||
optional RTCMMessage rm = 19;
|
||||
}
|
||||
|
|
1312
navparse.cc
1312
navparse.cc
File diff suppressed because it is too large
Load Diff
139
navparse.hh
139
navparse.hh
|
@ -6,6 +6,10 @@
|
|||
#include "glonass.hh"
|
||||
#include <map>
|
||||
#include "tle.hh"
|
||||
#include "sbas.hh"
|
||||
#include "ephemeris.hh"
|
||||
#include "rtcm.hh"
|
||||
|
||||
using namespace std; // XXX
|
||||
|
||||
struct SVPerRecv
|
||||
|
@ -18,128 +22,55 @@ struct SVPerRecv
|
|||
int qi{-1}; // quality indicator, -1 = unknown
|
||||
time_t t; // last seen
|
||||
};
|
||||
|
||||
struct SVIOD
|
||||
{
|
||||
std::bitset<32> words;
|
||||
int gnssid;
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 60 seconds
|
||||
uint16_t t0c; // clock epoch, stored UNSCALED, since it is not in the same place as GPS
|
||||
|
||||
// 2^-34 2^-46
|
||||
int32_t af0{0} , af1{0};
|
||||
// 2^-59
|
||||
int8_t af2{0};
|
||||
|
||||
uint8_t sisa;
|
||||
|
||||
uint32_t wn{0}, tow{0};
|
||||
bool complete() const
|
||||
{
|
||||
if(gnssid==2)
|
||||
return words[1] && words[2] && words[3] && words[4];
|
||||
else
|
||||
return words[2] && words[3];
|
||||
}
|
||||
void addGalileoWord(std::basic_string_view<uint8_t> page);
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
if(gnssid == 2) return 3.986004418 * pow(10.0, 14.0);
|
||||
if(gnssid == 0) return 3.986005 * pow(10.0, 14.0);
|
||||
throw std::runtime_error("getMu() called for unsupported gnssid "+to_string(gnssid));
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
uint32_t getT0e() const { return t0e; }
|
||||
uint32_t getT0c() const { return 60*t0c; }
|
||||
double getSqrtA() const { return ldexp(sqrtA, -19); }
|
||||
double getE() const { return ldexp(e, -33); }
|
||||
double getCuc() const { return ldexp(cuc, -29); } // radians
|
||||
double getCus() const { return ldexp(cus, -29); } // radians
|
||||
double getCrc() const { return ldexp(crc, -5); } // meters
|
||||
double getCrs() const { return ldexp(crs, -5); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
double getDeltan()const { return ldexp(deltan *M_PI, -43); } //radians/s
|
||||
double getI0() const { return ldexp(i0 * M_PI, -31); } // radians
|
||||
double getCic() const { return ldexp(cic, -29); } // radians
|
||||
double getCis() const { return ldexp(cis, -29); } // radians
|
||||
double getOmegadot() const { return ldexp(omegadot * M_PI, -43); } // radians/s
|
||||
double getOmega0() const { return ldexp(omega0 * M_PI, -31); } // radians
|
||||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/* Most of thes fields are raw, EXCEPT:
|
||||
t0t = seconds, raw fields are 3600 seconds (galileo), 4096 seconds (GPS)
|
||||
*/
|
||||
|
||||
class InfluxPusher;
|
||||
struct SVStat
|
||||
{
|
||||
uint8_t 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};
|
||||
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
//
|
||||
// 2^-30 2^-50 1 8-bit week
|
||||
int32_t a0{0}, a1{0}, t0t{0}, wn0t{0};
|
||||
int32_t a0g{0}, a1g{0}, t0g{0}, wn0g{0};
|
||||
int8_t dtLS{0}, dtLSF{0};
|
||||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
// 1 2^-31 2^-43 2^-55 16 second
|
||||
int ura, af0, af1, af2, t0c, gpsiod; // GPS parameters that should not be here XXX (or perhaps they should)
|
||||
int gnss;
|
||||
|
||||
GPSState gpsmsg; // continuously being updated
|
||||
GPSState gpsmsg2, gpsmsg3; // new ephemeris being assembled here
|
||||
GPSState ephgpsmsg, oldephgpsmsg; // always has a consistent ephemeris
|
||||
GPSAlmanac gpsalma;
|
||||
|
||||
// beidou:
|
||||
int t0eMSB{-1}, t0eLSB{-1}, aode{-1}, aodc{-1};
|
||||
BeidouMessage beidouMessage, oldBeidouMessage;
|
||||
BeidouMessage lastBeidouMessage1, lastBeidouMessage2;
|
||||
|
||||
int wn() const; // gets from the 'live' message
|
||||
int tow() const; // same
|
||||
|
||||
TLERepo::Match tleMatch;
|
||||
double lastTLELookupX{0};
|
||||
|
||||
// live, ephemeris
|
||||
BeidouMessage beidoumsg, ephBeidoumsg, oldephBeidoumsg;
|
||||
// internal
|
||||
BeidouMessage lastBeidouMessage1, lastBeidouMessage2;
|
||||
|
||||
// new galileo
|
||||
GalileoMessage galmsg;
|
||||
// consistent, live
|
||||
GalileoMessage ephgalmsg, galmsg, oldephgalmsg;
|
||||
// internal
|
||||
map<int, GalileoMessage> galmsgTyped;
|
||||
|
||||
// Glonass
|
||||
GlonassMessage glonassMessage;
|
||||
GlonassMessage ephglomsg, glonassMessage, oldephglomsg;
|
||||
pair<uint32_t, GlonassMessage> glonassAlmaEven;
|
||||
|
||||
map<uint64_t, SVPerRecv> perrecv;
|
||||
|
||||
double latestDisco{-1}, latestDiscoAge{-1}, timeDisco{-1000};
|
||||
|
||||
map<int, SVIOD> iods;
|
||||
void addGalileoWord(std::basic_string_view<uint8_t> page);
|
||||
|
||||
bool completeIOD() const;
|
||||
uint16_t getIOD() const;
|
||||
SVIOD liveIOD() const;
|
||||
SVIOD& getEph(int i) { return iods[i]; } // XXXX gps adaptor
|
||||
|
||||
pair<int,SVIOD> prevIOD{-1, SVIOD()};
|
||||
void clearPrev()
|
||||
{
|
||||
prevIOD.first = -1;
|
||||
}
|
||||
void checkCompleteAndClean(int iod);
|
||||
map<int, SBASCombo> sbas;
|
||||
|
||||
RTCMMessage::EphemerisDelta rtcmEphDelta;
|
||||
|
||||
const GPSLikeEphemeris& liveIOD() const;
|
||||
const GPSLikeEphemeris& prevIOD() const;
|
||||
|
||||
bool completeIOD() const;
|
||||
double getCoordinates(double tow, Point* p, bool quiet=true) const;
|
||||
double getOldEphCoordinates(double tow, Point* p, bool quiet=true) const;
|
||||
void getSpeed(double tow, Vector* v) const;
|
||||
DopplerData doDoppler(double tow, const Point& us, double freq) const;
|
||||
void reportNewEphemeris(const SatID& id, InfluxPusher& idb);
|
||||
};
|
||||
|
||||
|
||||
|
|
52
navrecv.cc
52
navrecv.cc
|
@ -11,9 +11,10 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "zstdwrap.hh"
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "version.hh"
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
static char program[]="navrecv";
|
||||
|
||||
|
@ -138,10 +139,55 @@ void writeToDisk(time_t s, uint64_t sourceid, std::string_view message)
|
|||
}
|
||||
}
|
||||
|
||||
// note that this moves the socket
|
||||
void recvSession2(Socket&& uns, ComboAddress client)
|
||||
{
|
||||
string secret = SRead(uns, 8); // ignored for now
|
||||
cerr << "Entering compressed session for "<<client.toStringWithPort()<<endl;
|
||||
ZStdReader zsr(uns);
|
||||
int s = zsr.getFD();
|
||||
// time_t start = time(0);
|
||||
for(;;) {
|
||||
// enable this to test ubxtool resilience & buffering
|
||||
// if(time(0) - start > 30)
|
||||
// sleep(10);
|
||||
string num=SRead(s, 4);
|
||||
if(num.empty()) {
|
||||
cerr<<"EOF from "<<client.toStringWithPort()<<endl;
|
||||
break;
|
||||
}
|
||||
string out="bert";
|
||||
|
||||
string part = SRead(s, 2);
|
||||
out += part;
|
||||
|
||||
uint16_t len;
|
||||
memcpy(&len, part.c_str(), 2);
|
||||
len = htons(len);
|
||||
|
||||
part = SRead(s, len);
|
||||
out += part;
|
||||
|
||||
NavMonMessage nmm;
|
||||
nmm.ParseFromString(part);
|
||||
uint32_t denum;
|
||||
|
||||
memcpy(&denum, num.c_str(), 4);
|
||||
denum = htonl(denum);
|
||||
// cerr<<"Received message "<<denum<< " "<<nmm.localutcseconds()<<" " << nmm.localutcnanoseconds()/1000000000.0<<endl;
|
||||
writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
|
||||
|
||||
SSetsockopt(uns, IPPROTO_TCP, TCP_CORK, 1 );
|
||||
|
||||
SWrite(uns, num);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void recvSession(int s, ComboAddress client)
|
||||
{
|
||||
try {
|
||||
Socket sock(s);
|
||||
Socket sock(s); // this closes on destruction
|
||||
cerr<<"Receiving messages from "<<client.toStringWithPort()<<endl;
|
||||
for(;;) {
|
||||
string part=SRead(sock, 4);
|
||||
|
@ -150,6 +196,8 @@ void recvSession(int s, ComboAddress client)
|
|||
break;
|
||||
}
|
||||
if(part != "bert") {
|
||||
if(part == "RNIE")
|
||||
return recvSession2(std::move(sock), client); // protocol v2, socket is moved cuz cleanup is special
|
||||
cerr << "Wrong magic from "<<client.toStringWithPort()<<": "<<part<<endl;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
#include "nmmsender.hh"
|
||||
#include "comboaddress.hh"
|
||||
#include "swrappers.hh"
|
||||
#include "sclasses.hh"
|
||||
#include <random>
|
||||
#include "navmon.hh"
|
||||
#include <algorithm>
|
||||
#include "zstdwrap.hh"
|
||||
#include <netinet/tcp.h>
|
||||
using namespace std;
|
||||
|
||||
void NMMSender::sendTCPThread(Destination* d)
|
||||
{
|
||||
struct NameError{};
|
||||
for(;;) {
|
||||
ComboAddress chosen;
|
||||
map<uint32_t, string> unacked;
|
||||
try {
|
||||
vector<ComboAddress> addrs;
|
||||
for(;;) {
|
||||
addrs=resolveName(d->dst, true, true);
|
||||
if(!addrs.empty())
|
||||
break;
|
||||
|
||||
cerr<<humanTimeNow()<<" Unable to resolve "<<d->dst<<", sleeping and trying again later"<<endl;
|
||||
throw NameError();
|
||||
}
|
||||
|
||||
std::random_device rng;
|
||||
std::mt19937 urng(rng());
|
||||
std::shuffle(addrs.begin(), addrs.end(), urng);
|
||||
|
||||
for(auto& addr: addrs) {
|
||||
if(!addr.sin4.sin_port)
|
||||
addr.sin4.sin_port = ntohs(29603);
|
||||
chosen=addr;
|
||||
Socket s(addr.sin4.sin_family, SOCK_STREAM);
|
||||
SocketCommunicator sc(s);
|
||||
sc.setTimeout(3);
|
||||
sc.connect(addr);
|
||||
time_t connStartTime = time(0);
|
||||
if (d_debug) { cerr<<humanTimeNow()<<" Connected to "<<d->dst<<" on "<<addr.toStringWithPort()<<endl; }
|
||||
auto emit = [&sc](const char*buf, uint32_t len) {
|
||||
sc.writen(string(buf, len));
|
||||
};
|
||||
std::unique_ptr<ZStdCompressor> zsc;
|
||||
if(d_compress) {
|
||||
sc.writen("RNIE00000000"); // the other magic value is "bert". hence.
|
||||
// the 00000000 is a placeholder for a "secret" we might implement later
|
||||
zsc = std::make_unique<ZStdCompressor>(emit, 20);
|
||||
}
|
||||
bool hadMessage=false;
|
||||
int msgnum = 0;
|
||||
|
||||
for(;;) {
|
||||
uint32_t num;
|
||||
// read acks
|
||||
for(;;) {
|
||||
int res = read(s, &num, 4);
|
||||
if(res < 0) {
|
||||
if(errno != EAGAIN)
|
||||
unixDie("Reading acknowledgements in nmmsender");
|
||||
break;
|
||||
}
|
||||
if(res==0)
|
||||
throw std::runtime_error("EOF while reading acks");
|
||||
if(res==4) {
|
||||
num = ntohl(num);
|
||||
unacked.erase(num);
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Partial read of "+to_string(res)+" bytes");
|
||||
}
|
||||
|
||||
|
||||
std::string msg;
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if(!d->queue.empty()) {
|
||||
msg = d->queue.front();
|
||||
}
|
||||
}
|
||||
if(!msg.empty()) {
|
||||
hadMessage=true;
|
||||
if(zsc) {
|
||||
|
||||
uint32_t num = htonl(msgnum);
|
||||
string encap((const char*)&num, 4);
|
||||
encap += msg;
|
||||
zsc->give(encap.c_str(), encap.size());
|
||||
unacked[msgnum] = msg;
|
||||
msgnum++;
|
||||
|
||||
}
|
||||
else
|
||||
sc.writen(msg);
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
d->queue.pop_front();
|
||||
|
||||
}
|
||||
else {
|
||||
if(zsc && hadMessage) {
|
||||
// cerr << "Compressed to: "<< 100.0*zsc->d_outputBytes/zsc->d_inputBytes<<"%, buffered compressed: "<<zsc->outputBufferBytes()<<" out of " <<zsc->outputBufferCapacity()<<" bytes. Unacked: "<<unacked.size()<<endl;
|
||||
|
||||
zsc->flush();
|
||||
|
||||
if(time(0) - connStartTime > 10 && unacked.size() > 1000)
|
||||
throw std::runtime_error("Too many messages unacked, recycling connection");
|
||||
|
||||
|
||||
|
||||
}
|
||||
hadMessage = false;
|
||||
usleep(100000);
|
||||
#ifdef __linux__
|
||||
SSetsockopt(s, IPPROTO_TCP, TCP_CORK, 1 );
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(NameError&) {
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (d_debug) { cerr<<humanTimeNow()<<" There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<", and "<<unacked.size()<<" unacknowledged"<<endl; }
|
||||
}
|
||||
sleep(30);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
if (d_debug) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst<<" via "<<chosen.toStringWithPort()<<" had error: "<<e.what()<<endl; }
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (d_debug) { cerr<<humanTimeNow()<<" There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<", and "<<unacked.size()<<" unacknowledged"<<endl; }
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
catch(...) {
|
||||
if (d_debug) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst <<" via "<<chosen.toStringWithPort()<<" had error"; }
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (d_debug) { cerr<<"There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<", and "<<unacked.size()<<" unacknowledge via "<<chosen.toStringWithPort()<<endl; }
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if(!unacked.empty()) {
|
||||
cerr<<humanTimeNow()<< " Stuffing "<<unacked.size()<<" messages back into the queue"<<endl;
|
||||
for(auto iter= unacked.rbegin(); iter != unacked.rend(); ++iter) {
|
||||
d->queue.push_front(iter->second);
|
||||
}
|
||||
unacked.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NMMSender::emitNMM(const NavMonMessage& nmm)
|
||||
{
|
||||
for(auto& d : d_dests) {
|
||||
d->emitNMM(nmm, d_compress);
|
||||
}
|
||||
}
|
||||
|
||||
void NMMSender::Destination::emitNMM(const NavMonMessage& nmm, bool compressed)
|
||||
{
|
||||
string out;
|
||||
nmm.SerializeToString(& out);
|
||||
string msg;
|
||||
if(!compressed)
|
||||
msg="bert";
|
||||
|
||||
uint16_t len = htons(out.size());
|
||||
msg.append((char*)&len, 2);
|
||||
msg.append(out);
|
||||
|
||||
if(!dst.empty()) {
|
||||
std::lock_guard<std::mutex> l(mut);
|
||||
queue.push_back(msg);
|
||||
}
|
||||
else
|
||||
writen2(fd, msg.c_str(), msg.size());
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <atomic>
|
||||
#include "navmon.pb.h"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
class NMMSender
|
||||
{
|
||||
struct Destination
|
||||
{
|
||||
int fd{-1};
|
||||
std::string dst;
|
||||
std::string fname;
|
||||
|
||||
std::deque<std::string> queue;
|
||||
std::mutex mut;
|
||||
void emitNMM(const NavMonMessage& nmm, bool compress);
|
||||
};
|
||||
|
||||
public:
|
||||
void addDestination(int fd)
|
||||
{
|
||||
auto d = std::make_unique<Destination>();
|
||||
d->fd = fd;
|
||||
d_dests.push_back(std::move(d));
|
||||
}
|
||||
void addDestination(const std::string& dest)
|
||||
{
|
||||
auto d = std::make_unique<Destination>();
|
||||
d->dst = dest;
|
||||
d_dests.push_back(std::move(d));
|
||||
}
|
||||
|
||||
void launch()
|
||||
{
|
||||
for(auto& d : d_dests) {
|
||||
if(!d->dst.empty()) {
|
||||
std::thread t(&NMMSender::sendTCPThread, this, d.get());
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendTCPThread(Destination* d);
|
||||
|
||||
void emitNMM(const NavMonMessage& nmm);
|
||||
bool d_debug{false};
|
||||
bool d_compress{false}; // set BEFORE launch
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Destination>> d_dests;
|
||||
};
|
1
osen.cc
1
osen.cc
|
@ -1,5 +1,6 @@
|
|||
#include <tuple>
|
||||
#include <math.h>
|
||||
#include "ephemeris.hh"
|
||||
|
||||
/* gratefully adopted from http://danceswithcode.net/engineeringnotes/geodetic_to_ecef/geodetic_to_ecef.html
|
||||
which in turn is based on the excellent work from
|
||||
|
|
48
reporter.cc
48
reporter.cc
|
@ -38,6 +38,8 @@ struct IntervalStat
|
|||
{
|
||||
std::optional<int> unhealthy;
|
||||
std::optional<int> sisa;
|
||||
bool ripe{false};
|
||||
bool expired{false};
|
||||
};
|
||||
|
||||
|
||||
|
@ -116,12 +118,34 @@ int main(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
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"]) {
|
||||
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"]) {
|
||||
if(v.size() > 1 && v[1] != nullptr) {
|
||||
int seconds = (int)v[1];
|
||||
if(seconds > 86400) { // probably wraparound
|
||||
}
|
||||
else if(seconds > 4*3600) {
|
||||
g_stats[id][(int)v[0]].expired = 1;
|
||||
cout<<makeSatIDName(id)<<": "<<humanTimeShort(v[0])<<" " << seconds<<endl;
|
||||
}
|
||||
else if(seconds > 2*3600)
|
||||
g_stats[id][(int)v[0]].ripe = (int)v[1] > 7200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,11,1}];
|
||||
g_stats[{2,19,1}];
|
||||
|
||||
unsigned int maxintervals=0;
|
||||
time_t start=time(0), stop=0;
|
||||
|
@ -140,10 +164,16 @@ int main(int argc, char **argv)
|
|||
cout<<"Report on "<<g_stats.size()<<" SVs from "<<humanTime(start) <<" to " <<humanTime(stop) << endl;
|
||||
int totnapa=0, totunhealthy=0, tothealthy=0, tottesting=0;
|
||||
int totunobserved=0;
|
||||
int totripe = 0, totexpired = 0;
|
||||
for(const auto& sv : g_stats) {
|
||||
|
||||
int napa=0, unhealthy=0, healthy=0, testing=0;
|
||||
int napa=0, unhealthy=0, healthy=0, testing=0, ripe=0, expired=0;
|
||||
for(const auto& i : sv.second) {
|
||||
if(i.second.ripe)
|
||||
ripe++;
|
||||
if(i.second.expired)
|
||||
expired++;
|
||||
|
||||
if(i.second.unhealthy) {
|
||||
if(*i.second.unhealthy==1)
|
||||
unhealthy++;
|
||||
|
@ -169,24 +199,30 @@ int main(int argc, char **argv)
|
|||
totunhealthy += unhealthy;
|
||||
tottesting += testing;
|
||||
tothealthy += healthy;
|
||||
totripe += ripe;
|
||||
totexpired += expired;
|
||||
totunobserved += maxintervals-sv.second.size();
|
||||
|
||||
cout<<fmt::sprintf("E%02d: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa",
|
||||
cout<<fmt::sprintf("E%02d: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa, %6.2f%% ripe, %6.2f%% expired",
|
||||
sv.first.sv,
|
||||
100.0*(maxintervals-sv.second.size())/maxintervals,
|
||||
100.0*unhealthy/maxintervals,
|
||||
100.0*healthy/maxintervals,
|
||||
100.0*testing/maxintervals,
|
||||
100.0*napa/maxintervals
|
||||
100.0*napa/maxintervals,
|
||||
100.0*ripe/maxintervals,
|
||||
100.0*expired/maxintervals
|
||||
)<<endl;
|
||||
}
|
||||
cout<<"------------------------------------------------------------------------------------------"<<endl;
|
||||
cout<<fmt::sprintf("Tot: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa",
|
||||
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(),
|
||||
100.0*totunhealthy/maxintervals/g_stats.size(),
|
||||
100.0*tothealthy/maxintervals/g_stats.size(),
|
||||
100.0*tottesting/maxintervals/g_stats.size(),
|
||||
100.0*totnapa/maxintervals/g_stats.size()
|
||||
100.0*totnapa/maxintervals/g_stats.size(),
|
||||
100.0*totripe/maxintervals/g_stats.size(),
|
||||
100.0*totexpired/maxintervals/g_stats.size()
|
||||
)<<endl;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
#include "rtcm.hh"
|
||||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void RTCMMessage::parse(const std::string& str)
|
||||
{
|
||||
auto gbu=[&str](int offset, int bits) {
|
||||
return getbitu((const unsigned char*)str.c_str(), offset, bits);
|
||||
};
|
||||
auto gbs=[&str](int offset, int bits) {
|
||||
return getbits((const unsigned char*)str.c_str(), offset, bits);
|
||||
};
|
||||
|
||||
type = gbu(0, 12);
|
||||
// cout<<"Message number: "<<type << " of size "<<str.size()<<"\n";
|
||||
if(type == 1057 || type == 1240) {
|
||||
d_ephs.clear();
|
||||
int stride;
|
||||
int iodlen;
|
||||
if(type == 1057) { // GPS
|
||||
stride = 135;
|
||||
iodlen=8;
|
||||
}
|
||||
else { // Galileo
|
||||
stride=137;
|
||||
iodlen=10;
|
||||
}
|
||||
|
||||
int sats = gbu(62, 6);
|
||||
sow = gbu(12, 20);
|
||||
udi = gbu(32, 4);
|
||||
mmi = gbu(36, 1);
|
||||
reference = gbu(37,1);
|
||||
ssrIOD = gbu(38,4);
|
||||
ssrProvider = gbu(42, 16);
|
||||
ssrSolution = gbu(58, 4);
|
||||
|
||||
// cout <<" sow "<< sow <<" sats "<<sats<<" update interval " << udi <<" mmi " << mmi;
|
||||
// cout <<" reference "<< reference << " iod-ssr "<< ssrIOD << " ssr-provider " << ssrProvider << " ssr-solution ";
|
||||
// cout<< ssrSolution <<":\n";
|
||||
|
||||
for(int n = 0; n < sats; ++n) {
|
||||
EphemerisDelta ed;
|
||||
|
||||
int off = 68+stride*n;
|
||||
ed.radial = gbs(off+ iodlen + 6, 22) * 0.1;
|
||||
ed.along = gbs(off+ iodlen+ 28, 20) * 0.4;
|
||||
ed.cross = gbs(off+ iodlen+48, 20) * 0.4;
|
||||
|
||||
|
||||
ed.dradial = gbs(off + iodlen+ 68, 21) * 0.001;
|
||||
ed.dalong = gbs(off + iodlen + 89, 19) * 0.004;
|
||||
ed.dcross = gbs(off + iodlen +108, 19) * 0.004;
|
||||
ed.iod = gbu(off +6, iodlen);
|
||||
ed.sow = sow;
|
||||
if(type == 1057) {
|
||||
ed.id.gnss = 0;
|
||||
ed.id.sigid = 0;
|
||||
}
|
||||
else if(type == 1240) {
|
||||
ed.id.gnss = 2;
|
||||
ed.id.sigid = 1;
|
||||
}
|
||||
|
||||
ed.id.sv = gbu(off + 0, 6);
|
||||
// cout<<" "<<makeSatIDName(ed.id)<<" iode "<< ed.iod<<" ("<< ed.radial<<", "<<ed.along<<", "<<ed.cross<<") mm -> (";
|
||||
// cout<< ed.dradial<<", "<<ed.dalong<<", "<<ed.dcross<< ") mm/s\n";
|
||||
d_ephs.push_back(ed);
|
||||
}
|
||||
}
|
||||
else if(type == 1058 || type == 1241) {
|
||||
d_clocks.clear();
|
||||
int sats = gbu(61, 6);
|
||||
sow = gbu(12, 20);
|
||||
udi = gbu(32, 4);
|
||||
mmi = gbu(36, 1);
|
||||
ssrIOD = gbu(37, 4);
|
||||
ssrProvider = gbu(41, 16);
|
||||
ssrSolution=gbu(57, 4);
|
||||
|
||||
// cout <<" sow "<< sow <<" sats "<<sats<<" update interval " << udi <<" mmi " << mmi;
|
||||
// cout << " iod-ssr "<< ssrIOD << " ssr-provider " << ssrProvider << " ssr-solution ";
|
||||
// cout<< ssrSolution <<":\n";
|
||||
|
||||
for(int n = 0; n < sats; ++n) {
|
||||
ClockDelta cd;
|
||||
if(type == 1058) {
|
||||
cd.id.gnss = 0;
|
||||
cd.id.sigid = 0;
|
||||
}
|
||||
else if(type == 1241) {
|
||||
cd.id.gnss = 2;
|
||||
cd.id.sigid = 1;
|
||||
}
|
||||
|
||||
int off = 67+76*n;
|
||||
cd.id.sv = gbu(off +0, 6);
|
||||
cd.dclock0 = gbs(off + 6, 22)*1e-4;
|
||||
cd.dclock1 = gbs(off + 28, 21)*1e-6;
|
||||
cd.dclock2 = gbs(off + 49, 27)*2e-8;
|
||||
d_clocks.push_back(cd);
|
||||
// cout<<" "<< makeSatIDName(cd.id)<<" ";
|
||||
// cout<< cd.dclock0 <<" ";
|
||||
// cout<< cd.dclock1 <<" ";
|
||||
// cout<< cd.dclock2 << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include "navmon.hh"
|
||||
#include <vector>
|
||||
struct RTCMFrame
|
||||
{
|
||||
std::string payload;
|
||||
};
|
||||
|
||||
class RTCMReader
|
||||
{
|
||||
public:
|
||||
explicit RTCMReader(int fd) : d_fp(fdopen(fd, "r")) {}
|
||||
bool get(RTCMFrame& rf);
|
||||
private:
|
||||
FILE* d_fp;
|
||||
};
|
||||
|
||||
|
||||
struct RTCMMessage
|
||||
{
|
||||
void parse(const std::string& str);
|
||||
int type;
|
||||
int sow;
|
||||
int udi;
|
||||
bool mmi;
|
||||
bool reference;
|
||||
int ssrIOD, ssrProvider, ssrSolution;
|
||||
struct EphemerisDelta
|
||||
{
|
||||
SatID id;
|
||||
// in millimeters
|
||||
double radial, along, cross;
|
||||
double dradial, dalong, dcross;
|
||||
int iod;
|
||||
int sow;
|
||||
};
|
||||
struct ClockDelta
|
||||
{
|
||||
SatID id;
|
||||
double dclock0; // in meters
|
||||
double dclock1;
|
||||
double dclock2;
|
||||
};
|
||||
|
||||
std::vector<EphemerisDelta> d_ephs;
|
||||
std::vector<ClockDelta> d_clocks;
|
||||
|
||||
};
|
|
@ -0,0 +1,166 @@
|
|||
#include "rtcm.hh"
|
||||
#include "bits.hh"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include "nmmsender.hh"
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "swrappers.hh"
|
||||
#include "sclasses.hh"
|
||||
#include "version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool RTCMReader::get(RTCMFrame& rf)
|
||||
{
|
||||
int c;
|
||||
while( ((c=fgetc(d_fp)) != -1) && c != 211) {
|
||||
cerr<<"Skipped.. "<<endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c != 211) {
|
||||
cerr<<"EOF"<<endl;
|
||||
return false;
|
||||
}
|
||||
// cout<<"Found preamble"<<endl;
|
||||
unsigned char buffer[2];
|
||||
if(fread((char*)buffer, 1, 2, d_fp) != 2)
|
||||
return false;
|
||||
|
||||
// cout<<"Got two byte buffer"<<endl;
|
||||
// 6 bits reserved, 10 bits of size
|
||||
int size = getbitu(buffer, 6, 10);
|
||||
size += 3;
|
||||
// cout<<"Now reading "<<size<<" bytes"<<endl;
|
||||
vector<char> buf(size);
|
||||
if((int)fread(&buf[0], 1, size, d_fp) != size)
|
||||
return false;
|
||||
|
||||
// cout<<"Returning true"<<endl;
|
||||
rf.payload.assign(&buf[0], size - 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Message types:
|
||||
Type 1240, Galileo orbit corrections to Broadcast Ephemeris
|
||||
Type 1241, Galileo clock corrections to Broadcast Ephemeris
|
||||
1057 message for GPS orbit corrections to Broadcast Ephemeris,
|
||||
1058 message for GPS clock corrections to Broadcast Ephemeris,
|
||||
*/
|
||||
|
||||
|
||||
/* 1057 (GPS), 1240 (Galileo)
|
||||
0 12 bits message number
|
||||
12 20 bits galileo SOW
|
||||
32 4 bits SSR update interval
|
||||
36 1 bit multiple message indicator
|
||||
37 1 bit Satellite Reference Datum (0: ITRF, 1: regional)
|
||||
38 4 bit (IOD SSR)
|
||||
42 16 bits SSR provider ID
|
||||
58 4 bits SSR solution ID
|
||||
62 6 bits number of satellites
|
||||
68
|
||||
Repeat, starting from pos 68:
|
||||
0 6 bits sv
|
||||
6 10 bits IODE nav / 8 for GPS, a shift of -2 everywhere below
|
||||
16 22 bits delta radial (0.1mm)
|
||||
38 20 bits along track (0.4mm)
|
||||
58 20 bits cross track (0.4mm)
|
||||
78 21 bits dot delta radial (0.001 mm/s)
|
||||
99 19 bits dot delta along (0.004 mm/s)
|
||||
118 19 bits dot delta cross-track (0.004 mm/s)
|
||||
137
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
1058 (GPS), 1241 (Galileo)
|
||||
0 12 bits message number
|
||||
12 20 bits galileo SOW (GPS Epoch time)
|
||||
32 4 bits "UDI" ("SSR Update interval")
|
||||
36 1 bit "sync" ("multiple message indicator")
|
||||
37 4 bit (IOD SSR, link to 1240 I think)
|
||||
41 16 bits SSR provider ID
|
||||
57 4 bits SSR solution ID
|
||||
61 6 bits number of satellites
|
||||
67
|
||||
|
||||
67
|
||||
Repeat, starting from pos 68:
|
||||
0 6 bits sv
|
||||
6 22 bits dclk[0] 1e-4 meter // 0.1 mm
|
||||
28 21 bits dclk[1] 1e-6 meter/s // 0.001 mm/s
|
||||
49 27 bits dclk[2] 2e-8 meter // 0.0002mm/s^2
|
||||
76
|
||||
|
||||
Reference time is Epoch Time + 0.5* SSR update interval, which can be zero.
|
||||
*/
|
||||
|
||||
/*
|
||||
CLKA[0,1]_DEU1 – containing the SSR corrections regarding the satellites’ Antenna Phase Center
|
||||
CLKC[0,1]_DEU1 – containing the SSR corrections regarding the satellites’ Center of Mass.
|
||||
*/
|
||||
|
||||
static char program[]="rtcmtool";
|
||||
uint16_t g_srcid{0};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// time_t starttime=time(0);
|
||||
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
||||
vector<string> destinations;
|
||||
|
||||
bool doVERSION{false}, doSTDOUT{false};
|
||||
CLI::App app(program);
|
||||
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
|
||||
app.add_option("--station", g_srcid, "Station id")->required();
|
||||
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);
|
||||
|
||||
ns.launch();
|
||||
|
||||
RTCMReader rr(0);
|
||||
RTCMFrame rf;
|
||||
while(rr.get(rf)) {
|
||||
// cout<<"Got a "<<rf.payload.size()<<" byte frame"<<endl;
|
||||
RTCMMessage rm;
|
||||
NavMonMessage nmm;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, & ts);
|
||||
nmm.set_type(NavMonMessage::RTCMMessageType);
|
||||
nmm.set_localutcseconds(ts.tv_sec);
|
||||
nmm.set_localutcnanoseconds(ts.tv_nsec);
|
||||
nmm.set_sourceid(g_srcid);
|
||||
nmm.mutable_rm()->set_contents(rf.payload);
|
||||
ns.emitNMM(nmm);
|
||||
|
||||
// rm.parse(rf.payload);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
#include "sbas.hh"
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
#include "bits.hh"
|
||||
#include <math.h>
|
||||
|
||||
void SBASState::parse0(const basic_string<uint8_t>& sbas, time_t now)
|
||||
{
|
||||
d_lastDNU = now;
|
||||
d_lastSeen = now;
|
||||
}
|
||||
|
||||
|
||||
void SBASState::parse1(const basic_string<uint8_t>& sbas, time_t now)
|
||||
{
|
||||
d_lastSeen = now;
|
||||
int slot=1;
|
||||
d_slot2prn.clear();
|
||||
for(int prn = 0; prn < 210; ++prn) {
|
||||
if(getbitu(&sbas[0], 14+ prn, 1)) {
|
||||
d_slot2prn[slot]=prn+1;
|
||||
// cout<<slot<<"=G"<<prn+1<<" ";
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<SBASState::FastCorrection> SBASState::parse2_5(const basic_string<uint8_t>&sbas, time_t now)
|
||||
{
|
||||
d_lastSeen = now;
|
||||
int type = getbitu(&sbas[0], 8, 6);
|
||||
vector<SBASState::FastCorrection> ret;
|
||||
// IODFi, IODP, 13*12 bits fast correction, 13*4 bits UDREI
|
||||
for(int pos = 0; pos < 13; ++pos) {
|
||||
int slot = 1+13*(type-2)+pos;
|
||||
|
||||
if(d_slot2prn.count(slot)) {
|
||||
FastCorrection fc;
|
||||
fc.id = getSBASSatID(slot);
|
||||
fc.udrei = getbitu(&sbas[0], 14 + 4 + 12*13 + 4*pos, 4);
|
||||
fc.correction = getbits(&sbas[0], 14 + 4 + 12*pos, 12)*0.125;
|
||||
fc.lastUpdate=now;
|
||||
ret.push_back(fc);
|
||||
d_fast[fc.id] = fc;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<SBASState::FastCorrection> SBASState::parse6(const basic_string<uint8_t>&sbas, time_t now)
|
||||
{
|
||||
d_lastSeen = now;
|
||||
vector<SBASState::FastCorrection> ret;
|
||||
|
||||
for(int slot = 0; slot < 51; ++slot) {
|
||||
SatID sid = getSBASSatID(slot + 1);
|
||||
if(sid.gnss == 255)
|
||||
continue;
|
||||
if(!d_fast.count(sid))
|
||||
continue;
|
||||
|
||||
FastCorrection& fc = d_fast[sid];
|
||||
fc.id = sid;
|
||||
fc.udrei = getbitu(&sbas[0], 14 + 8 + 4* slot, 4);
|
||||
fc.lastUpdate = now;
|
||||
ret.push_back(fc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SBASState::parse7(const basic_string<uint8_t>&sbas, time_t now)
|
||||
{
|
||||
d_lastSeen = now;
|
||||
d_latency = getbitu(&sbas[0], 14+4, 4);
|
||||
}
|
||||
|
||||
int SBASState::getSBASNumber(int slot) const
|
||||
{
|
||||
auto iter = d_slot2prn.find(slot);
|
||||
if(iter != d_slot2prn.end()) {
|
||||
int prn = iter->second;
|
||||
if(prn < 37)
|
||||
return prn;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
SatID SBASState::getSBASSatID(int slot) const
|
||||
{
|
||||
SatID ret;
|
||||
auto iter = d_slot2prn.find(slot);
|
||||
if(iter != d_slot2prn.end()) {
|
||||
int prn = iter->second;
|
||||
if(prn < 37) {
|
||||
ret.gnss = 0;
|
||||
ret.sv = prn;
|
||||
ret.sigid = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<SBASState::LongTermCorrection> SBASState::parse25(const basic_string<uint8_t>& sbas, time_t t)
|
||||
{
|
||||
d_lastSeen = t;
|
||||
vector<LongTermCorrection> ret;
|
||||
for(int n=0; n < 2; ++n) {
|
||||
parse25H(sbas, t, 14 +106 *n, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> SBASState::parse24(const basic_string<uint8_t>& sbas, time_t t)
|
||||
{
|
||||
d_lastSeen = t;
|
||||
pair<vector<FastCorrection>, vector<LongTermCorrection>> ret;
|
||||
int fcid = getbitu(&sbas[0], 14+98, 2);
|
||||
|
||||
for(int pos = 0; pos < 6; ++pos) {
|
||||
FastCorrection fc;
|
||||
int slot = 13*(fcid)+pos+1;
|
||||
fc.id = getSBASSatID(slot);
|
||||
|
||||
fc.correction = getbits(&sbas[0], 14 + 12*pos, 12)*0.125;
|
||||
fc.udrei = getbitu(&sbas[0], 14 + 72 + 4*pos, 4);
|
||||
fc.lastUpdate = t;
|
||||
if(d_slot2prn.count(slot)) {
|
||||
|
||||
d_fast[fc.id] = fc;
|
||||
ret.first.push_back(fc);
|
||||
}
|
||||
}
|
||||
parse25H(sbas, t, 120, ret.second);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> SBASState::parse(const std::basic_string<uint8_t>& sbas, time_t now)
|
||||
{
|
||||
pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> ret;
|
||||
int type = getbitu(&sbas[0], 8, 6);
|
||||
if(type == 0) {
|
||||
parse0(sbas, now);
|
||||
}
|
||||
else if(type == 1) {
|
||||
parse1(sbas, now);
|
||||
}
|
||||
else if(type >= 2 && type <= 5) {
|
||||
ret.first = parse2_5(sbas, now);
|
||||
}
|
||||
|
||||
else if(type == 6) {
|
||||
ret.first = parse6(sbas, now);
|
||||
}
|
||||
else if(type ==7) {
|
||||
parse7(sbas, now);
|
||||
}
|
||||
else if(type == 24) {
|
||||
ret = parse24(sbas, now);
|
||||
}
|
||||
else if(type == 25) {
|
||||
ret.second = parse25(sbas, now);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SBASState::parse25H(const basic_string<uint8_t>& sbas, time_t t, int offset, vector<LongTermCorrection>& ret)
|
||||
{
|
||||
LongTermCorrection ltc;
|
||||
ltc.velocity = getbitu(&sbas[0], offset, 1);
|
||||
|
||||
if(ltc.velocity) { // 1 SV
|
||||
int slot = getbitu(&sbas[0], offset + 1, 6);
|
||||
ltc.id = getSBASSatID(slot);
|
||||
ltc.iod8 = getbitu(&sbas[0], offset + 7, 8);
|
||||
|
||||
ltc.dx = 0.125*getbits(&sbas[0], offset + 15, 11);
|
||||
ltc.dy = 0.125*getbits(&sbas[0], offset + 26, 11);
|
||||
ltc.dz = 0.125*getbits(&sbas[0], offset + 37, 11);
|
||||
ltc.dai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 48, 11), -31);
|
||||
|
||||
ltc.ddx = ldexp(getbits(&sbas[0], offset + 59, 8), -11);
|
||||
ltc.ddy = ldexp(getbits(&sbas[0], offset + 67, 8), -11);
|
||||
ltc.ddz = ldexp(getbits(&sbas[0], offset + 75, 8), -11);
|
||||
ltc.ddai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 83, 8), -39);
|
||||
|
||||
ltc.toa = 16 * getbitu(&sbas[0], offset+91, 13);
|
||||
ltc.iodp = getbitu(&sbas[0], offset+104, 2);
|
||||
// 105
|
||||
ltc.lastUpdate = t;
|
||||
if(ltc.toa) {
|
||||
ret.push_back(ltc);
|
||||
d_longterm[ltc.id]=ltc;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(int n = 0 ; n < 2; ++n) {
|
||||
int slot = getbitu(&sbas[0], offset + 1, 6);
|
||||
ltc.id = getSBASSatID(slot);
|
||||
ltc.iod8 = getbitu(&sbas[0], offset + 7, 8);
|
||||
|
||||
ltc.dx = 0.125*getbits(&sbas[0], offset + 15, 9);
|
||||
ltc.dy = 0.125*getbits(&sbas[0], offset + 24, 9);
|
||||
ltc.dz = 0.125*getbits(&sbas[0], offset + 33, 9);
|
||||
|
||||
ltc.dai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 42, 10), -31);
|
||||
|
||||
ltc.ddx = ltc.ddy = ltc. ddz = ltc.ddai = 0;
|
||||
ltc.lastUpdate = t;
|
||||
ret.push_back(ltc);
|
||||
d_longterm[ltc.id]=ltc;
|
||||
offset += 51;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// old version with ephemeris parsing
|
||||
#if 0
|
||||
void parseSBAS25H(int sv, const basic_string<uint8_t>& sbas, time_t t, ofstream& sbascsv, int offset, map<int, GPSState>* gpseph, const Point& src)
|
||||
{
|
||||
bool velocity = getbitu(&sbas[0], offset, 1);
|
||||
|
||||
if(velocity) { // 1 SV
|
||||
int slot = getbitu(&sbas[0], offset + 1, 6);
|
||||
int iod = getbitu(&sbas[0], offset + 7, 8);
|
||||
|
||||
double dx = 0.125*getbits(&sbas[0], offset + 15, 11);
|
||||
double dy = 0.125*getbits(&sbas[0], offset + 26, 11);
|
||||
double dz = 0.125*getbits(&sbas[0], offset + 37, 11);
|
||||
double dai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 48, 11), -31);
|
||||
|
||||
double ddx = ldexp(getbits(&sbas[0], offset + 53, 8), -11);
|
||||
double ddy = ldexp(getbits(&sbas[0], offset + 61, 8), -11);
|
||||
double ddz = ldexp(getbits(&sbas[0], offset + 69, 8), -11);
|
||||
double ddai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 77, 8), -39);
|
||||
|
||||
int tvalid = 16 * getbitu(&sbas[0], offset+85, 13);
|
||||
if(tvalid) {
|
||||
sbascsv << std::fixed<< t <<" " << sv << " ";
|
||||
sbascsv << getSBASSV(sv, slot);
|
||||
cout << "("<<dx<<", "<<dy<<", "<<dz<<", "<<dai<<") -> ";
|
||||
cout << "("<<ddx<<", "<<ddy<<", "<<ddz<<", "<<1000000000*ddai<<") tvalid "<<tvalid<<"\n";
|
||||
|
||||
|
||||
sbascsv <<" " << iod<<" " << dx << " " << dy << " " << dz<<" " << dai;
|
||||
sbascsv << " " << ddx <<" " <<ddy <<" " << ddz<<" " << ddai;
|
||||
if(gpseph && gpseph->count(getSBASNumber(sv, slot))) {
|
||||
auto& gs = (*gpseph)[getSBASNumber(sv, slot)];
|
||||
if(gs.isComplete(gs.gpsiod)) {
|
||||
Point sat;
|
||||
getCoordinates(gs.tow, gs.iods[gs.gpsiod], &sat);
|
||||
sbascsv <<" " << sat.x<<" "<<sat.y<<" " << sat.z;
|
||||
double prerange = Vector(sat, src).length();
|
||||
sat.x += dx; sat.y += dy; sat.z += dz;
|
||||
double postrange = Vector(sat, src).length();
|
||||
sbascsv<<" " <<postrange - prerange;
|
||||
}
|
||||
}
|
||||
|
||||
sbascsv<<"\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
int slot = getbitu(&sbas[0], offset + 1, 6);
|
||||
int iod = getbitu(&sbas[0], offset + 7, 8);
|
||||
|
||||
sbascsv << std::fixed<< t <<" " << sv << " ";
|
||||
sbascsv << getSBASSV(sv, slot);
|
||||
cout<< getSBASSV(sv, slot);
|
||||
cout<<" IOD8 " <<iod<<" ";
|
||||
|
||||
|
||||
double dx = 0.125*getbits(&sbas[0], offset + 15, 9);
|
||||
double dy = 0.125*getbits(&sbas[0], offset + 24, 9);
|
||||
double dz = 0.125*getbits(&sbas[0], offset + 33, 9);
|
||||
double dai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 42, 10), -31);
|
||||
cout << "("<<dx<<", "<<dy<<", "<<dz<<", "<< dai<<" ns)\n";
|
||||
sbascsv <<" " << iod<<" " << dx << " " << dy << " " << dz<<" " << dai;
|
||||
sbascsv <<" 0 0 0 0"; // no delta
|
||||
if(gpseph && gpseph->count(getSBASNumber(sv, slot))) {
|
||||
auto& gs = (*gpseph)[getSBASNumber(sv, slot)];
|
||||
if(gs.isComplete(gs.gpsiod)) {
|
||||
Point sat;
|
||||
getCoordinates(gs.tow, gs.iods[gs.gpsiod], &sat);
|
||||
sbascsv <<" " << sat.x<<" "<<sat.y<<" " << sat.z;
|
||||
double prerange = Vector(sat, src).length();
|
||||
sat.x += dx; sat.y += dy; sat.z += dz;
|
||||
double postrange = Vector(sat, src).length();
|
||||
sbascsv<<" " <<postrange - prerange;
|
||||
|
||||
}
|
||||
}
|
||||
sbascsv <<'\n';
|
||||
|
||||
offset += 51;
|
||||
|
||||
slot = getbitu(&sbas[0], offset + 1, 6);
|
||||
iod = getbitu(&sbas[0], offset + 7, 8);
|
||||
|
||||
sbascsv << t <<" " << sv << " ";
|
||||
sbascsv << getSBASSV(sv, slot);
|
||||
cout<< getSBASSV(sv, slot);
|
||||
cout<<" IOD8 " <<iod<<" ";
|
||||
|
||||
|
||||
dx = 0.125*getbits(&sbas[0], offset + 15, 9);
|
||||
dy = 0.125*getbits(&sbas[0], offset + 24, 9);
|
||||
dz = 0.125*getbits(&sbas[0], offset + 33, 9);
|
||||
dai = 1000000000.0*ldexp(getbits(&sbas[0], offset + 42, 10), -31);
|
||||
cout << "("<<dx<<", "<<dy<<", "<<dz<<", "<< dai<<" ns)\n";
|
||||
sbascsv << std::fixed <<" " << iod<<" " << dx << " " << dy << " " << dz<<" " << dai <<" 0 0 0 0";
|
||||
if(gpseph && gpseph->count(getSBASNumber(sv, slot))) {
|
||||
auto& gs = (*gpseph)[getSBASNumber(sv, slot)];
|
||||
if(gs.isComplete(gs.gpsiod)) {
|
||||
Point sat;
|
||||
getCoordinates(gs.tow, gs.iods[gs.gpsiod], &sat);
|
||||
sbascsv <<" " << sat.x<<" "<<sat.y<<" " << sat.z;
|
||||
double prerange = Vector(sat, src).length();
|
||||
sat.x += dx; sat.y += dy; sat.z += dz;
|
||||
double postrange = Vector(sat, src).length();
|
||||
sbascsv<<" " <<postrange - prerange;
|
||||
}
|
||||
}
|
||||
sbascsv <<'\n';
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "navmon.hh"
|
||||
#include <vector>
|
||||
#include "minivec.hh"
|
||||
|
||||
// 0 do not use
|
||||
// 1 PRN mask
|
||||
// 2-5 fast corrections
|
||||
// 7 Fast correction degradation factor
|
||||
// 10 Degradation parameters
|
||||
// 18 Ionospheric grid point masks
|
||||
// 24 Mixed fastllong-term satellite error corrections
|
||||
// 25 half-message
|
||||
// 26 Ionospheric delay corrections
|
||||
// 27 SBAS service message
|
||||
|
||||
// GSA HQ Prague
|
||||
const Point c_egnosCenter{3970085, 1021937, 4869792};
|
||||
|
||||
// Somewhere in Minnesota, Dakota, Canada border
|
||||
const Point c_waasCenter{-510062, -4166466, 4786089};
|
||||
|
||||
struct SBASState
|
||||
{
|
||||
struct FastCorrection
|
||||
{
|
||||
SatID id;
|
||||
double correction;
|
||||
int udrei;
|
||||
time_t lastUpdate{-1};
|
||||
};
|
||||
|
||||
struct LongTermCorrection
|
||||
{
|
||||
SatID id;
|
||||
int iod8;
|
||||
int toa;
|
||||
int iodp;
|
||||
double dx, dy, dz, dai;
|
||||
double ddx{0}, ddy{0}, ddz{0}, ddai{0};
|
||||
bool velocity{false};
|
||||
time_t lastUpdate{-1};
|
||||
};
|
||||
|
||||
std::pair<std::vector<SBASState::FastCorrection>, std::vector<SBASState::LongTermCorrection>> parse(const std::basic_string<uint8_t>& sbas, time_t now);
|
||||
|
||||
void parse0(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
// updates slot2prn mapping
|
||||
void parse1(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
|
||||
std::vector<FastCorrection> parse2_5(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
std::vector<FastCorrection> parse6(const std::basic_string<uint8_t>& message, time_t now);
|
||||
void parse7(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
std::pair<std::vector<FastCorrection>, std::vector<LongTermCorrection>> parse24(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
std::vector<LongTermCorrection> parse25(const std::basic_string<uint8_t>& message, time_t now);
|
||||
|
||||
int getSBASNumber(int slot) const;
|
||||
SatID getSBASSatID(int slot) const;
|
||||
|
||||
std::map<SatID, FastCorrection> d_fast;
|
||||
std::map<SatID, LongTermCorrection> d_longterm;
|
||||
|
||||
time_t d_lastDNU{-1};
|
||||
std::map<int,int> d_slot2prn;
|
||||
int d_latency = -1;
|
||||
time_t d_lastSeen{-1};
|
||||
void parse25H(const std::basic_string<uint8_t>& sbas, time_t t, int offset, std::vector<LongTermCorrection>& ret);
|
||||
|
||||
};
|
||||
|
||||
struct SBASCombo
|
||||
{
|
||||
SBASState::FastCorrection fast;
|
||||
SBASState::LongTermCorrection longterm;
|
||||
};
|
6
sp3.cc
6
sp3.cc
|
@ -71,10 +71,12 @@ bool SP3Reader::get(SP3Entry& entry)
|
|||
if(!num) {
|
||||
if(token[1]=='G')
|
||||
entry.gnss = 0;
|
||||
if(token[1]=='E')
|
||||
else if(token[1]=='E')
|
||||
entry.gnss = 2;
|
||||
if(token[1]=='C')
|
||||
else if(token[1]=='C')
|
||||
entry.gnss = 3;
|
||||
else
|
||||
continue;
|
||||
entry.sv = atoi(token.c_str()+2);
|
||||
}
|
||||
double val = atof(token.c_str());
|
||||
|
|
171
ubxtool.cc
171
ubxtool.cc
|
@ -34,7 +34,7 @@
|
|||
#include "comboaddress.hh"
|
||||
#include "swrappers.hh"
|
||||
#include "sclasses.hh"
|
||||
|
||||
#include "nmmsender.hh"
|
||||
#include "version.hh"
|
||||
|
||||
static char program[]="ubxtool";
|
||||
|
@ -71,25 +71,6 @@ static int getBaudrate(int baud)
|
|||
|
||||
static int g_baudval;
|
||||
|
||||
size_t writen2(int fd, const void *buf, size_t count)
|
||||
{
|
||||
const char *ptr = (char*)buf;
|
||||
const char *eptr = ptr + count;
|
||||
|
||||
ssize_t res;
|
||||
while(ptr != eptr) {
|
||||
res = ::write(fd, ptr, eptr - ptr);
|
||||
if(res < 0) {
|
||||
throw runtime_error("failed in writen2: "+string(strerror(errno)));
|
||||
}
|
||||
else if (res == 0)
|
||||
throw EofException();
|
||||
|
||||
ptr += (size_t) res;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* inav schedule:
|
||||
1) Find plausible start time of next cycle
|
||||
|
@ -295,147 +276,6 @@ bool sendAndWaitForUBXAckNack(int fd, int seconds, basic_string_view<uint8_t> ms
|
|||
}
|
||||
|
||||
|
||||
class NMMSender
|
||||
{
|
||||
struct Destination
|
||||
{
|
||||
int fd{-1};
|
||||
std::string dst;
|
||||
std::string fname;
|
||||
|
||||
deque<string> queue;
|
||||
std::mutex mut;
|
||||
void emitNMM(const NavMonMessage& nmm);
|
||||
};
|
||||
|
||||
public:
|
||||
void addDestination(int fd)
|
||||
{
|
||||
auto d = std::make_unique<Destination>();
|
||||
d->fd = fd;
|
||||
d_dests.push_back(std::move(d));
|
||||
}
|
||||
void addDestination(const std::string& dest)
|
||||
{
|
||||
auto d = std::make_unique<Destination>();
|
||||
d->dst = dest;
|
||||
d_dests.push_back(std::move(d));
|
||||
}
|
||||
|
||||
void launch()
|
||||
{
|
||||
for(auto& d : d_dests) {
|
||||
if(!d->dst.empty()) {
|
||||
thread t(&NMMSender::sendTCPThread, this, d.get());
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendTCPThread(Destination* d)
|
||||
{
|
||||
struct NameError{};
|
||||
for(;;) {
|
||||
ComboAddress chosen;
|
||||
try {
|
||||
vector<ComboAddress> addrs;
|
||||
for(;;) {
|
||||
addrs=resolveName(d->dst, true, true);
|
||||
if(!addrs.empty())
|
||||
break;
|
||||
|
||||
cerr<<humanTimeNow()<<" Unable to resolve "<<d->dst<<", sleeping and trying again later"<<endl;
|
||||
throw NameError();
|
||||
}
|
||||
|
||||
std::random_device rng;
|
||||
std::mt19937 urng(rng());
|
||||
std::shuffle(addrs.begin(), addrs.end(), urng);
|
||||
|
||||
for(auto& addr: addrs) {
|
||||
if(!addr.sin4.sin_port)
|
||||
addr.sin4.sin_port = ntohs(29603);
|
||||
chosen=addr;
|
||||
Socket s(addr.sin4.sin_family, SOCK_STREAM);
|
||||
SocketCommunicator sc(s);
|
||||
sc.setTimeout(3);
|
||||
sc.connect(addr);
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" Connected to "<<d->dst<<" on "<<addr.toStringWithPort()<<endl; }
|
||||
for(;;) {
|
||||
std::string msg;
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if(!d->queue.empty()) {
|
||||
msg = d->queue.front();
|
||||
}
|
||||
}
|
||||
if(!msg.empty()) {
|
||||
sc.writen(msg);
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
d->queue.pop_front();
|
||||
}
|
||||
else usleep(100000);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(NameError&) {
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<endl; }
|
||||
}
|
||||
sleep(30);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst<<" via "<<chosen.toStringWithPort()<<" had error: "<<e.what()<<endl; }
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<endl; }
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
catch(...) {
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst <<" via "<<chosen.toStringWithPort()<<" had error"; }
|
||||
{
|
||||
std::lock_guard<std::mutex> mut(d->mut);
|
||||
if (doDEBUG) { cerr<<"There are now "<<d->queue.size()<<" messages queued for "<<d->dst<<" via "<<chosen.toStringWithPort()<<endl; }
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void emitNMM(const NavMonMessage& nmm);
|
||||
|
||||
private:
|
||||
|
||||
vector<unique_ptr<Destination>> d_dests;
|
||||
};
|
||||
|
||||
void NMMSender::emitNMM(const NavMonMessage& nmm)
|
||||
{
|
||||
for(auto& d : d_dests) {
|
||||
d->emitNMM(nmm);
|
||||
}
|
||||
}
|
||||
|
||||
void NMMSender::Destination::emitNMM(const NavMonMessage& nmm)
|
||||
{
|
||||
string out;
|
||||
nmm.SerializeToString(& out);
|
||||
string msg("bert");
|
||||
|
||||
uint16_t len = htons(out.size());
|
||||
msg.append((char*)&len, 2);
|
||||
msg.append(out);
|
||||
|
||||
if(!dst.empty()) {
|
||||
std::lock_guard<std::mutex> l(mut);
|
||||
queue.push_back(msg);
|
||||
}
|
||||
else
|
||||
writen2(fd, msg.c_str(), msg.size());
|
||||
}
|
||||
|
||||
|
||||
bool version9 = false;
|
||||
void enableUBXMessageOnPort(int fd, uint8_t ubxClass, uint8_t ubxType, uint8_t port, uint8_t rate=1)
|
||||
|
@ -587,9 +427,10 @@ int main(int argc, char** argv)
|
|||
unsigned int fuzzPositionMeters=0;
|
||||
string owner;
|
||||
string remark;
|
||||
|
||||
bool doCompress=true;
|
||||
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
|
||||
app.add_flag("--wait", doWait, "Wait a bit, do not try to read init messages");
|
||||
// app.add_flag("--compress,-z", doCompress, "Use compressed protocol for network transmission");
|
||||
app.add_flag("--reset", doReset, "Reset UBX device");
|
||||
app.add_flag("--beidou,-c", doBeidou, "Enable BeiDou reception");
|
||||
app.add_flag("--gps,-g", doGPS, "Enable GPS reception");
|
||||
|
@ -642,6 +483,7 @@ int main(int argc, char** argv)
|
|||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
NMMSender ns;
|
||||
ns.d_debug = doDEBUG;
|
||||
for(const auto& s : destinations) {
|
||||
auto res=resolveName(s, true, true);
|
||||
if(res.empty()) {
|
||||
|
@ -725,7 +567,7 @@ int main(int argc, char** argv)
|
|||
}
|
||||
if (doDEBUG && m8t) { cerr<<humanTimeNow()<<" Detected timing module"<<endl; }
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" Sending serial number query"<<endl; }
|
||||
msg = buildUbxMessage(0x27, 0x03, {});
|
||||
msg = buildUbxMessage(0x27, 0x03, {}); // UBX-SEC-UNIQID
|
||||
um1=sendAndWaitForUBX(fd, 1, msg, 0x27, 0x03); // ask for serial
|
||||
serialno = fmt::sprintf("%02x%02x%02x%02x%02x",
|
||||
um1.getPayload()[4],
|
||||
|
@ -919,7 +761,7 @@ int main(int argc, char** argv)
|
|||
if (doDEBUG) { cerr<<humanTimeNow()<<" Got ack on SBAS setting"<<endl; }
|
||||
}
|
||||
else {
|
||||
if (doDEBUG) { cerr<<humanTimeNow()<<" Got nack on SBAS setting"<<endl; }
|
||||
cerr<<humanTimeNow()<<" Got nack on SBAS setting"<<endl;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
@ -1038,6 +880,7 @@ int main(int argc, char** argv)
|
|||
*/
|
||||
|
||||
int curCycleTOW{-1}; // means invalid
|
||||
ns.d_compress = doCompress;
|
||||
ns.launch();
|
||||
|
||||
cerr<<humanTimeNow()<<" Entering main loop"<<endl;
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
#include "zstdwrap.hh"
|
||||
#include <zstd.h>
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include "navmon.hh"
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::cerr;
|
||||
|
||||
|
||||
ZStdCompressor::ZStdCompressor(const std::function<void(const char*, uint32_t)>& emit, int compressionLevel): d_emit(emit)
|
||||
{
|
||||
d_z=ZSTD_createCStream();
|
||||
ZSTD_initCStream(d_z, compressionLevel);
|
||||
d_outcapacity=ZSTD_CStreamOutSize();
|
||||
d_out.dst = malloc(d_outcapacity); // ????
|
||||
d_out.pos=0;
|
||||
d_out.size=d_outcapacity;
|
||||
}
|
||||
|
||||
int ZStdCompressor::maxCompressionLevel()
|
||||
{
|
||||
return ZSTD_maxCLevel();
|
||||
}
|
||||
|
||||
ZStdCompressor::~ZStdCompressor()
|
||||
{
|
||||
for(;;) {
|
||||
auto res = ZSTD_endStream(d_z, &d_out);
|
||||
d_outputBytes += d_out.pos;
|
||||
try {
|
||||
d_emit((const char*)d_out.dst, d_out.pos);
|
||||
}
|
||||
catch(...){}
|
||||
// cout<<"res: "<<res<<endl;
|
||||
if(!res)
|
||||
break;
|
||||
d_out.pos = 0;
|
||||
if(ZSTD_isError(res)) {
|
||||
cerr<<"Error in ZSTD_endStream"<<endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ZSTD_freeCStream(d_z);
|
||||
|
||||
free(d_out.dst); // ????
|
||||
}
|
||||
|
||||
uint32_t ZStdCompressor::outputBufferBytes()
|
||||
{
|
||||
return d_out.pos;
|
||||
}
|
||||
|
||||
uint32_t ZStdCompressor::outputBufferCapacity()
|
||||
{
|
||||
return d_out.size;
|
||||
}
|
||||
|
||||
void ZStdCompressor::flushToEmit()
|
||||
{
|
||||
d_outputBytes += d_out.pos;
|
||||
d_emit((char*)d_out.dst, d_out.pos);
|
||||
d_out.pos=0;
|
||||
}
|
||||
|
||||
void ZStdCompressor::flush()
|
||||
{
|
||||
ZSTD_flushStream(d_z, &d_out);
|
||||
flushToEmit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ZStdCompressor::give(const char* data, uint32_t bytes)
|
||||
{
|
||||
d_inputBytes += bytes;
|
||||
ZSTD_inBuffer in;
|
||||
in.src=data;
|
||||
in.pos=0;
|
||||
in.size=bytes;
|
||||
|
||||
for(;;) {
|
||||
// cout<<"before out: "<<d_out.pos<<endl;
|
||||
ZSTD_compressStream(d_z, &d_out, &in);
|
||||
// cout<<"after out: "<<d_out.pos<<", in.pos="<<in.pos<<", in.size="<<in.size<<endl;
|
||||
if(in.pos == in.size)
|
||||
break;
|
||||
// if we are here, zstd did not consume everything, so we must make room
|
||||
flushToEmit();
|
||||
}
|
||||
}
|
||||
|
||||
ZStdReader::ZStdReader(int fd)
|
||||
{
|
||||
d_sourcefd = fd;
|
||||
|
||||
int pfd[2];
|
||||
if(pipe(pfd) < 0)
|
||||
unixDie("Creating pipe for zstd reader");
|
||||
|
||||
d_readpipe=pfd[0]; // for the customer
|
||||
d_writepipe=pfd[1]; // where we stuff data
|
||||
d_thread = std::thread(std::bind(&ZStdReader::worker, this));
|
||||
}
|
||||
|
||||
static size_t writen(int fd, const void *buf, size_t count)
|
||||
{
|
||||
const char *ptr = (char*)buf;
|
||||
const char *eptr = ptr + count;
|
||||
|
||||
while(ptr != eptr) {
|
||||
ssize_t res = ::write(fd, ptr, eptr - ptr);
|
||||
if(res < 0) {
|
||||
if (errno == EAGAIN)
|
||||
throw std::runtime_error("used writen2 on non-blocking socket, got EAGAIN");
|
||||
else if (errno == EPIPE) {
|
||||
// other end closed, we are pleased to exit
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
unixDie("failed in writen2");
|
||||
}
|
||||
else if (res == 0)
|
||||
throw std::runtime_error("could not write all bytes, got eof in writen2");
|
||||
|
||||
ptr += (size_t) res;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void ZStdReader::worker()
|
||||
{
|
||||
auto z = ZSTD_createDStream(); // think about automatic cleanup somehow
|
||||
ZSTD_initDStream(z);
|
||||
ZSTD_outBuffer output;
|
||||
ZSTD_inBuffer input;
|
||||
|
||||
auto inputcapacity=128;
|
||||
std::vector<char> src(inputcapacity);
|
||||
input.src = &src[0];
|
||||
|
||||
auto outputcapacity = ZSTD_DStreamOutSize();
|
||||
std::vector<char> dst(outputcapacity);
|
||||
output.dst = &dst[0];
|
||||
|
||||
for(;;) {
|
||||
input.pos=0;
|
||||
input.size=read(d_sourcefd, (char*)input.src, inputcapacity);
|
||||
if(input.size <= 0) {
|
||||
cerr<<"Got EOF on input fd "<<d_sourcefd<<", terminating thread"<<endl;
|
||||
break;
|
||||
}
|
||||
while(input.pos != input.size) {
|
||||
output.pos=0;
|
||||
output.size=outputcapacity;
|
||||
ZSTD_decompressStream(z, &output, &input);
|
||||
|
||||
int res;
|
||||
res = writen(d_writepipe, output.dst, output.pos);
|
||||
if(!res) // we are history
|
||||
break;
|
||||
if(res < 0) {
|
||||
cerr<<"Error in zstd thread: "<<strerror(errno)<<endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(d_writepipe);
|
||||
ZSTD_freeDStream(z);
|
||||
}
|
||||
|
||||
ZStdReader::~ZStdReader()
|
||||
{
|
||||
cerr<<"ZStdReader destructor called"<<endl;
|
||||
int rc = close(d_readpipe);
|
||||
cerr<<"Close rc = "<<rc<<endl;
|
||||
cerr<<"Waiting on join"<<endl;
|
||||
d_thread.join();
|
||||
cerr<<"Done waiting on join"<<endl;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <zstd.h> // can't easily be moved to zstdwrap.cc, trust me
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
// users submit (give()) data to this class
|
||||
// the class has an internal buffer to which compressed data gets written
|
||||
// If that buffer is full, we call emit() to empty it
|
||||
// the emit() function must make sure that everything in the buffer gets sent!
|
||||
|
||||
class ZStdCompressor
|
||||
{
|
||||
public:
|
||||
explicit ZStdCompressor(const std::function<void(const char*, uint32_t)>& emit, int compressionLevel);
|
||||
ZStdCompressor(const ZStdCompressor& rhs) = delete;
|
||||
~ZStdCompressor();
|
||||
void give(const char* data, uint32_t bytes);
|
||||
|
||||
static int maxCompressionLevel();
|
||||
uint64_t d_inputBytes{0}, d_outputBytes{0};
|
||||
uint32_t outputBufferBytes(); // Number of bytes in output buffer
|
||||
uint32_t outputBufferCapacity(); // output buffer capacity
|
||||
void flush();
|
||||
|
||||
private:
|
||||
void flushToEmit();
|
||||
ZSTD_CCtx *d_z{nullptr};
|
||||
ZSTD_outBuffer d_out;
|
||||
uint32_t d_outcapacity;
|
||||
std::function<void(const char*, uint32_t)> d_emit;
|
||||
|
||||
};
|
||||
|
||||
/* this class is tremendously devious
|
||||
you pass it a filedescriptor from which it reads zstd compressed data
|
||||
You can then read the uncompressed data on the filedescriptor you
|
||||
get from getFD()
|
||||
*/
|
||||
|
||||
class ZStdReader
|
||||
{
|
||||
public:
|
||||
ZStdReader(int fd); // we don't close this for you
|
||||
ZStdReader(const ZStdReader& rhs) = delete;
|
||||
~ZStdReader();
|
||||
int getFD()
|
||||
{
|
||||
return d_readpipe;
|
||||
}
|
||||
private:
|
||||
std::thread d_thread;
|
||||
int d_sourcefd; // this is where we read compressed data
|
||||
int d_writepipe; // which we then stuff into this pipe
|
||||
int d_readpipe; // and it comes out here for the client
|
||||
|
||||
void worker();
|
||||
};
|
Loading…
Reference in New Issue