Merge branch 'master' into armhf

pull/56/head
Leonid Evdokimov 2020-04-28 12:39:45 +03:00
commit ac9f8576b3
65 changed files with 3354 additions and 1130 deletions

View File

@ -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

View File

@ -22,6 +22,7 @@ RUN set -ex && \
g++ \
git \
libprotobuf-dev \
libzstd-dev \
make \
protobuf-compiler \
&& \

View File

@ -23,6 +23,8 @@ RUN set -ex && \
git \
make \
protobuf-dev \
zstd-dev \
zstd-static \
&& \
:

View File

@ -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

View File

@ -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

112
PACKAGE-DEBIAN.md 100644
View File

@ -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)

View File

@ -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)
--------------------------------

69
architecture.md 100644
View File

@ -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.

View File

@ -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)
{

87
debian/86E7F51C04FBAAB0.asc vendored 100644
View File

@ -0,0 +1,87 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
J7VNNdmxD1RF3zsMFsPTQWY=
=fqVo
-----END PGP PUBLIC KEY BLOCK-----

9
debian/README.Debian vendored 100644
View File

@ -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

5
debian/changelog vendored 100644
View File

@ -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

1
debian/compat vendored 100644
View File

@ -0,0 +1 @@
11

24
debian/control vendored 100644
View File

@ -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"

45
debian/copyright vendored 100644
View File

@ -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.

32
debian/galmon.default vendored 100644
View File

@ -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"

6
debian/galmon.docs vendored 100644
View File

@ -0,0 +1,6 @@
Building.md
influxdb.md
Operator.md
PACKAGE-DEBIAN.md
README.md

View File

@ -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

View File

@ -0,0 +1,10 @@
[Unit]
Description=Update galmon
Documentation=https://github.com/ahupowerdns/galmon
[Timer]
OnBootSec=3min
OnUnitActiveSec=3d
[Install]
WantedBy=timers.target

20
debian/galmon.navnexus.service vendored 100644
View File

@ -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

20
debian/galmon.navrecv.service vendored 100644
View File

@ -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

9
debian/galmon.navstar.default vendored 100644
View File

@ -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"

52
debian/galmon.postinst vendored 100755
View File

@ -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#

3
debian/galmon.substvars vendored 100644
View File

@ -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=

20
debian/galmon.ubxtool@.service vendored 100644
View File

@ -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

1
debian/patches/series vendored 100644
View File

@ -0,0 +1 @@
# You must remove unused comment lines for the released package.

17
debian/profile-debuild.sh vendored 100644
View File

@ -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

22
debian/rules vendored 100755
View File

@ -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

1
debian/source/format vendored 100644
View File

@ -0,0 +1 @@
3.0 (quilt)

2
debian/source/local-options vendored 100644
View File

@ -0,0 +1,2 @@
#abort-on-upstream-changes
#unapply-patches

1
debian/watch vendored 100644
View File

@ -0,0 +1 @@
version=3

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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
View File

@ -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
View File

@ -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;
}

View File

@ -14,7 +14,7 @@ function maketable(str, arr)
enter().
append("tr");
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "delta-utc", "sources", "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 += "&nbsp;<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) {

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://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> &nbsp;&nbsp;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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)
{

View File

@ -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;

View File

@ -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));
}

View File

@ -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);

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
};

View File

@ -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;
}

184
nmmsender.cc 100644
View File

@ -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());
}

54
nmmsender.hh 100644
View File

@ -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;
};

View File

@ -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

View File

@ -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;
}

112
rtcm.cc 100644
View File

@ -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;
}
}
}

50
rtcm.hh 100644
View File

@ -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;
};

166
rtcmtool.cc 100644
View File

@ -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);
}
}

328
sbas.cc 100644
View File

@ -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

83
sbas.hh 100644
View File

@ -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
View File

@ -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());

View File

@ -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;

183
zstdwrap.cc 100644
View File

@ -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;
}

59
zstdwrap.hh 100644
View File

@ -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();
};