Compare commits

..

No commits in common. "master" and "sarsend" have entirely different histories.

109 changed files with 14622 additions and 15175 deletions

View file

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

View file

@ -1,32 +0,0 @@
name: Build and publish Docker image
on:
push:
branches: master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout submodules
run: git submodule update --init --recursive
- name: Set up docker buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v3
with:
buildx-version: latest
qemu-version: latest
- name: Login to docker registry
run: |
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_TOKEN }}
- name: Run buildx
run: |
docker buildx build \
--tag berthubert/galmon \
--platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 \
--output "type=registry" \
--build-arg MAKE_FLAGS=-j1 \
--file Dockerfile \
.

View file

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

18
Dockerfile-pi Normal file
View file

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

View file

@ -1,7 +1,7 @@
CFLAGS = -O3 -Wall -ggdb
CXXFLAGS:= -std=gnu++17 -Wall -O3 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
-Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
CXXFLAGS:= -std=gnu++17 -Wall -O0 -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/ \
-I/usr/local/include
@ -21,10 +21,11 @@ else ifneq (,$(wildcard ubxsec.o))
EXTRADEP = ubxsec.o
endif
CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter sp3feed \
galmonmon rinreport rinjoin rtcmtool gndate septool navmerge
galmonmon rinreport rtcmtool
all: navmon.pb.cc $(PROGRAMS)
@ -42,10 +43,9 @@ navmon.pb.cc: 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
clean:
rm -f *~ *.o *.d ext/*/*.o ext/*/*.d $(PROGRAMS) navmon.pb.h navmon.pb.cc $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) $(H2OPP) $(SIMPLESOCKETS)
rm -f ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
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
@ -73,76 +73,59 @@ download-raspbian-package:
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
$(CXX) -std=gnu++17 $^ -o $@ -lfmt
decrypt: decrypt.o bits.o ext/fmt-6.1.2/src/format.o
$(CXX) -std=gnu++17 $^ -o $@
navparse: navparse.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o galileo.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lh2o-evloop -lssl -lcrypto -lz -lcurl -lprotobuf $(WSLAY) -lfmt
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 $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o influxpush.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
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
sp3feed: sp3feed.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 influxpush.o githash.o sp3.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
sp3feed: sp3feed.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 influxpush.o githash.o sp3.o
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl
tracker: tracker.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 -lfmt
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 $(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 -lfmt
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
# rs.o fixhunter.o
navdump: navdump.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o galileo.o ${EXTRADEP}
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz -lfmt
# -lfec
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 bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses -lfmt
navdisplay: navdisplay.o ext/fmt-6.1.2/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses
navnexus: navnexus.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt
navnexus: navnexus.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
navcat: navcat.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt
navcat: navcat.o ext/fmt-6.1.2/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
navrecv: navrecv.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 -lfmt
navmerge: navmerge.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o nmmsender.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd -lfmt
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
rinreport: rinreport.o rinex.o githash.o navmon.o ephemeris.o osen.o
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
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
rinjoin: rinjoin.o rinex.o githash.o navmon.o ephemeris.o osen.o
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
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 $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd
rtcmtool: rtcmtool.o navmon.pb.o githash.o bits.o nmmsender.o $(SIMPLESOCKETS) navmon.o rtcm.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd -lfmt
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o ext/fmt-6.1.2/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
septool: navmon.pb.o septool.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
testrunner: navmon.pb.o testrunner.o ubx.o bits.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 -pthread -lfmt
gndate: gndate.o githash.o navmon.o
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lfmt
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
check: testrunner
./testrunner

182
README.md
View file

@ -4,17 +4,14 @@ galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
Live website: https://galmon.eu/
Multi-vendor, with support for U-blox 8 and 9 chipsets and many Septentrio
devices. Navilock NL-8012U receiver works really well, as does the U-blox
evaluation kit for the 8MT. In addition, many stations have reported
success with this very cheap [AliExpress sourced
device](https://www.aliexpress.com/item/32816656706.html).
For ublox, there is good support for the F9P, several of us use the
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board. It adds
the Galileo E5b band.
Septentrio devices support even more bands.
Theoretically multi-vendor, although currently only the U-blox 8 and 9
chipsets are supported. Navilock NL-8012U receiver works really well, as
does the U-blox evaluation kit for the 8MT. In addition, many stations have
reported success with this very cheap [AliExpress sourced
device](https://www.aliexpress.com/item/32816656706.html). The best and
most high-end receiver, which does all bands, all the time, is the Ublox
F9P, several of us use the
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board.
An annotated presentation about our project aimed at GNSS professionals can
be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
@ -27,15 +24,14 @@ be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
To deliver data to the project, please read
[The Galmon GNSS Monitoring Project](https://berthub.eu/articles/posts/galmon-project/)
and consult the rules outlined in [the operator
guidelines](https://github.com/berthubert/galmon/blob/master/Operator.md).
guidelines](https://github.com/ahupowerdns/galmon/blob/master/Operator.md).
Highlights
----------
* Support for Septentrio and U-blox.
* Processes raw frames/strings/words from GPS, GLONASS, BeiDou and Galileo
* All-band support (E1, E5a, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
so far.
* All-band support (E1, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
so far, GPS L2C and Galileo E5a pending).
* Calculate ephemeris positions
* Comparison of ephemerides to independent SP3 data to determine SISE
* Globally, locally, worst user location
@ -53,7 +49,7 @@ Highlights
Data is made available as JSON, as a user-friendly website and as a
time-series database. This time-series database is easily mated to the
industry standard Matplotlib/Pandas/Jupyter combination (details
[here](https://github.com/berthubert/galmon/blob/master/influxdb.md).
[here](https://github.com/ahupowerdns/galmon/blob/master/influxdb.md).
There is also tooling to extract raw frames/strings/words from specific
timeframes.
@ -72,8 +68,8 @@ Goals:
Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD.
Build locally (Linux, Debian, Ubuntu)
-------------------------------------
Build locally
-------------
To get started, make sure you have a C++17 compiler (like g++ 8 or higher),
git, protobuf-compiler. Then run 'make ubxtool navdump' to build the
@ -83,8 +79,8 @@ To build everything, including the webserver, try:
```
apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev \
libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev g++ libfmt-dev
git clone https://github.com/berthubert/galmon.git --recursive
libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev
git clone https://github.com/ahupowerdns/galmon.git --recursive
cd galmon
make
```
@ -96,57 +92,29 @@ library installed. If you get an error about 'wslay', do the following, and run
echo WSLAY=-lwslay > Makefile.local
```
Building on OSX
Build in Docker
---------------
With thanks to a contributor from Prague. First make sure you've installed
brew, which you can get [here](https://brew.sh/). Then do:
To build it in Docker:
```
brew install protobuf lzlib zstd h2o eigen
git clone https://github.com/ahupowerdns/galmon.git --recursive
docker build -t galmon --build-arg MAKE_FLAGS=-j2 .
```
And then:
```
git clone https://github.com/berthubert/galmon.git --recursive
cd galmon
make
```
Running in Docker
-----------------
We publish official Docker images for galmon on
[docker hub](https://hub.docker.com/r/berthubert/galmon) for multiple architectures.
To run a container with a shell in there (this will also expose a port so you
can view the UI too and assumes a ublox GPS device too -
you may need to tweak as necessary):
To run a container with a shell in there (this will also expose a port so you can view the UI too and assumes a ublox GPS device too - you may need to tweak as necessary):
```
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 berthubert/galmon
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 galmon
```
Running a daemonized docker container reporting data to a remote server
might look like:
```
docker run -d --restart=always --device=/dev/ttyACM0 --name=galmon berthubert/galmon ubxtool --wait --port /dev/ttyACM0 --gps --galileo --glonass --destination [server] --station [station-id] --owner [owner]
```
To make your docker container update automatically you could use a tool such as
[watchtower](https://containrrr.github.io/watchtower/).
Running
-------
On u-blox:
Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0
--station 1 --stdout --galileo | ./navparse --bind [::1]:10000`
For Septentrio, try: `nc 192.168.1.1 29000 | ./septool --station x --stdout |
./navparse --bind [::1]:10000`, assuming your Septentrio can be reached on
192.168.1.1.1 and you have defined an SBF stream on port 29000. For more
details, please see below.
Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and
you should be in business. ubxtool changes (non-permanently) the
configuration of your u-blox receiver so it emits the required frames for
@ -181,13 +149,9 @@ to stdout, add `--stdout`.
Tooling:
* ubxtool: can configure a u-blox 8/9/10 chipset, parses its output & will
* ubxtool: can configure a u-blox 8 chipset, parses its output & will
convert it into a protbuf stream of GNSS NAV frames + metadata
Adds 64-bit timestamps plus origin information to each message
* septool: ingests the Septentrio binary format (SBF) and converts it to our
protobuf format. Supports same protocol as ubxtool.
* rtcmtool: ingest ntripclient output, decodes RTCM messages and converts
them to our protobuf format
* xtool: if you have another chipset, build something that extracts NAV
frames & metadata. Not done yet.
* navrecv: receives GNSS NAV frames and stores them on disk, split out per
@ -200,11 +164,6 @@ Tooling:
computations on ephemerides.
* grafana dashboard: makes pretty graphs
Per device notes
----------------
The "SparkFun GNSS L1/L5 Breakout - NEO-F10N, SMA" needs '-u1 -b 38400
--wait'.
Linux Systemd
-------------
First make sure 'ubxtool' has been compiled (run: make ubxtool). Then, as
@ -263,47 +222,6 @@ This also works for `navparse` for the pretty website and influx storage, `nc 12
if you have an influxdb running on localhost with a galileo database in there.
The default URL is http://127.0.0.1:29599/
Septentrio specifics
--------------------
Unlike `ubxtool`, our `septool` does not (re)configure your Septentrio
device. Instead, the tool expects Septentrio Binary Format (SBF) on input,
and that this stream includes at least the following messages:
* MeasEpoch
* PVTCartesian
We currently parse and understand:
* GALRawFNAV
* GALRawINAV
Support will be added soon for:
* GPSRawCA
* GPSRawL2C
* GPSRawL5
* GLORawCA
* BDSRaw
* BDSRawB1C
* BDSRawB2a
A typical invocation of `septool` looks like this:
```
nc 192.168.1.1 29000 | ./septool --station x --destination galmon-eu-server.example.com
```
Or to test, try:
```
nc 192.168.1.1 29000 | ./septool --station x --stdout | ./navdump
```
This is assuming that you can reach your Septentrio on 192.168.1.1 and that
you have defined a TCP server stream on port 29000.
Septool will also accept input from a serial port or basically anything that
can provide SBF. Please let us know if our tooling can make your life
easier.
Internals
---------
The transport format consists of repeats of:
@ -328,10 +246,9 @@ Documents
* [GLONASS CDMA](http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf)
not actually relevant for the CDMA aspects, but has appendices on more
precise orbit determinations.
* [GPS](https://www.navcen.uscg.gov/sites/default/files/pdf/gps/IS-GPS-200N.pdf)
* [GPS](https://www.gps.gov/technical/icwg/IS-GPS-200K.pdf)
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
* [U-blox 9 interface specification](https://www.u-blox.com/sites/default/files/u-blox_ZED-F9P_InterfaceDescription_%28UBX-18010854%29.pdf)
* [U-blox 10 interface specification](https://content.u-blox.com/sites/default/files/documents/u-blox-F10-SPG-6.00_InterfaceDescription_UBX-23002975.pdf)
* [U-blox 9 integration manual](https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_%28UBX-18010802%29.pdf)
Data sources
@ -351,39 +268,17 @@ The software can interpret SP3 files, good sources:
to have less of a delay than the ESA ESM series.
* GBU = ultra rapid, still a few days delay, but much more recent.
To get SP3 GBM from GFZ Potsdam for GPS week number 2111:
```
WN=2111
lftp -c "mget ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss/${WN}/gbm*sp3.Z"
gunzip gbm*sp3.Z
```
To feed data, use:
```
./sp3feed --sp3src=gbm --influxdb=galileo gbm*sp3
```
This will populate the sp3 tables in the database. A subsequent run of
`reporter`, while setting the `--sp3src` parameter, will provide SP3
deviation statistics, and fill out the `sp3delta` table in there, which
stores deviations from the SP3 provided position, per sp3src.
Further interesting (ephemeris) data is on http://mgex.igs.org/IGS_MGEX_Products.php
Uncompress and concatenate all downloaded files into 'all.sp3' and run
'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 protocol is proprietary, but search for a file called `RTCM3.2.pdf` or
`104-2013-SC104-STD - Vers. 3.2.docx` and you might find a copy.
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).
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.
@ -397,17 +292,6 @@ $ ntripclient ntrip:CLKA0_DEU1/user:password@navcast.spaceopal.com:2101 | ./rtcm
User and password can be obtained from https://spaceopal.com/navcast/ - the
Galileo operating company.
The IGS also offers excellent streams, but without Galileo. Information is
[here](http://www.igs.org/rts/products). A typical commandline is:
```
$ ntripclient ntrip:IGS01/user:password@products.igs-ip.net:2101 | ./rtcmtool --station x --destination y
```
User and password can be requested through http://www.igs.org/rts/access
An interesting list is here: http://products.igs-ip.net/
There are many other sources of RTCM but currently not many offer the SSR
messages we can use.

View file

@ -1,8 +1,6 @@
#include "beidou.hh"
#include "bits.hh"
#include <iostream>
#include <vector>
#include "navmon.hh"
using namespace std;
@ -31,7 +29,7 @@ static int checkbds(int bits)
return 1;
}
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload)
std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload)
{
// payload consists of 32 bit words where we have to ignore the first 2 bits of every word
@ -58,7 +56,7 @@ std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& paylo
setbitu(buffer, 26+22*(w-1), 22, getbitu(&payload[0], 2 + w*32, 22));
}
return makeVec(buffer, 28);
return std::basic_string<uint8_t>(buffer, 28);
}

View file

@ -4,10 +4,9 @@
#include "bits.hh"
#include <math.h>
#include <stdexcept>
#include <vector>
#include "ephemeris.hh"
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload);
std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload);
int beidouBitconv(int their);
/* Geostationary, so D2, so not to be parsed by this parser:
@ -22,7 +21,7 @@ struct BeidouMessage : GPSLikeEphemeris
{
uint8_t strtype;
std::vector<uint8_t> g_cond;
std::basic_string_view<uint8_t> g_cond;
int bbitu(int bit, int len)
{
return getbitu(&g_cond[0], beidouBitconv(bit), len);
@ -35,7 +34,7 @@ struct BeidouMessage : GPSLikeEphemeris
int fraid{-1}, sow{-1}; // part of every message (thanks!)
int parse(const std::vector<uint8_t>& cond, uint8_t* pageno)
int parse(std::basic_string_view<uint8_t> cond, uint8_t* pageno)
{
g_cond = cond;
if(pageno)
@ -90,7 +89,7 @@ struct BeidouMessage : GPSLikeEphemeris
return {factor * cur, factor * trend};
}
void parse1(const std::vector<uint8_t>& cond)
void parse1(std::basic_string_view<uint8_t> cond)
{
sath1 = bbitu(43,1);
aodc = bbitu(31+13, 5);
@ -125,7 +124,7 @@ struct BeidouMessage : GPSLikeEphemeris
return -1;
}
void parse2(const std::vector<uint8_t>& cond)
void parse2(std::basic_string_view<uint8_t> cond)
{
deltan = bbits(43, 16);
cuc = bbits(67, 18);
@ -152,7 +151,7 @@ struct BeidouMessage : GPSLikeEphemeris
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
void parse3(const std::vector<uint8_t>& cond)
void parse3(std::basic_string_view<uint8_t> cond)
{
t0eLSB = bbitu(43, 15);
i0 = bbits(66, 32);
@ -208,7 +207,7 @@ struct BeidouMessage : GPSLikeEphemeris
} alma;
// 4 is all almanac
int parse4(const std::vector<uint8_t>& cond)
int parse4(std::basic_string_view<uint8_t> cond)
{
alma.sqrtA = bbitu(51, 24);
alma.a1 = bbits(91, 11);
@ -229,8 +228,7 @@ struct BeidouMessage : GPSLikeEphemeris
// 2^-30 2^-50
int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc;
int8_t deltaTLS, deltaTLSF;
uint8_t wnLSF, dn;
int8_t deltaTLS;
// in Beidou the offset is a0utc + SOW * a1utc
std::pair<double, double> getUTCOffset(int tow) const
@ -255,7 +253,7 @@ struct BeidouMessage : GPSLikeEphemeris
}
int parse5(const std::vector<uint8_t>& cond)
int parse5(std::basic_string_view<uint8_t> cond)
{
alma.pageno = bbitu(44, 7);
if(alma.pageno == 9) {
@ -270,9 +268,6 @@ struct BeidouMessage : GPSLikeEphemeris
a0utc = bbits(91, 32);
a1utc = bbits(131, 24);
deltaTLS = bbits(31+12+1+7, 8);
deltaTLSF = bbits(61+6, 8);
wnLSF = bbits(61+6+8, 8);
dn = bbits(151+12, 8);
}
else {
alma.sqrtA = bbitu(51, 24);

View file

@ -87,7 +87,6 @@ covmap_t emitCoverage(const vector<Point>& sats)
double phi = M_PI* latitude / 180;
double longsteps = 1 + 360.0 * cos(phi);
double step = 4*180.0 / longsteps;
// this does sorta equi-distanced measurements
vector<tuple<double, int, int, int, double, double, double, double, double, double,double, double, double>> latvect;
for(double longitude = -180; longitude < 180; longitude += step) { // east - west
Point p;

View file

@ -89,7 +89,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
cerr << "sqrtA = "<< sqrtA << endl;
cerr << "deltan = "<< deltan << endl;
cerr << "t0e = "<< t0e << "( rel "<<(tow - t0e)<<")"<<endl;
cerr << "t0e = "<< t0e << endl;
cerr << "m0 = "<< m0 << " ("<<todeg(m0)<<")"<<endl;
cerr << "e = "<< e << endl;
cerr << "omega = " << omega << " ("<<todeg(omega)<<")"<<endl;
@ -122,7 +122,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
double M = m0 + n * tk;
if(!quiet)
cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk << " = " <<M <<endl;
cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk <<endl;
double E = M;
double newE;
for(int k =0 ; k < 10; ++k) {

File diff suppressed because it is too large Load diff

View file

@ -1,36 +1,16 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
// CLI Library includes
// Order is important for combiner script
#include "Version.hpp"
#include "Macros.hpp"
#include "StringTools.hpp"
#include "Error.hpp"
#include "TypeTools.hpp"
#include "Split.hpp"
#include "ConfigFwd.hpp"
#include "Validators.hpp"
#include "FormatterFwd.hpp"
#include "Option.hpp"
#include "App.hpp"
#include "Config.hpp"
#include "Formatter.hpp"
#include "CLI/Error.hpp"
#include "CLI/TypeTools.hpp"
#include "CLI/StringTools.hpp"
#include "CLI/Split.hpp"
#include "CLI/Ini.hpp"
#include "CLI/Validators.hpp"
#include "CLI/Option.hpp"
#include "CLI/App.hpp"

View file

@ -1,346 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "App.hpp"
#include "ConfigFwd.hpp"
#include "StringTools.hpp"
namespace CLI {
namespace detail {
inline std::string convert_arg_for_ini(const std::string &arg) {
if(arg.empty()) {
return std::string(2, '"');
}
// some specifically supported strings
if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
return arg;
}
// floating point conversion can convert some hex codes, but don't try that here
if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
double val;
if(detail::lexical_cast(arg, val)) {
return arg;
}
}
// just quote a single non numeric character
if(arg.size() == 1) {
return std::string("'") + arg + '\'';
}
// handle hex, binary or octal arguments
if(arg.front() == '0') {
if(arg[1] == 'x') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
})) {
return arg;
}
} else if(arg[1] == 'o') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
return arg;
}
} else if(arg[1] == 'b') {
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
return arg;
}
}
}
if(arg.find_first_of('"') == std::string::npos) {
return std::string("\"") + arg + '"';
} else {
return std::string("'") + arg + '\'';
}
}
/// Comma separated join, adds quotes if needed
inline std::string
ini_join(const std::vector<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') {
std::string joined;
if(args.size() > 1 && arrayStart != '\0') {
joined.push_back(arrayStart);
}
std::size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0) {
joined.push_back(sepChar);
if(isspace(sepChar) == 0) {
joined.push_back(' ');
}
}
joined.append(convert_arg_for_ini(arg));
}
if(args.size() > 1 && arrayEnd != '\0') {
joined.push_back(arrayEnd);
}
return joined;
}
inline std::vector<std::string> generate_parents(const std::string &section, std::string &name) {
std::vector<std::string> parents;
if(detail::to_lower(section) != "default") {
if(section.find('.') != std::string::npos) {
parents = detail::split(section, '.');
} else {
parents = {section};
}
}
if(name.find('.') != std::string::npos) {
std::vector<std::string> plist = detail::split(name, '.');
name = plist.back();
detail::remove_quotes(name);
plist.pop_back();
parents.insert(parents.end(), plist.begin(), plist.end());
}
// clean up quotes on the parents
for(auto &parent : parents) {
detail::remove_quotes(parent);
}
return parents;
}
/// assuming non default segments do a check on the close and open of the segments in a configItem structure
inline void checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection) {
std::string estring;
auto parents = detail::generate_parents(currentSection, estring);
if(!output.empty() && output.back().name == "--") {
std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
while(output.back().parents.size() >= msize) {
output.push_back(output.back());
output.back().parents.pop_back();
}
if(parents.size() > 1) {
std::size_t common = 0;
std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
for(std::size_t ii = 0; ii < mpair; ++ii) {
if(output.back().parents[ii] != parents[ii]) {
break;
}
++common;
}
if(common == mpair) {
output.pop_back();
} else {
while(output.back().parents.size() > common + 1) {
output.push_back(output.back());
output.back().parents.pop_back();
}
}
for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
output.emplace_back();
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
output.back().name = "++";
}
}
} else if(parents.size() > 1) {
for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
output.emplace_back();
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
output.back().name = "++";
}
}
// insert a section end which is just an empty items_buffer
output.emplace_back();
output.back().parents = std::move(parents);
output.back().name = "++";
}
} // namespace detail
inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
std::string line;
std::string section = "default";
std::vector<ConfigItem> output;
bool defaultArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
char aStart = (defaultArray) ? '[' : arrayStart;
char aEnd = (defaultArray) ? ']' : arrayEnd;
char aSep = (defaultArray && arraySeparator == ' ') ? ',' : arraySeparator;
while(getline(input, line)) {
std::vector<std::string> items_buffer;
std::string name;
detail::trim(line);
std::size_t len = line.length();
if(len > 1 && line.front() == '[' && line.back() == ']') {
if(section != "default") {
// insert a section end which is just an empty items_buffer
output.emplace_back();
output.back().parents = detail::generate_parents(section, name);
output.back().name = "--";
}
section = line.substr(1, len - 2);
// deal with double brackets for TOML
if(section.size() > 1 && section.front() == '[' && section.back() == ']') {
section = section.substr(1, section.size() - 2);
}
if(detail::to_lower(section) == "default") {
section = "default";
} else {
detail::checkParentSegments(output, section);
}
continue;
}
if(len == 0) {
continue;
}
// comment lines
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
continue;
}
// Find = in string, split and recombine
auto pos = line.find(valueDelimiter);
if(pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) {
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
} else if(defaultArray && item.find_first_of(aSep) != std::string::npos) {
items_buffer = detail::split_up(item, aSep);
} else if(defaultArray && item.find_first_of(' ') != std::string::npos) {
items_buffer = detail::split_up(item);
} else {
items_buffer = {item};
}
} else {
name = detail::trim_copy(line);
items_buffer = {"true"};
}
if(name.find('.') == std::string::npos) {
detail::remove_quotes(name);
}
// clean up quotes on the items
for(auto &it : items_buffer) {
detail::remove_quotes(it);
}
std::vector<std::string> parents = detail::generate_parents(section, name);
if(!output.empty() && name == output.back().name && parents == output.back().parents) {
output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
} else {
output.emplace_back();
output.back().parents = std::move(parents);
output.back().name = std::move(name);
output.back().inputs = std::move(items_buffer);
}
}
if(section != "default") {
// insert a section end which is just an empty items_buffer
std::string ename;
output.emplace_back();
output.back().parents = detail::generate_parents(section, ename);
output.back().name = "--";
while(output.back().parents.size() > 1) {
output.push_back(output.back());
output.back().parents.pop_back();
}
}
return output;
}
inline std::string
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
std::stringstream out;
std::string commentLead;
commentLead.push_back(commentChar);
commentLead.push_back(' ');
std::vector<std::string> groups = app->get_groups();
bool defaultUsed = false;
groups.insert(groups.begin(), std::string("Options"));
if(write_description) {
out << commentLead << app->get_description() << '\n';
}
for(auto &group : groups) {
if(group == "Options" || group.empty()) {
if(defaultUsed) {
continue;
}
defaultUsed = true;
}
if(write_description && group != "Options" && !group.empty()) {
out << '\n' << commentLead << group << " Options\n";
}
for(const Option *opt : app->get_options({})) {
// Only process option with a long-name and configurable
if(!opt->get_lnames().empty() && opt->get_configurable()) {
if(opt->get_group() != group) {
if(!(group == "Options" && opt->get_group().empty())) {
continue;
}
}
std::string name = prefix + opt->get_lnames()[0];
std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd);
if(value.empty() && default_also) {
if(!opt->get_default_str().empty()) {
value = detail::convert_arg_for_ini(opt->get_default_str());
} else if(opt->get_expected_min() == 0) {
value = "false";
}
}
if(!value.empty()) {
if(write_description && opt->has_description()) {
out << '\n';
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
}
out << name << valueDelimiter << value << '\n';
}
}
}
}
auto subcommands = app->get_subcommands({});
for(const App *subcom : subcommands) {
if(subcom->get_name().empty()) {
if(write_description && !subcom->get_group().empty()) {
out << '\n' << commentLead << subcom->get_group() << " Options\n";
}
out << to_config(subcom, default_also, write_description, prefix);
}
}
for(const App *subcom : subcommands) {
if(!subcom->get_name().empty()) {
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
if(!prefix.empty() || app->get_parent() == nullptr) {
out << '[' << prefix << subcom->get_name() << "]\n";
} else {
std::string subname = app->get_name() + "." + subcom->get_name();
auto p = app->get_parent();
while(p->get_parent() != nullptr) {
subname = p->get_name() + "." + subname;
p = p->get_parent();
}
out << '[' << subname << "]\n";
}
out << to_config(subcom, default_also, write_description, "");
} else {
out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
}
}
}
return out.str();
}
} // namespace CLI

View file

@ -1,131 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "Error.hpp"
#include "StringTools.hpp"
namespace CLI {
class App;
/// Holds values to load into Options
struct ConfigItem {
/// This is the list of parents
std::vector<std::string> parents{};
/// This is the name
std::string name{};
/// Listing of inputs
std::vector<std::string> inputs{};
/// The list of parents and name joined by "."
std::string fullname() const {
std::vector<std::string> tmp = parents;
tmp.emplace_back(name);
return detail::join(tmp, ".");
}
};
/// This class provides a converter for configuration files.
class Config {
protected:
std::vector<ConfigItem> items{};
public:
/// Convert an app into a configuration
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
/// Get a flag value
virtual std::string to_flag(const ConfigItem &item) const {
if(item.inputs.size() == 1) {
return item.inputs.at(0);
}
throw ConversionError::TooManyInputsFlag(item.fullname());
}
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
std::vector<ConfigItem> from_file(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError::Missing(name);
return from_config(input);
}
/// Virtual destructor
virtual ~Config() = default;
};
/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML
class ConfigBase : public Config {
protected:
/// the character used for comments
char commentChar = ';';
/// the character used to start an array '\0' is a default to not use
char arrayStart = '\0';
/// the character used to end an array '\0' is a default to not use
char arrayEnd = '\0';
/// the character used to separate elements in an array
char arraySeparator = ' ';
/// the character used separate the name from the value
char valueDelimiter = '=';
public:
std::string
to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
std::vector<ConfigItem> from_config(std::istream &input) const override;
/// Specify the configuration for comment characters
ConfigBase *comment(char cchar) {
commentChar = cchar;
return this;
}
/// Specify the start and end characters for an array
ConfigBase *arrayBounds(char aStart, char aEnd) {
arrayStart = aStart;
arrayEnd = aEnd;
return this;
}
/// Specify the delimiter character for an array
ConfigBase *arrayDelimiter(char aSep) {
arraySeparator = aSep;
return this;
}
/// Specify the delimiter between a name and value
ConfigBase *valueSeparator(char vSep) {
valueDelimiter = vSep;
return this;
}
};
/// the default Config is the INI file format
using ConfigINI = ConfigBase;
/// ConfigTOML generates a TOML compliant output
class ConfigTOML : public ConfigINI {
public:
ConfigTOML() {
commentChar = '#';
arrayStart = '[';
arrayEnd = ']';
arraySeparator = ',';
valueDelimiter = '=';
}
};
} // namespace CLI

View file

@ -1,38 +1,14 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <exception>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
// CLI library includes
#include "StringTools.hpp"
namespace CLI {
// Use one of these on all error classes.
// These are temporary and are undef'd at the end of this file.
#define CLI11_ERROR_DEF(parent, name) \
protected: \
name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \
name(std::string ename, std::string msg, ExitCodes exit_code) \
: parent(std::move(ename), std::move(msg), exit_code) {} \
\
public: \
name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
// This is added after the one above if a class is used directly and builds its own message
#define CLI11_ERROR_SIMPLE(name) \
explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
/// int values from e.get_error_code().
enum class ExitCodes {
@ -40,19 +16,18 @@ enum class ExitCodes {
IncorrectConstruction = 100,
BadNameString,
OptionAlreadyAdded,
FileError,
ConversionError,
ValidationError,
RequiredError,
RequiresError,
ExcludesError,
ExtrasError,
ConfigError,
InvalidError,
HorribleError,
File,
Conversion,
Validation,
Required,
Requires,
Excludes,
Extras,
ExtrasINI,
Invalid,
Horrible,
OptionNotFound,
ArgumentMismatch,
BaseClass = 127
BaseClass = 255
};
// Error definitions
@ -64,277 +39,127 @@ enum class ExitCodes {
/// @{
/// All errors derive from this one
class Error : public std::runtime_error {
int actual_exit_code;
std::string error_name{"Error"};
public:
int get_exit_code() const { return actual_exit_code; }
std::string get_name() const { return error_name; }
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
struct Error : public std::runtime_error {
int exit_code;
bool print_help;
int get_exit_code() const { return exit_code; }
Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
: runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)), print_help(print_help) {}
Error(std::string parent,
std::string name,
int exit_code = static_cast<int>(ExitCodes::BaseClass),
bool print_help = true)
: runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
};
// Note: Using Error::Error constructors does not work on GCC 4.7
/// Construction errors (not in parsing)
class ConstructionError : public Error {
CLI11_ERROR_DEF(Error, ConstructionError)
struct ConstructionError : public Error {
// Using Error::Error constructors seem to not work on GCC 4.7
ConstructionError(std::string parent,
std::string name,
ExitCodes exit_code = ExitCodes::BaseClass,
bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
};
/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
class IncorrectConstruction : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
CLI11_ERROR_SIMPLE(IncorrectConstruction)
static IncorrectConstruction PositionalFlag(std::string name) {
return IncorrectConstruction(name + ": Flags cannot be positional");
}
static IncorrectConstruction Set0Opt(std::string name) {
return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
}
static IncorrectConstruction SetFlag(std::string name) {
return IncorrectConstruction(name + ": Cannot set an expected number for flags");
}
static IncorrectConstruction ChangeNotVector(std::string name) {
return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
}
static IncorrectConstruction AfterMultiOpt(std::string name) {
return IncorrectConstruction(
name + ": You can't change expected arguments after you've changed the multi option policy!");
}
static IncorrectConstruction MissingOption(std::string name) {
return IncorrectConstruction("Option " + name + " is not defined");
}
static IncorrectConstruction MultiOptionPolicy(std::string name) {
return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
}
struct IncorrectConstruction : public ConstructionError {
IncorrectConstruction(std::string name)
: ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {}
};
/// Thrown on construction of a bad name
class BadNameString : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, BadNameString)
CLI11_ERROR_SIMPLE(BadNameString)
static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
static BadNameString DashesOnly(std::string name) {
return BadNameString("Must have a name, not just dashes: " + name);
}
static BadNameString MultiPositionalNames(std::string name) {
return BadNameString("Only one positional name allowed, remove: " + name);
}
struct BadNameString : public ConstructionError {
BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {}
};
/// Thrown when an option already exists
class OptionAlreadyAdded : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
explicit OptionAlreadyAdded(std::string name)
: OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
static OptionAlreadyAdded Requires(std::string name, std::string other) {
return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
}
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
}
struct OptionAlreadyAdded : public ConstructionError {
OptionAlreadyAdded(std::string name)
: ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {}
};
// Parsing errors
/// Anything that can error in Parse
class ParseError : public Error {
CLI11_ERROR_DEF(Error, ParseError)
struct ParseError : public Error {
ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
: Error(parent, name, exit_code, print_help) {}
};
// Not really "errors"
/// This is a successful completion on parsing, supposed to exit
class Success : public ParseError {
CLI11_ERROR_DEF(ParseError, Success)
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
struct Success : public ParseError {
Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {}
};
/// -h or --help on command line
class CallForHelp : public ParseError {
CLI11_ERROR_DEF(ParseError, CallForHelp)
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Usually something like --help-all on command line
class CallForAllHelp : public ParseError {
CLI11_ERROR_DEF(ParseError, CallForAllHelp)
CallForAllHelp()
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
class RuntimeError : public ParseError {
CLI11_ERROR_DEF(ParseError, RuntimeError)
explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
struct CallForHelp : public ParseError {
CallForHelp()
: ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Thrown when parsing an INI file and it is missing
class FileError : public ParseError {
CLI11_ERROR_DEF(ParseError, FileError)
CLI11_ERROR_SIMPLE(FileError)
static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
struct FileError : public ParseError {
FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
};
/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
class ConversionError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConversionError)
CLI11_ERROR_SIMPLE(ConversionError)
ConversionError(std::string member, std::string name)
: ConversionError("The value " + member + " is not an allowed value for " + name) {}
ConversionError(std::string name, std::vector<std::string> results)
: ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
static ConversionError TooManyInputsFlag(std::string name) {
return ConversionError(name + ": too many inputs for a flag");
}
static ConversionError TrueFalse(std::string name) {
return ConversionError(name + ": Should be true/false or a number");
}
/// Thrown when conversion call back fails, such as when an int fails to coerse to a string
struct ConversionError : public ParseError {
ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {}
};
/// Thrown when validation of results fails
class ValidationError : public ParseError {
CLI11_ERROR_DEF(ParseError, ValidationError)
CLI11_ERROR_SIMPLE(ValidationError)
explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
struct ValidationError : public ParseError {
ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {}
};
/// Thrown when a required option is missing
class RequiredError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiredError)
explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
static RequiredError Subcommand(std::size_t min_subcom) {
if(min_subcom == 1) {
return RequiredError("A subcommand");
}
return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
ExitCodes::RequiredError);
}
static RequiredError
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
if((min_option == 1) && (max_option == 1) && (used == 0))
return RequiredError("Exactly 1 option from [" + option_list + "]");
if((min_option == 1) && (max_option == 1) && (used > 1)) {
return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
" were given",
ExitCodes::RequiredError);
}
if((min_option == 1) && (used == 0))
return RequiredError("At least 1 option from [" + option_list + "]");
if(used < min_option) {
return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
std::to_string(used) + "were given from [" + option_list + "]",
ExitCodes::RequiredError);
}
if(max_option == 1)
return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
ExitCodes::RequiredError);
return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
std::to_string(used) + "were given from [" + option_list + "]",
ExitCodes::RequiredError);
}
};
/// Thrown when the wrong number of arguments has been received
class ArgumentMismatch : public ParseError {
CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
CLI11_ERROR_SIMPLE(ArgumentMismatch)
ArgumentMismatch(std::string name, int expected, std::size_t received)
: ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
", got " + std::to_string(received))
: ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
", got " + std::to_string(received)),
ExitCodes::ArgumentMismatch) {}
static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
}
static ArgumentMismatch FlagOverride(std::string name) {
return ArgumentMismatch(name + " was given a disallowed flag override");
}
struct RequiredError : public ParseError {
RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {}
};
/// Thrown when a requires option is missing
class RequiresError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiresError)
RequiresError(std::string curname, std::string subname)
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
struct RequiresError : public ParseError {
RequiresError(std::string name, std::string subname)
: ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {}
};
/// Thrown when an excludes option is present
class ExcludesError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExcludesError)
ExcludesError(std::string curname, std::string subname)
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
/// Thrown when a exludes option is present
struct ExcludesError : public ParseError {
ExcludesError(std::string name, std::string subname)
: ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {}
};
/// Thrown when too many positionals or options are found
class ExtrasError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExtrasError)
explicit ExtrasError(std::vector<std::string> args)
: ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
: "The following argument was not expected: ") +
detail::rjoin(args, " "),
ExitCodes::ExtrasError) {}
ExtrasError(const std::string &name, std::vector<std::string> args)
: ExtrasError(name,
(args.size() > 1 ? "The following arguments were not expected: "
: "The following argument was not expected: ") +
detail::rjoin(args, " "),
ExitCodes::ExtrasError) {}
struct ExtrasError : public ParseError {
ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {}
};
/// Thrown when extra values are found in an INI file
class ConfigError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConfigError)
CLI11_ERROR_SIMPLE(ConfigError)
static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
static ConfigError NotConfigurable(std::string item) {
return ConfigError(item + ": This option is not allowed in a configuration file");
}
struct ExtrasINIError : public ParseError {
ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {}
};
/// Thrown when validation fails before parsing
class InvalidError : public ParseError {
CLI11_ERROR_DEF(ParseError, InvalidError)
explicit InvalidError(std::string name)
: InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
}
struct InvalidError : public ParseError {
InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {}
};
/// This is just a safety check to verify selection and parsing match - you should not ever see it
/// Strings are directly added to this error, but again, it should never be seen.
class HorribleError : public ParseError {
CLI11_ERROR_DEF(ParseError, HorribleError)
CLI11_ERROR_SIMPLE(HorribleError)
/// This is just a safety check to verify selection and parsing match
struct HorribleError : public ParseError {
HorribleError(std::string name)
: ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {}
};
// After parsing
/// Thrown when counting a non-existent option
class OptionNotFound : public Error {
CLI11_ERROR_DEF(Error, OptionNotFound)
explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
struct OptionNotFound : public Error {
OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {}
};
#undef CLI11_ERROR_DEF
#undef CLI11_ERROR_SIMPLE
/// @}
} // namespace CLI
} // namespace CLI

View file

@ -1,281 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <string>
#include <vector>
#include "App.hpp"
#include "FormatterFwd.hpp"
namespace CLI {
inline std::string
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
std::stringstream out;
out << "\n" << group << ":\n";
for(const Option *opt : opts) {
out << make_option(opt, is_positional);
}
return out.str();
}
inline std::string Formatter::make_positionals(const App *app) const {
std::vector<const Option *> opts =
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
if(opts.empty())
return std::string();
return make_group(get_label("Positionals"), true, opts);
}
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
std::stringstream out;
std::vector<std::string> groups = app->get_groups();
// Options
for(const std::string &group : groups) {
std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
return opt->get_group() == group // Must be in the right group
&& opt->nonpositional() // Must not be a positional
&& (mode != AppFormatMode::Sub // If mode is Sub, then
|| (app->get_help_ptr() != opt // Ignore help pointer
&& app->get_help_all_ptr() != opt)); // Ignore help all pointer
});
if(!group.empty() && !opts.empty()) {
out << make_group(group, false, opts);
if(group != groups.back())
out << "\n";
}
}
return out.str();
}
inline std::string Formatter::make_description(const App *app) const {
std::string desc = app->get_description();
auto min_options = app->get_require_option_min();
auto max_options = app->get_require_option_max();
if(app->get_required()) {
desc += " REQUIRED ";
}
if((max_options == min_options) && (min_options > 0)) {
if(min_options == 1) {
desc += " \n[Exactly 1 of the following options is required]";
} else {
desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
}
} else if(max_options > 0) {
if(min_options > 0) {
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
" of the follow options are required]";
} else {
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
}
} else if(min_options > 0) {
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
}
return (!desc.empty()) ? desc + "\n" : std::string{};
}
inline std::string Formatter::make_usage(const App *app, std::string name) const {
std::stringstream out;
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
std::vector<std::string> groups = app->get_groups();
// Print an Options badge if any options exist
std::vector<const Option *> non_pos_options =
app->get_options([](const Option *opt) { return opt->nonpositional(); });
if(!non_pos_options.empty())
out << " [" << get_label("OPTIONS") << "]";
// Positionals need to be listed here
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
// Print out positionals if any are left
if(!positionals.empty()) {
// Convert to help names
std::vector<std::string> positional_names(positionals.size());
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
return make_option_usage(opt);
});
out << " " << detail::join(positional_names, " ");
}
// Add a marker if subcommands are expected or optional
if(!app->get_subcommands(
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
.empty()) {
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
: "SUBCOMMANDS")
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
}
out << std::endl;
return out.str();
}
inline std::string Formatter::make_footer(const App *app) const {
std::string footer = app->get_footer();
if(footer.empty()) {
return std::string{};
}
return footer + "\n";
}
inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
// This immediately forwards to the make_expanded method. This is done this way so that subcommands can
// have overridden formatters
if(mode == AppFormatMode::Sub)
return make_expanded(app);
std::stringstream out;
if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
if(app->get_group() != "Subcommands") {
out << app->get_group() << ':';
}
}
out << make_description(app);
out << make_usage(app, name);
out << make_positionals(app);
out << make_groups(app, mode);
out << make_subcommands(app, mode);
out << '\n' << make_footer(app);
return out.str();
}
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
std::stringstream out;
std::vector<const App *> subcommands = app->get_subcommands({});
// Make a list in definition order of the groups seen
std::vector<std::string> subcmd_groups_seen;
for(const App *com : subcommands) {
if(com->get_name().empty()) {
if(!com->get_group().empty()) {
out << make_expanded(com);
}
continue;
}
std::string group_key = com->get_group();
if(!group_key.empty() &&
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
return detail::to_lower(a) == detail::to_lower(group_key);
}) == subcmd_groups_seen.end())
subcmd_groups_seen.push_back(group_key);
}
// For each group, filter out and print subcommands
for(const std::string &group : subcmd_groups_seen) {
out << "\n" << group << ":\n";
std::vector<const App *> subcommands_group = app->get_subcommands(
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
for(const App *new_com : subcommands_group) {
if(new_com->get_name().empty())
continue;
if(mode != AppFormatMode::All) {
out << make_subcommand(new_com);
} else {
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
out << "\n";
}
}
}
return out.str();
}
inline std::string Formatter::make_subcommand(const App *sub) const {
std::stringstream out;
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
return out.str();
}
inline std::string Formatter::make_expanded(const App *sub) const {
std::stringstream out;
out << sub->get_display_name() << "\n";
out << make_description(sub);
out << make_positionals(sub);
out << make_groups(sub, AppFormatMode::Sub);
out << make_subcommands(sub, AppFormatMode::Sub);
// Drop blank spaces
std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n'
// Indent all but the first line (the name)
return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
}
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
if(is_positional)
return opt->get_name(true, false);
return opt->get_name(false, true);
}
inline std::string Formatter::make_option_opts(const Option *opt) const {
std::stringstream out;
if(opt->get_type_size() != 0) {
if(!opt->get_type_name().empty())
out << " " << get_label(opt->get_type_name());
if(!opt->get_default_str().empty())
out << "=" << opt->get_default_str();
if(opt->get_expected_max() == detail::expected_max_vector_size)
out << " ...";
else if(opt->get_expected_min() > 1)
out << " x " << opt->get_expected();
if(opt->get_required())
out << " " << get_label("REQUIRED");
}
if(!opt->get_envname().empty())
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
if(!opt->get_needs().empty()) {
out << " " << get_label("Needs") << ":";
for(const Option *op : opt->get_needs())
out << " " << op->get_name();
}
if(!opt->get_excludes().empty()) {
out << " " << get_label("Excludes") << ":";
for(const Option *op : opt->get_excludes())
out << " " << op->get_name();
}
return out.str();
}
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
inline std::string Formatter::make_option_usage(const Option *opt) const {
// Note that these are positionals usages
std::stringstream out;
out << make_option_name(opt, true);
if(opt->get_expected_max() >= detail::expected_max_vector_size)
out << "...";
else if(opt->get_expected_max() > 1)
out << "(" << opt->get_expected() << "x)";
return opt->get_required() ? out.str() : "[" + out.str() + "]";
}
} // namespace CLI

View file

@ -1,180 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "StringTools.hpp"
namespace CLI {
class Option;
class App;
/// This enum signifies the type of help requested
///
/// This is passed in by App; all user classes must accept this as
/// the second argument.
enum class AppFormatMode {
Normal, ///< The normal, detailed help
All, ///< A fully expanded help
Sub, ///< Used when printed as part of expanded subcommand
};
/// This is the minimum requirements to run a formatter.
///
/// A user can subclass this is if they do not care at all
/// about the structure in CLI::Formatter.
class FormatterBase {
protected:
/// @name Options
///@{
/// The width of the first column
std::size_t column_width_{30};
/// @brief The required help printout labels (user changeable)
/// Values are Needs, Excludes, etc.
std::map<std::string, std::string> labels_{};
///@}
/// @name Basic
///@{
public:
FormatterBase() = default;
FormatterBase(const FormatterBase &) = default;
FormatterBase(FormatterBase &&) = default;
/// Adding a destructor in this form to work around bug in GCC 4.7
virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
/// This is the key method that puts together help
virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
///@}
/// @name Setters
///@{
/// Set the "REQUIRED" label
void label(std::string key, std::string val) { labels_[key] = val; }
/// Set the column width
void column_width(std::size_t val) { column_width_ = val; }
///@}
/// @name Getters
///@{
/// Get the current value of a name (REQUIRED, etc.)
std::string get_label(std::string key) const {
if(labels_.find(key) == labels_.end())
return key;
else
return labels_.at(key);
}
/// Get the current column width
std::size_t get_column_width() const { return column_width_; }
///@}
};
/// This is a specialty override for lambda functions
class FormatterLambda final : public FormatterBase {
using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>;
/// The lambda to hold and run
funct_t lambda_;
public:
/// Create a FormatterLambda with a lambda function
explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
/// Adding a destructor (mostly to make GCC 4.7 happy)
~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)
/// This will simply call the lambda function
std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
return lambda_(app, name, mode);
}
};
/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few
/// overridable methods, to be highly customizable with minimal effort.
class Formatter : public FormatterBase {
public:
Formatter() = default;
Formatter(const Formatter &) = default;
Formatter(Formatter &&) = default;
/// @name Overridables
///@{
/// This prints out a group of options with title
///
virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
/// This prints out just the positionals "group"
virtual std::string make_positionals(const App *app) const;
/// This prints out all the groups of options
std::string make_groups(const App *app, AppFormatMode mode) const;
/// This prints out all the subcommands
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
/// This prints out a subcommand
virtual std::string make_subcommand(const App *sub) const;
/// This prints out a subcommand in help-all
virtual std::string make_expanded(const App *sub) const;
/// This prints out all the groups of options
virtual std::string make_footer(const App *app) const;
/// This displays the description line
virtual std::string make_description(const App *app) const;
/// This displays the usage line
virtual std::string make_usage(const App *app, std::string name) const;
/// This puts everything together
std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override;
///@}
/// @name Options
///@{
/// This prints out an option help line, either positional or optional form
virtual std::string make_option(const Option *opt, bool is_positional) const {
std::stringstream out;
detail::format_help(
out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
return out.str();
}
/// @brief This is the name part of an option, Default: left column
virtual std::string make_option_name(const Option *, bool) const;
/// @brief This is the options part of the name, Default: combined into left column
virtual std::string make_option_opts(const Option *) const;
/// @brief This is the description. Default: Right column, on new line if left column too large
virtual std::string make_option_desc(const Option *) const;
/// @brief This is used to print the name on the USAGE line
virtual std::string make_option_usage(const Option *opt) const;
///@}
};
} // namespace CLI

115
ext/CLI11/CLI/Ini.hpp Normal file
View file

@ -0,0 +1,115 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "CLI/StringTools.hpp"
namespace CLI {
namespace detail {
inline std::string inijoin(std::vector<std::string> args) {
std::ostringstream s;
size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0)
s << " ";
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); });
if(it == arg.end())
s << arg;
else if(arg.find(R"(")") == std::string::npos)
s << R"(")" << arg << R"(")";
else
s << R"(')" << arg << R"(')";
}
return s.str();
}
struct ini_ret_t {
/// This is the full name with dots
std::string fullname;
/// Listing of inputs
std::vector<std::string> inputs;
/// Current parent level
size_t level = 0;
/// Return parent or empty string, based on level
///
/// Level 0, a.b.c would return a
/// Level 1, a.b.c could return b
std::string parent() const {
std::vector<std::string> plist = detail::split(fullname, '.');
if(plist.size() > (level + 1))
return plist[level];
else
return "";
}
/// Return name
std::string name() const {
std::vector<std::string> plist = detail::split(fullname, '.');
return plist.at(plist.size() - 1);
}
};
/// Internal parsing function
inline std::vector<ini_ret_t> parse_ini(std::istream &input) {
std::string name, line;
std::string section = "default";
std::vector<ini_ret_t> output;
while(getline(input, line)) {
std::vector<std::string> items;
detail::trim(line);
size_t len = line.length();
if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
section = line.substr(1, len - 2);
} else if(len > 0 && line[0] != ';') {
output.emplace_back();
ini_ret_t &out = output.back();
// Find = in string, split and recombine
auto pos = line.find("=");
if(pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
items = detail::split_up(item);
} else {
name = detail::trim_copy(line);
items = {"ON"};
}
if(detail::to_lower(section) == "default")
out.fullname = name;
else
out.fullname = section + "." + name;
out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
}
}
return output;
}
/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure
inline std::vector<ini_ret_t> parse_ini(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError(name);
return parse_ini(input);
}
} // namespace detail
} // namespace CLI

View file

@ -1,44 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// [CLI11:verbatim]
// The following version macro is very similar to the one in PyBind11
#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
#if __cplusplus >= 201402L
#define CLI11_CPP14
#if __cplusplus >= 201703L
#define CLI11_CPP17
#if __cplusplus > 201703L
#define CLI11_CPP20
#endif
#endif
#endif
#elif defined(_MSC_VER) && __cplusplus == 199711L
// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
#if _MSVC_LANG >= 201402L
#define CLI11_CPP14
#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
#define CLI11_CPP17
#if __MSVC_LANG > 201703L && _MSC_VER >= 1910
#define CLI11_CPP20
#endif
#endif
#endif
#endif
#if defined(CLI11_CPP14)
#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
#elif defined(_MSC_VER)
#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
#else
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
#endif
// [CLI11:verbatim]

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,14 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "Error.hpp"
#include "StringTools.hpp"
#include "CLI/Error.hpp"
#include "CLI/StringTools.hpp"
namespace CLI {
namespace detail {
@ -23,14 +19,14 @@ inline bool split_short(const std::string &current, std::string &name, std::stri
name = current.substr(1, 1);
rest = current.substr(2);
return true;
}
return false;
} else
return false;
}
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
inline bool split_long(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
auto loc = current.find_first_of('=');
auto loc = current.find("=");
if(loc != std::string::npos) {
name = current.substr(2, loc - 2);
value = current.substr(loc + 1);
@ -39,62 +35,19 @@ inline bool split_long(const std::string &current, std::string &name, std::strin
value = "";
}
return true;
}
return false;
}
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
auto loc = current.find_first_of(':');
if(loc != std::string::npos) {
name = current.substr(1, loc - 1);
value = current.substr(loc + 1);
} else {
name = current.substr(1);
value = "";
}
return true;
}
return false;
} else
return false;
}
// Splits a string into multiple long and short names
inline std::vector<std::string> split_names(std::string current) {
std::vector<std::string> output;
std::size_t val;
size_t val;
while((val = current.find(",")) != std::string::npos) {
output.push_back(trim_copy(current.substr(0, val)));
output.push_back(current.substr(0, val));
current = current.substr(val + 1);
}
output.push_back(trim_copy(current));
return output;
}
/// extract default flag values either {def} or starting with a !
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
std::vector<std::string> flags = split_names(str);
flags.erase(std::remove_if(flags.begin(),
flags.end(),
[](const std::string &name) {
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
(name.back() == '}')) ||
(name[0] == '!'))));
}),
flags.end());
std::vector<std::pair<std::string, std::string>> output;
output.reserve(flags.size());
for(auto &flag : flags) {
auto def_start = flag.find_first_of('{');
std::string defval = "false";
if((def_start != std::string::npos) && (flag.back() == '}')) {
defval = flag.substr(def_start + 1);
defval.pop_back();
flag.erase(def_start, std::string::npos);
}
flag.erase(0, flag.find_first_not_of("-!"));
output.emplace_back(flag, defval);
}
output.push_back(current);
return output;
}
@ -107,25 +60,24 @@ get_names(const std::vector<std::string> &input) {
std::string pos_name;
for(std::string name : input) {
if(name.length() == 0) {
if(name.length() == 0)
continue;
}
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
if(name.length() == 2 && valid_first_char(name[1]))
short_names.emplace_back(1, name[1]);
else
throw BadNameString::OneCharName(name);
throw BadNameString("Invalid one char name: " + name);
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
name = name.substr(2);
if(valid_name_string(name))
long_names.push_back(name);
else
throw BadNameString::BadLongName(name);
throw BadNameString("Bad long name: " + name);
} else if(name == "-" || name == "--") {
throw BadNameString::DashesOnly(name);
throw BadNameString("Must have a name, not just dashes");
} else {
if(pos_name.length() > 0)
throw BadNameString::MultiPositionalNames(name);
throw BadNameString("Only one positional name allowed, remove: " + name);
pos_name = name;
}
}
@ -134,5 +86,5 @@ get_names(const std::vector<std::string> &input) {
short_names, long_names, pos_name);
}
} // namespace detail
} // namespace CLI
} // namespace detail
} // namespace CLI

View file

@ -1,50 +1,26 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <iomanip>
#include <locale>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>
namespace CLI {
/// Include the items in this namespace to get free conversion of enums to/from streams.
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
namespace enums {
/// output streaming for enumerations
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
std::ostream &operator<<(std::ostream &in, const T &item) {
// make sure this is out of the detail namespace otherwise it won't be found when needed
return in << static_cast<typename std::underlying_type<T>::type>(item);
}
} // namespace enums
/// Export to CLI namespace
using enums::operator<<;
namespace detail {
/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
/// produce overflow for some expected uses
constexpr int expected_max_vector_size{1 << 29};
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
/// Split a string by a delim
inline std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
// Check to see if empty string, give consistent result
if(s.empty()) {
elems.emplace_back();
} else {
// Check to see if emtpy string, give consistent result
if(s == "")
elems.emplace_back("");
else {
std::stringstream ss;
ss.str(s);
std::string item;
@ -58,28 +34,11 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
/// Simple function to join a string
template <typename T> std::string join(const T &v, std::string delim = ",") {
std::ostringstream s;
auto beg = std::begin(v);
auto end = std::end(v);
if(beg != end)
s << *beg++;
while(beg != end) {
s << delim << *beg++;
}
return s.str();
}
/// Simple function to join a string from processed elements
template <typename T,
typename Callable,
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
std::string join(const T &v, Callable func, std::string delim = ",") {
std::ostringstream s;
auto beg = std::begin(v);
auto end = std::end(v);
if(beg != end)
s << func(*beg++);
while(beg != end) {
s << delim << func(*beg++);
size_t start = 0;
for(const auto &i : v) {
if(start++ > 0)
s << delim;
s << i;
}
return s.str();
}
@ -87,7 +46,7 @@ std::string join(const T &v, Callable func, std::string delim = ",") {
/// Join a string in reverse order
template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
std::ostringstream s;
for(std::size_t start = 0; start < v.size(); start++) {
for(size_t start = 0; start < v.size(); start++) {
if(start > 0)
s << delim;
s << v[v.size() - start - 1];
@ -138,47 +97,30 @@ inline std::string trim_copy(const std::string &str) {
return trim(s);
}
/// remove quotes at the front and back of a string either '"' or '\''
inline std::string &remove_quotes(std::string &str) {
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
if(str.front() == str.back()) {
str.pop_back();
str.erase(str.begin(), str.begin() + 1);
}
}
return str;
}
/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
inline std::string trim_copy(const std::string &str, const std::string &filter) {
std::string s = str;
return trim(s, filter);
}
/// Print a two part "help" string
inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) {
inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) {
name = " " + name;
out << std::setw(static_cast<int>(wid)) << std::left << name;
if(!description.empty()) {
if(description != "") {
if(name.length() >= wid)
out << "\n" << std::setw(static_cast<int>(wid)) << "";
for(const char c : description) {
out.put(c);
if(c == '\n') {
out << std::setw(static_cast<int>(wid)) << "";
}
}
out << std::endl << std::setw(static_cast<int>(wid)) << "";
out << description;
}
out << "\n";
return out;
out << std::endl;
}
/// Verify the first character of an option
template <typename T> bool valid_first_char(T c) {
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';
}
template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; }
/// Verify following characters of an option
template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; }
template <typename T> bool valid_later_char(T c) {
return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-';
}
/// Verify an option name
inline bool valid_name_string(const std::string &str) {
@ -190,11 +132,6 @@ inline bool valid_name_string(const std::string &str) {
return true;
}
/// Verify that str consists of letters only
inline bool isalpha(const std::string &str) {
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
}
/// Return a lower case version of a string
inline std::string to_lower(std::string str) {
std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
@ -203,105 +140,18 @@ inline std::string to_lower(std::string str) {
return str;
}
/// remove underscores from a string
inline std::string remove_underscore(std::string str) {
str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
return str;
}
/// Find and replace a substring with another substring
inline std::string find_and_replace(std::string str, std::string from, std::string to) {
std::size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
}
/// check if the flag definitions has possible false flags
inline bool has_default_flag_values(const std::string &flags) {
return (flags.find_first_of("{!") != std::string::npos);
}
inline void remove_default_flag_values(std::string &flags) {
auto loc = flags.find_first_of('{');
while(loc != std::string::npos) {
auto finish = flags.find_first_of("},", loc + 1);
if((finish != std::string::npos) && (flags[finish] == '}')) {
flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
}
loc = flags.find_first_of('{', loc + 1);
}
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
}
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
inline std::ptrdiff_t find_member(std::string name,
const std::vector<std::string> names,
bool ignore_case = false,
bool ignore_underscore = false) {
auto it = std::end(names);
if(ignore_case) {
if(ignore_underscore) {
name = detail::to_lower(detail::remove_underscore(name));
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(detail::remove_underscore(local_name)) == name;
});
} else {
name = detail::to_lower(name);
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::to_lower(local_name) == name;
});
}
} else if(ignore_underscore) {
name = detail::remove_underscore(name);
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
return detail::remove_underscore(local_name) == name;
});
} else {
it = std::find(std::begin(names), std::end(names), name);
}
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
}
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
/// trigger and returns the position in the string to search for the next trigger string
template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
std::size_t start_pos = 0;
while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
start_pos = modify(str, start_pos);
}
return str;
}
/// Split a string '"one two" "three"' into 'one two', 'three'
/// Quote characters can be ` ' or "
inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
inline std::vector<std::string> split_up(std::string str) {
const std::string delims("\'\"`");
auto find_ws = [delimiter](char ch) {
return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
};
std::vector<char> delims = {'\'', '\"'};
auto find_ws = [](char ch) { return std::isspace<char>(ch, std::locale()); };
trim(str);
std::vector<std::string> output;
bool embeddedQuote = false;
char keyChar = ' ';
while(!str.empty()) {
if(delims.find_first_of(str[0]) != std::string::npos) {
keyChar = str[0];
auto end = str.find_first_of(keyChar, 1);
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
end = str.find_first_of(keyChar, end + 1);
embeddedQuote = true;
}
if(str[0] == '\'') {
auto end = str.find('\'', 1);
if(end != std::string::npos) {
output.push_back(str.substr(1, end - 1));
str = str.substr(end + 1);
@ -309,71 +159,32 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0')
output.push_back(str.substr(1));
str = "";
}
} else if(str[0] == '\"') {
auto end = str.find('\"', 1);
if(end != std::string::npos) {
output.push_back(str.substr(1, end - 1));
str = str.substr(end + 1);
} else {
output.push_back(str.substr(1));
str = "";
}
} else {
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
if(it != std::end(str)) {
std::string value = std::string(str.begin(), it);
output.push_back(value);
str = std::string(it + 1, str.end());
str = std::string(it, str.end());
} else {
output.push_back(str);
str = "";
}
}
// transform any embedded quotes into the regular character
if(embeddedQuote) {
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
embeddedQuote = false;
}
trim(str);
}
return output;
}
/// Add a leader to the beginning of all new lines (nothing is added
/// at the start of the first line). `"; "` would be for ini files
///
/// Can't use Regex, or this would be a subs.
inline std::string fix_newlines(const std::string &leader, std::string input) {
std::string::size_type n = 0;
while(n != std::string::npos && n < input.size()) {
n = input.find('\n', n);
if(n != std::string::npos) {
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
n += leader.size();
}
}
return input;
}
/// This function detects an equal or colon followed by an escaped quote after an argument
/// then modifies the string to replace the equality with a space. This is needed
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
/// the return value is the offset+1 which is required by the find_and_modify function.
inline std::size_t escape_detect(std::string &str, std::size_t offset) {
auto next = str[offset + 1];
if((next == '\"') || (next == '\'') || (next == '`')) {
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
if(astart != std::string::npos) {
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
str[offset] = ' '; // interpret this as a space so the split_up works properly
}
}
return offset + 1;
}
/// Add quotes if the string contains spaces
inline std::string &add_quotes_if_needed(std::string &str) {
if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
char quote = str.find('"') < str.find('\'') ? '\'' : '"';
if(str.find(' ') != std::string::npos) {
str.insert(0, 1, quote);
str.append(1, quote);
}
}
return str;
}
} // namespace detail
} // namespace CLI
} // namespace detail
} // namespace CLI

View file

@ -1,19 +1,9 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// On GCC < 4.8, the following define is often missing. Due to the
// fact that this library only uses sleep_for, this should be safe
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8
#define _GLIBCXX_USE_NANOSLEEP
#endif
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <array>
#include <chrono> // NOLINT(build/c++11)
#include <chrono>
#include <functional>
#include <iostream>
#include <string>
@ -21,7 +11,6 @@
namespace CLI {
/// This is a simple timer with pretty printing. Creating the timer starts counting.
class Timer {
protected:
/// This is a typedef to make clocks easier to use
@ -43,7 +32,7 @@ class Timer {
time_point start_;
/// This is the number of times cycles (print divides by this number)
std::size_t cycles{1};
size_t cycles{1};
public:
/// Standard print function, this one is set by default
@ -57,7 +46,7 @@ class Timer {
public:
/// Standard constructor, can set title and print function
explicit Timer(std::string title = "Timer", time_print_t time_print = Simple)
Timer(std::string title = "Timer", time_print_t time_print = Simple)
: title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {}
/// Time a function by running it multiple times. Target time is the len to target.
@ -66,14 +55,14 @@ class Timer {
double total_time;
start_ = clock::now();
std::size_t n = 0;
size_t n = 0;
do {
f();
std::chrono::duration<double> elapsed = clock::now() - start_;
total_time = elapsed.count();
} while(n++ < 100u && total_time < target_time);
} while(n++ < 100 && total_time < target_time);
std::string out = make_time_str(total_time / static_cast<double>(n)) + " for " + std::to_string(n) + " tries";
std::string out = make_time_str(total_time / n) + " for " + std::to_string(n) + " tries";
start_ = start;
return out;
}
@ -82,18 +71,16 @@ class Timer {
std::string make_time_str() const {
time_point stop = clock::now();
std::chrono::duration<double> elapsed = stop - start_;
double time = elapsed.count() / static_cast<double>(cycles);
double time = elapsed.count() / cycles;
return make_time_str(time);
}
// LCOV_EXCL_START
/// This prints out a time string from a time
std::string make_time_str(double time) const {
auto print_it = [](double x, std::string unit) {
const unsigned int buffer_length = 50;
std::array<char, buffer_length> buffer;
std::snprintf(buffer.data(), buffer_length, "%.5g", x);
return buffer.data() + std::string(" ") + unit;
char buffer[50];
std::snprintf(buffer, 50, "%.5g", x);
return buffer + std::string(" ") + unit;
};
if(time < .000001)
@ -105,13 +92,13 @@ class Timer {
else
return print_it(time, "s");
}
// LCOV_EXCL_STOP
// LCOV_EXCL_END
/// This is the main function, it creates a string
std::string to_string() const { return time_print_(title_, make_time_str()); }
/// Division sets the number of cycles to divide by (no graphical change)
Timer &operator/(std::size_t val) {
Timer &operator/(size_t val) {
cycles = val;
return *this;
}
@ -121,14 +108,14 @@ class Timer {
class AutoTimer : public Timer {
public:
/// Reimplementing the constructor is required in GCC 4.7
explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
// GCC 4.7 does not support using inheriting constructors.
/// This destructor prints the string
/// This desctructor prints the string
~AutoTimer() { std::cout << to_string() << std::endl; }
};
} // namespace CLI
} // namespace CLI
/// This prints out the time if shifted into a std::cout like stream.
inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
// [CLI11:verbatim]
#define CLI11_VERSION_MAJOR 1
#define CLI11_VERSION_MINOR 9
#define CLI11_VERSION_PATCH 1
#define CLI11_VERSION "1.9.1"
// [CLI11:verbatim]

File diff suppressed because it is too large Load diff

27
ext/fmt-6.1.2/LICENSE.rst Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2012 - present, Victor Zverovich
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.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

501
ext/fmt-6.1.2/README.rst Normal file
View file

@ -0,0 +1,501 @@
{fmt}
=====
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master
:target: https://travis-ci.org/fmtlib/fmt
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
:target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
:alt: fmt is continuously fuzzed att oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dlibfmt&can=1
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
:alt: Ask questions at StackOverflow with the tag fmt
:target: http://stackoverflow.com/questions/tagged/fmt
**{fmt}** is an open-source formatting library for C++.
It can be used as a safe and fast alternative to (s)printf and iostreams.
`Documentation <https://fmt.dev/latest/>`__
Q&A: ask questions on `StackOverflow with the tag fmt <http://stackoverflow.com/questions/tagged/fmt>`_.
Features
--------
* Replacement-based `format API <https://fmt.dev/dev/api.html>`_ with
positional arguments for localization.
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
of `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
in Python.
* Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
the POSIX extension for positional arguments.
* Implementation of `C++20 std::format <https://fmt.dev/Text%20Formatting.html>`__.
* Support for user-defined types.
* High performance: faster than common standard library implementations of
`printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_ and
iostreams. See `Speed tests`_ and `Fast integer to string conversion in C++
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
* Small code size both in terms of source code (the minimum configuration
consists of just three header files, ``core.h``, ``format.h`` and
``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
* Reliability: the library has an extensive set of `unit tests
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
* Safety: the library is fully type safe, errors in format strings can be
reported at compile time, automatic memory management prevents buffer overflow
errors.
* Ease of use: small self-contained code base, no external dependencies,
permissive MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
consistent output across platforms and support for older compilers.
* Clean warning-free codebase even on high warning levels
(``-Wall -Wextra -pedantic``).
* Support for wide strings.
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro.
See the `documentation <https://fmt.dev/latest/>`_ for more details.
Examples
--------
Print ``Hello, world!`` to ``stdout``:
.. code:: c++
fmt::print("Hello, {}!", "world"); // Python-like format string syntax
fmt::printf("Hello, %s!", "world"); // printf format string syntax
Format a string and use positional arguments:
.. code:: c++
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
Check a format string at compile time:
.. code:: c++
// test.cc
#include <fmt/format.h>
std::string s = format(FMT_STRING("{2}"), 42);
.. code::
$ c++ -Iinclude -std=c++14 test.cc
...
test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
std::string s = format(FMT_STRING("{2}"), 42);
^
include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
ErrorHandler::on_error(message);
^
include/fmt/format.h:2226:16: note: in call to '&checker.context_->on_error(&"argument index out of range"[0])'
context_.on_error("argument index out of range");
^
Use {fmt} as a safe portable replacement for ``itoa``
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
.. code:: c++
fmt::memory_buffer buf;
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
Format objects of user-defined types via a simple `extension API
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_:
.. code:: c++
#include "fmt/format.h"
struct date {
int year, month, day;
};
template <>
struct fmt::formatter<date> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const date& d, FormatContext& ctx) {
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
}
};
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"
Create your own functions similar to `format
<https://fmt.dev/latest/api.html#format>`_ and
`print <https://fmt.dev/latest/api.html#print>`_
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
.. code:: c++
// Prints formatted error message.
void vreport_error(const char* format, fmt::format_args args) {
fmt::print("Error: ");
fmt::vprint(format, args);
}
template <typename... Args>
void report_error(const char* format, const Args & ... args) {
vreport_error(format, fmt::make_format_args(args...));
}
report_error("file not found: {}", path);
Note that ``vreport_error`` is not parameterized on argument types which can
improve compile times and reduce code size compared to a fully parameterized
version.
Benchmarks
----------
Speed tests
~~~~~~~~~~~
================= ============= ===========
Library Method Run Time, s
================= ============= ===========
libc printf 1.04
libc++ std::ostream 3.05
{fmt} 6.1.1 fmt::print 0.75
Boost Format 1.67 boost::format 7.24
Folly Format folly::format 2.23
================= ============= ===========
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
The above results were generated by building ``tinyformat_test.cpp`` on macOS
10.14.6 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
{fmt} is 10x faster than ``std::ostringstream`` and ``sprintf`` on floating-point
formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and as fast as `double-conversion <https://github.com/google/double-conversion>`_:
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
:target: https://fmt.dev/unknown_mac64_clang10.0.html
Compile time and code bloat
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The script `bloat-test.py
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
tests compile time and code bloat for nontrivial projects.
It generates 100 translation units and uses ``printf()`` or its alternative
five times in each to simulate a medium sized project. The resulting
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
macOS Sierra, best of three) is shown in the following tables.
**Optimized build (-O3)**
============= =============== ==================== ==================
Method Compile Time, s Executable size, KiB Stripped size, KiB
============= =============== ==================== ==================
printf 2.6 29 26
printf+string 16.4 29 26
iostreams 31.1 59 55
{fmt} 19.0 37 34
Boost Format 91.9 226 203
Folly Format 115.7 101 88
============= =============== ==================== ==================
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
size compared to iostreams and comes pretty close to ``printf``. Boost Format
and Folly Format have the largest overheads.
``printf+string`` is the same as ``printf`` but with extra ``<string>``
include to measure the overhead of the latter.
**Non-optimized build**
============= =============== ==================== ==================
Method Compile Time, s Executable size, KiB Stripped size, KiB
============= =============== ==================== ==================
printf 2.2 33 30
printf+string 16.0 33 30
iostreams 28.3 56 52
{fmt} 18.2 59 50
Boost Format 54.1 365 303
Folly Format 79.9 445 430
============= =============== ==================== ==================
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
compare formatting function overhead only. Boost Format is a
header-only library so it doesn't provide any linkage options.
Running the tests
~~~~~~~~~~~~~~~~~
Please refer to `Building the library`__ for the instructions on how to build
the library and run the unit tests.
__ https://fmt.dev/latest/usage.html#building-the-library
Benchmarks reside in a separate repository,
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
so to run the benchmarks you first need to clone this repository and
generate Makefiles with CMake::
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
$ cd format-benchmark
$ cmake .
Then you can run the speed test::
$ make speed-test
or the bloat test::
$ make bloat-test
Projects using this library
---------------------------
* `0 A.D. <http://play0ad.com/>`_: A free, open-source, cross-platform real-time
strategy game
* `AMPL/MP <https://github.com/ampl/mp>`_:
An open-source library for mathematical programming
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
operations suite
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
* `ccache <https://ccache.dev/>`_: A compiler cache
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks
* `KBEngine <http://kbengine.org/>`_: An open-source MMOG server engine
* `Keypirinha <http://keypirinha.com/>`_: A semantic launcher for Windows
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
* `Drake <http://drake.mit.edu/>`_: A planning, control, and analysis toolbox
for nonlinear dynamical systems (MIT)
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
(Lyft)
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
* `MongoDB <https://mongodb.com/>`_: Distributed document database
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
generate randomized datasets
* `OpenSpace <http://openspaceproject.com/>`_: An open-source astrovisualization
framework
* `PenUltima Online (POL) <http://www.polserver.com/>`_:
An MMO server, compatible with most Ultima Online clients
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
associative database
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
proxy
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
library
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
Small crossplatform 2D graphic engine
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
Business intelligence software
* `Scylla <http://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
that can handle 1 million transactions per second on a single server
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
framework for high-performance server applications on modern hardware
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
* `Stellar <https://www.stellar.org/>`_: Financial platform
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
MMORPG framework
`More... <https://github.com/search?q=fmtlib&type=Code>`_
If you are aware of other projects using this library, please let me know
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
`issue <https://github.com/fmtlib/fmt/issues>`_.
Motivation
----------
So why yet another formatting library?
There are plenty of methods for doing this task, from standard ones like
the printf family of function and iostreams to Boost Format and FastFormat
libraries. The reason for creating a new library is that every existing
solution that I found either had serious issues or didn't provide
all the features I needed.
printf
~~~~~~
The good thing about ``printf`` is that it is pretty fast and readily available
being a part of the C standard library. The main drawback is that it
doesn't support user-defined types. ``printf`` also has safety issues although
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
There is a POSIX extension that adds positional arguments required for
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
to ``printf`` but it is not a part of C99 and may not be available on some
platforms.
iostreams
~~~~~~~~~
The main issue with iostreams is best illustrated with an example:
.. code:: c++
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
which is a lot of typing compared to printf:
.. code:: c++
printf("%.2f\n", 1.23456);
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
don't support positional arguments by design.
The good part is that iostreams support user-defined types and are safe although
error handling is awkward.
Boost Format
~~~~~~~~~~~~
This is a very powerful library which supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to
various benchmarks it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see
`Benchmarks`_).
FastFormat
~~~~~~~~~~
This is an interesting library which is fast, safe and has positional
arguments. However it has significant limitations, citing its author:
Three features that have no hope of being accommodated within the
current design are:
* Leading zeros (or any other non-space padding)
* Octal/hexadecimal encoding
* Runtime width/alignment specification
It is also quite big and has a heavy dependency, STLSoft, which might be
too restrictive for using it in some projects.
Boost Spirit.Karma
~~~~~~~~~~~~~~~~~~
This is not really a formatting library but I decided to include it here for
completeness. As iostreams, it suffers from the problem of mixing verbatim text
with arguments. The library is pretty fast, but slower on integer formatting
than ``fmt::format_int`` on Karma's own benchmark,
see `Fast integer to string conversion in C++
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
FAQ
---
Q: how can I capture formatting arguments and format them later?
A: use ``std::tuple``:
.. code:: c++
template <typename... Args>
auto capture(const Args&... args) {
return std::make_tuple(args...);
}
auto print_message = [](const auto&... args) {
fmt::print(args...);
};
// Capture and store arguments:
auto args = capture("{} {}", 42, "foo");
// Do formatting:
std::apply(print_message, args);
License
-------
{fmt} is distributed under the MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
The `Format String Syntax
<https://fmt.dev/latest/syntax.html>`_
section in the documentation is based on the one from Python `string module
documentation <https://docs.python.org/3/library/string.html#module-string>`_
adapted for the current library. For this reason the documentation is
distributed under the Python Software Foundation license available in
`doc/python-license.txt
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
It only applies if you distribute the documentation of fmt.
Acknowledgments
---------------
The {fmt} library is maintained by Victor Zverovich (`vitaut
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
<https://github.com/foonathan>`_) with contributions from many other people.
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
Let us know if your contribution is not listed or mentioned incorrectly and
we'll make it right.
The benchmark section of this readme file and the performance tests are taken
from the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library
written by Chris Foster. Boost Format library is acknowledged transitively
since it had some influence on tinyformat.
Some ideas used in the implementation are borrowed from `Loki
<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API
<http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
`Clang <http://clang.llvm.org/>`_.
Format string syntax and the documentation are based on Python's `str.format
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
comments and contribution to the design of the type-safe API and
`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
formatting. Thanks `Ruslan Baratov <https://github.com/ruslo>`_ for comprehensive
`comparison of integer formatting algorithms <https://github.com/ruslo/int-dec-format-tests>`_
and useful comments regarding performance, `Boris Kaul <https://github.com/localvoid>`_ for
`C++ counting digits benchmark <https://github.com/localvoid/cxx-benchmark-count-digits>`_.
Thanks to `CarterLi <https://github.com/CarterLi>`_ for contributing various
improvements to the code.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,570 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
italic = 1 << 1,
underline = 1 << 2,
strikethrough = 1 << 3
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace internal {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace internal
// Experimental text formatting support.
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems(em) {}
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
const text_style& rhs) {
return lhs |= rhs;
}
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
const text_style& rhs) {
return lhs &= rhs;
}
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
return set_foreground_color;
}
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
return set_background_color;
}
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
internal::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
FMT_NOEXCEPT;
internal::color_type foreground_color;
internal::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace internal {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
const char* esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == internal::data::background_color;
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
std::size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
uint8_t em_codes[4] = {};
uint8_t em_bits = static_cast<uint8_t>(em);
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
em_codes[3] = 9;
std::size_t index = 0;
for (int i = 0; i < 4; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
return buffer + std::strlen(buffer);
}
private:
Char buffer[7u + 3u * 4u + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
};
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
internal::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
internal::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, internal::data::background_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
return ansi_color_escape<Char>(em);
}
template <typename Char>
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
std::fputs(chars, stream);
}
template <>
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
std::fputws(chars, stream);
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(internal::data::reset_color, stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(internal::data::wreset_color, stream);
}
template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}
template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
internal::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background =
internal::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
vformat_to(buf, format_str, args);
if (has_style) {
internal::reset_color<Char>(buf);
}
}
} // namespace internal
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
internal::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
internal::fputs(buf.data(), f);
}
/**
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
internal::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
}
/**
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
internal::vformat_to(buf, ts, to_string_view(format_str), args);
return fmt::to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string using ANSI
escape sequences to specify text formatting.
**Example**::
#include <fmt/color.h>
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
"The answer is {}", 42);
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
{internal::make_args_checked<Args...>(format_str, args...)});
}
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View file

@ -0,0 +1,585 @@
// Formatting library for C++ - experimental format string compilation
//
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_
#include <vector>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace internal {
// Part of a compiled format string. It can be either literal text or a
// replacement field.
template <typename Char> struct format_part {
enum class kind { arg_index, arg_name, text, replacement };
struct replacement {
arg_ref<Char> arg_id;
dynamic_format_specs<Char> specs;
};
kind part_kind;
union value {
int arg_index;
basic_string_view<Char> str;
replacement repl;
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
FMT_CONSTEXPR value(replacement r) : repl(r) {}
} val;
// Position past the end of the argument id.
const Char* arg_id_end = nullptr;
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
: part_kind(k), val(v) {}
static FMT_CONSTEXPR format_part make_arg_index(int index) {
return format_part(kind::arg_index, index);
}
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
return format_part(kind::arg_name, name);
}
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
return format_part(kind::text, text);
}
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
return format_part(kind::replacement, repl);
}
};
template <typename Char> struct part_counter {
unsigned num_parts = 0;
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end) ++num_parts;
}
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
const Char* end) {
// Find the matching brace.
unsigned brace_counter = 0;
for (; begin != end; ++begin) {
if (*begin == '{') {
++brace_counter;
} else if (*begin == '}') {
if (brace_counter == 0u) break;
--brace_counter;
}
}
return begin;
}
FMT_CONSTEXPR void on_error(const char*) {}
};
// Counts the number of parts in a format string.
template <typename Char>
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
part_counter<Char> counter;
parse_format_string<true>(format_str, counter);
return counter.num_parts;
}
template <typename Char, typename PartHandler>
class format_string_compiler : public error_handler {
private:
using part = format_part<Char>;
PartHandler handler_;
part part_;
basic_string_view<Char> format_str_;
basic_format_parse_context<Char> parse_context_;
public:
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
PartHandler handler)
: handler_(handler),
format_str_(format_str),
parse_context_(format_str) {}
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end)
handler_(part::make_text({begin, to_unsigned(end - begin)}));
}
FMT_CONSTEXPR void on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
}
FMT_CONSTEXPR void on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
}
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
part_ = part::make_arg_name(id);
}
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
part_.arg_id_end = ptr;
handler_(part_);
}
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
const Char* end) {
auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
repl.specs, parse_context_);
auto it = parse_format_specs(begin, end, handler);
if (*it != '}') on_error("missing '}' in format string");
repl.arg_id = part_.part_kind == part::kind::arg_index
? arg_ref<Char>(part_.val.arg_index)
: arg_ref<Char>(part_.val.str);
auto part = part::make_replacement(repl);
part.arg_id_end = begin;
handler_(part);
return it;
}
};
// Compiles a format string and invokes handler(part) for each parsed part.
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
PartHandler handler) {
parse_format_string<IS_CONSTEXPR>(
format_str,
format_string_compiler<Char, PartHandler>(format_str, handler));
}
template <typename Range, typename Context, typename Id>
void format_arg(
basic_format_parse_context<typename Range::value_type>& parse_ctx,
Context& ctx, Id arg_id) {
ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
template <typename Context, typename Range, typename CompiledFormat>
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
-> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_));
Context ctx(out.begin(), args);
const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(parts);
++part_it) {
const auto& part = *part_it;
const auto& value = part.val;
using format_part_t = format_part<char_type>;
switch (part.part_kind) {
case format_part_t::kind::text: {
const auto text = value.str;
auto output = ctx.out();
auto&& it = reserve(output, text.size());
it = std::copy_n(text.begin(), text.size(), it);
ctx.advance_to(output);
break;
}
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
break;
case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end);
internal::format_arg<Range>(parse_ctx, ctx, value.str);
break;
case format_part_t::kind::replacement: {
const auto& arg_id_value = value.repl.arg_id.val;
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
? ctx.arg(arg_id_value.index)
: ctx.arg(arg_id_value.name);
auto specs = value.repl.specs;
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
handle_dynamic_spec<precision_checker>(specs.precision,
specs.precision_ref, ctx);
error_handler h;
numeric_specs_checker<error_handler> checker(h, arg.type());
if (specs.align == align::numeric) checker.require_numeric_argument();
if (specs.sign != sign::none) checker.check_sign();
if (specs.alt) checker.require_numeric_argument();
if (specs.precision >= 0) checker.check_precision();
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
break;
}
}
}
return ctx.out();
}
} // namespace cf
struct basic_compiled_format {};
template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>;
using parts_container = std::vector<internal::format_part<char_type>>;
parts_container compiled_parts;
explicit compiled_format_base(basic_string_view<char_type> format_str) {
compile_format_string<false>(format_str,
[this](const format_part<char_type>& part) {
compiled_parts.push_back(part);
});
}
const parts_container& parts() const { return compiled_parts; }
};
template <typename Char, unsigned N> struct format_part_array {
format_part<Char> data[N] = {};
FMT_CONSTEXPR format_part_array() = default;
};
template <typename Char, unsigned N>
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
basic_string_view<Char> format_str) {
format_part_array<Char, N> parts;
unsigned counter = 0;
// This is not a lambda for compatibility with older compilers.
struct {
format_part<Char>* parts;
unsigned* counter;
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
parts[(*counter)++] = part;
}
} collector{parts.data, &counter};
compile_format_string<true>(format_str, collector);
if (counter < N) {
parts.data[counter] =
format_part<Char>::make_text(basic_string_view<Char>());
}
return parts;
}
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename S>
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
: basic_compiled_format {
using char_type = char_t<S>;
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
// Workaround for old compilers. Format string compilation will not be
// performed there anyway.
#if FMT_USE_CONSTEXPR
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
constexpr_max(count_parts(to_string_view(S())), 1u);
#else
static const unsigned num_format_parts = 1;
#endif
using parts_container = format_part<char_type>[num_format_parts];
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>(
internal::to_string_view(S()));
return compiled_parts.data;
}
};
template <typename S, typename... Args>
class compiled_format : private compiled_format_base<S> {
public:
using typename compiled_format_base<S>::char_type;
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename Range, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
public:
compiled_format() = delete;
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
: compiled_format_base<S>(format_str), format_str_(format_str) {}
};
#ifdef __cpp_if_constexpr
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get(const T& first, const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
else
return get<N - 1>(rest...);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
using get_type = typename get_type_impl<N, T>::type;
template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
// TODO: reserve
return copy_str<Char>(data.begin(), data.end(), out);
}
};
template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
return {{&s[pos], size}};
}
template <typename Char, typename OutputIt, typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
OutputIt format_default(OutputIt out, T value) {
// TODO: reserve
format_int fi(value);
return std::copy(fi.data(), fi.data() + fi.size(), out);
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, double value) {
writer w(out);
w.write(value);
return w.out();
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, Char value) {
*out++ = value;
return out;
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, const Char* value) {
auto length = std::char_traits<Char>::length(value);
return copy_str<Char>(value, value + length, out);
}
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
return format_default<Char>(out, arg);
}
};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
};
template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS != to_string_view(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type;
constexpr basic_string_view<char_type> str = format_str;
if constexpr (str[POS] == '{') {
if (POS + 1 == str.size())
throw format_error("unmatched '{' in format string");
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using type = get_type<ID, Args>;
if constexpr (std::is_same<type, int>::value) {
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
format_str);
} else {
return unknown_format();
}
} else {
return unknown_format();
}
} else if constexpr (str[POS] == '}') {
if (POS + 1 == str.size())
throw format_error("unmatched '}' in format string");
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
}
}
#endif // __cpp_if_constexpr
} // namespace internal
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str;
if constexpr (str.size() == 0) {
return internal::make_text(str, 0, 0);
} else {
constexpr auto result =
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
internal::unknown_format>()) {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result;
}
}
}
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
cf.format(std::back_inserter(buffer), args...);
return to_string(buffer);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
# else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
}
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> internal::compiled_format<const Char*, Args...> {
return internal::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using range = buffer_range<Char>;
using context = buffer_context<Char>;
internal::cf::vformat_to<context>(range(buffer), cf,
{make_format_args<context>(args...)});
return to_string(buffer);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using range = internal::output_range<OutputIt, char_type>;
using context = format_context_t<OutputIt, char_type>;
return internal::cf::vformat_to<context>(
range(out), cf, {make_format_args<context>(args...)});
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
auto it =
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(internal::counting_iterator(), cf, args...).count();
}
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
// Formatting library for C++ - std::locale support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_LOCALE_H_
#define FMT_LOCALE_H_
#include <locale>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace internal {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
using range = buffer_range<Char>;
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
internal::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(const std::locale& loc,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buffer;
internal::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace internal
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<Char>> args) {
return internal::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return internal::vformat(
loc, to_string_view(format_str),
{internal::make_args_checked<Args...>(format_str, args...)});
}
template <typename S, typename OutputIt, typename... Args,
typename Char = enable_if_t<
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(OutputIt out, const std::locale& loc,
const S& format_str,
format_args_t<OutputIt, Char> args) {
using range = internal::output_range<OutputIt, Char>;
return vformat_to<arg_formatter<range>>(
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
internal::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
internal::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE
#endif // FMT_LOCALE_H_

View file

@ -0,0 +1,141 @@
// Formatting library for C++ - std::ostream support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#include <ostream>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace internal {
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
private:
using int_type = typename std::basic_streambuf<Char>::int_type;
using traits_type = typename std::basic_streambuf<Char>::traits_type;
buffer<Char>& buffer_;
public:
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
protected:
// The put-area is actually always empty. This makes the implementation
// simpler and has the advantage that the streambuf and the buffer are always
// in sync and sputc never writes into uninitialized memory. The obvious
// disadvantage is that each call to sputc always results in a (virtual) call
// to overflow. There is no disadvantage here for sputn since this always
// results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch));
return ch;
}
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
buffer_.append(s, s + count);
return count;
}
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
// Hide all operator<< from std::basic_ostream<Char>.
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
};
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
private:
template <typename U>
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
<< std::declval<U>()),
void_t<>>::value>
test(int);
template <typename> static std::false_type test(...);
using result = decltype(test<T>(0));
public:
static const bool value = result::value;
};
// Write the content of buf to os.
template <typename Char>
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
unsigned_streamsize n = size <= max_size ? size : max_size;
os.write(buf_data, static_cast<std::streamsize>(n));
buf_data += n;
size -= n;
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) {
formatbuf<Char> format_buf(buf);
std::basic_ostream<Char> output(&format_buf);
if (loc) output.imbue(loc.get<std::locale>());
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
output << value;
buf.resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: formatter<basic_string_view<Char>, Char> {
template <typename Context>
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
basic_memory_buffer<Char> buffer;
format_value(buffer, value, ctx.locale());
basic_string_view<Char> str(buffer.data(), buffer.size());
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
}
};
} // namespace internal
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buffer;
internal::vformat_to(buffer, format_str, args);
internal::write(os, buffer);
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
{internal::make_args_checked<Args...>(format_str, args...)});
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_

View file

@ -0,0 +1,321 @@
// A C++ interface to POSIX functions.
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_POSIX_H_
#define FMT_POSIX_H_
#if defined(__MINGW32__) || defined(__CYGWIN__)
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
# undef __STRICT_ANSI__
#endif
#include <cerrno>
#include <clocale> // for locale_t
#include <cstdio>
#include <cstdlib> // for strtod_l
#include <cstddef>
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
#endif
#include "format.h"
// UWP doesn't provide _pipe.
#if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
#endif
#if FMT_HAS_INCLUDE("fcntl.h") && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
#else
# define FMT_USE_FCNTL 0
#endif
#ifndef FMT_POSIX
# if defined(_WIN32) && !defined(__MINGW32__)
// Fix warnings about deprecated symbols.
# define FMT_POSIX(call) _##call
# else
# define FMT_POSIX(call) call
# endif
#endif
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
#ifdef FMT_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else
# define FMT_SYSTEM(call) call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call
# else
# define FMT_POSIX_CALL(call) ::call
# endif
#endif
// Retries the expression while it evaluates to error_result and errno
// equals to EINTR.
#ifndef _WIN32
# define FMT_RETRY_VAL(result, expression, error_result) \
do { \
(result) = (expression); \
} while ((result) == (error_result) && errno == EINTR)
#else
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
#endif
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
/**
\rst
A reference to a null-terminated string. It can be constructed from a C
string or ``std::string``.
You can use one of the following type aliases for common character types:
+---------------+-----------------------------+
| Type | Definition |
+===============+=============================+
| cstring_view | basic_cstring_view<char> |
+---------------+-----------------------------+
| wcstring_view | basic_cstring_view<wchar_t> |
+---------------+-----------------------------+
This class is most useful as a parameter type to allow passing
different types of strings to a function, for example::
template <typename... Args>
std::string format(cstring_view format_str, const Args & ... args);
format("{}", 42);
format(std::string("{}"), 42);
\endrst
*/
template <typename Char> class basic_cstring_view {
private:
const Char* data_;
public:
/** Constructs a string reference object from a C string. */
basic_cstring_view(const Char* s) : data_(s) {}
/**
\rst
Constructs a string reference from an ``std::string`` object.
\endrst
*/
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
/** Returns the pointer to a C string. */
const Char* c_str() const { return data_; }
};
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
// An error code.
class error_code {
private:
int value_;
public:
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
int get() const FMT_NOEXCEPT { return value_; }
};
// A buffered file.
class buffered_file {
private:
FILE* file_;
friend class file;
explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() FMT_NOEXCEPT;
public:
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
other.file_ = nullptr;
}
buffered_file& operator=(buffered_file&& other) {
close();
file_ = other.file_;
other.file_ = nullptr;
return *this;
}
// Opens a file.
FMT_API buffered_file(cstring_view filename, cstring_view mode);
// Closes the file.
FMT_API void close();
// Returns the pointer to a FILE object representing this file.
FILE* get() const FMT_NOEXCEPT { return file_; }
// We place parentheses around fileno to workaround a bug in some versions
// of MinGW that define fileno as a macro.
FMT_API int(fileno)() const;
void vprint(string_view format_str, format_args args) {
fmt::vprint(file_, format_str, args);
}
template <typename... Args>
inline void print(string_view format_str, const Args&... args) {
vprint(format_str, make_format_args(args...));
}
};
#if FMT_USE_FCNTL
// A file. Closed file is represented by a file object with descriptor -1.
// Methods that are not declared with FMT_NOEXCEPT may throw
// fmt::system_error in case of failure. Note that some errors such as
// closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler.
class file {
private:
int fd_; // File descriptor.
// Constructs a file object with a given descriptor.
explicit file(int fd) : fd_(fd) {}
public:
// Possible values for the oflag argument to the constructor.
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
};
// Constructs a file object which doesn't represent any file.
file() FMT_NOEXCEPT : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
FMT_API file(cstring_view path, int oflag);
public:
file(const file&) = delete;
void operator=(const file&) = delete;
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
file& operator=(file&& other) FMT_NOEXCEPT {
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
// Destroys the object closing the file it represents if any.
FMT_API ~file() FMT_NOEXCEPT;
// Returns the file descriptor.
int descriptor() const FMT_NOEXCEPT { return fd_; }
// Closes the file.
FMT_API void close();
// Returns the file size. The size has signed type for consistency with
// stat::st_size.
FMT_API long long size() const;
// Attempts to read count bytes from the file into the specified buffer.
FMT_API std::size_t read(void* buffer, std::size_t count);
// Attempts to write count bytes from the specified buffer to the file.
FMT_API std::size_t write(const void* buffer, std::size_t count);
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
FMT_API static file dup(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
FMT_API void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
FMT_API static void pipe(file& read_end, file& write_end);
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
FMT_API buffered_file fdopen(const char* mode);
};
// Returns the memory page size.
long getpagesize();
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE
// A "C" numeric locale.
class Locale {
private:
# ifdef _WIN32
using locale_t = _locale_t;
enum { LC_NUMERIC_MASK = LC_NUMERIC };
static locale_t newlocale(int category_mask, const char* locale, locale_t) {
return _create_locale(category_mask, locale);
}
static void freelocale(locale_t locale) { _free_locale(locale); }
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) {
return _strtod_l(nptr, endptr, locale);
}
# endif
locale_t locale_;
public:
using type = locale_t;
Locale(const Locale&) = delete;
void operator=(const Locale&) = delete;
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) {
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
}
~Locale() { freelocale(locale_); }
type get() const { return locale_; }
// Converts string to floating-point number and advances str past the end
// of the parsed input.
double strtod(const char*& str) const {
char* end = nullptr;
double result = strtod_l(str, &end, locale_);
str = end;
return result;
}
};
#endif // FMT_LOCALE
FMT_END_NAMESPACE
#endif // FMT_POSIX_H_

View file

@ -0,0 +1,711 @@
// Formatting library for C++ - legacy printf implementation
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_PRINTF_H_
#define FMT_PRINTF_H_
#include <algorithm> // std::max
#include <limits> // std::numeric_limits
#include "ostream.h"
FMT_BEGIN_NAMESPACE
namespace internal {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <typename T> static bool fits_in_int(T value) {
unsigned max = max_value<int>();
return value <= max;
}
static bool fits_in_int(bool) { return true; }
};
template <> struct int_checker<true> {
template <typename T> static bool fits_in_int(T value) {
return value >= std::numeric_limits<int>::min() &&
value <= max_value<int>();
}
static bool fits_in_int(int) { return true; }
};
class printf_precision_handler {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(format_error("number is too big"));
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) {
FMT_THROW(format_error("precision is not integer"));
return 0;
}
};
// An argument visitor that returns true iff arg is a zero integer.
class is_zero_int {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
bool operator()(T value) {
return value == 0;
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
bool operator()(T) {
return false;
}
};
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
template <typename T, typename Context> class arg_converter {
private:
using char_type = typename Context::char_type;
basic_format_arg<Context>& arg_;
char_type type_;
public:
arg_converter(basic_format_arg<Context>& arg, char_type type)
: arg_(arg), type_(type) {}
void operator()(bool value) {
if (type_ != 's') operator()<bool>(value);
}
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
void operator()(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
arg_ = internal::make_arg<Context>(
static_cast<int>(static_cast<target_type>(value)));
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = internal::make_arg<Context>(
static_cast<unsigned>(static_cast<unsigned_type>(value)));
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
} else {
arg_ = internal::make_arg<Context>(
static_cast<typename make_unsigned_or_bool<U>::type>(value));
}
}
}
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
void operator()(U) {} // No conversion needed for non-integral types.
};
// Converts an integer argument to T for printf, if T is an integral type.
// If T is void, the argument is converted to corresponding signed or unsigned
// type depending on the type specifier: 'd' and 'i' - signed, other -
// unsigned).
template <typename T, typename Context, typename Char>
void convert_arg(basic_format_arg<Context>& arg, Char type) {
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
}
// Converts an integer argument to char for printf.
template <typename Context> class char_converter {
private:
basic_format_arg<Context>& arg_;
public:
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
arg_ = internal::make_arg<Context>(
static_cast<typename Context::char_type>(value));
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
void operator()(T) {} // No conversion needed for non-integral types.
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
private:
using format_specs = basic_format_specs<Char>;
format_specs& specs_;
public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (internal::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
unsigned int_max = max_value<int>();
if (width > int_max) FMT_THROW(format_error("number is too big"));
return static_cast<unsigned>(width);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) {
FMT_THROW(format_error("width is not integer"));
return 0;
}
};
template <typename Char, typename Context>
void printf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
}
template <typename OutputIt, typename Char, typename Context>
internal::truncating_iterator<OutputIt> printf(
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
basic_format_args<Context> args) {
return Context(it, format, args).format();
}
} // namespace internal
using internal::printf; // For printing into memory_buffer.
template <typename Range> class printf_arg_formatter;
template <typename OutputIt, typename Char> class basic_printf_context;
/**
\rst
The ``printf`` argument formatter.
\endrst
*/
template <typename Range>
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
public:
using iterator = typename Range::iterator;
private:
using char_type = typename Range::value_type;
using base = internal::arg_formatter_base<Range>;
using context_type = basic_printf_context<iterator, char_type>;
context_type& context_;
void write_null_pointer(char) {
this->specs()->type = 0;
this->write("(nil)");
}
void write_null_pointer(wchar_t) {
this->specs()->type = 0;
this->write(L"(nil)");
}
public:
using format_specs = typename base::format_specs;
/**
\rst
Constructs an argument formatter object.
*buffer* is a reference to the output buffer and *specs* contains format
specifier information for standard argument types.
\endrst
*/
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
iterator operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and char_type so
// use std::is_same instead.
if (std::is_same<T, bool>::value) {
format_specs& fmt_specs = *this->specs();
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
fmt_specs.type = 0;
this->write(value != 0);
} else if (std::is_same<T, char_type>::value) {
format_specs& fmt_specs = *this->specs();
if (fmt_specs.type && fmt_specs.type != 'c')
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.align = align::right;
return base::operator()(value);
} else {
return base::operator()(value);
}
return this->out();
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
iterator operator()(T value) {
return base::operator()(value);
}
/** Formats a null-terminated C string. */
iterator operator()(const char* value) {
if (value)
base::operator()(value);
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write("(null)");
return this->out();
}
/** Formats a null-terminated wide C string. */
iterator operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write(L"(null)");
return this->out();
}
iterator operator()(basic_string_view<char_type> value) {
return base::operator()(value);
}
iterator operator()(monostate value) { return base::operator()(value); }
/** Formats a pointer. */
iterator operator()(const void* value) {
if (value) return base::operator()(value);
this->specs()->type = 0;
write_null_pointer(char_type());
return this->out();
}
/** Formats an argument of a custom (user-defined) type. */
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
handle.format(context_.parse_context(), context_);
return this->out();
}
};
template <typename T> struct printf_formatter {
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
internal::format_value(internal::get_container(ctx.out()), value);
return ctx.out();
}
};
/** This template formats data and writes the output to a writer. */
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
using char_type = Char;
using format_arg = basic_format_arg<basic_printf_context>;
template <typename T> using formatter_type = printf_formatter<T>;
private:
using format_specs = basic_format_specs<char_type>;
OutputIt out_;
basic_format_args<basic_printf_context> args_;
basic_format_parse_context<Char> parse_ctx_;
static void parse_flags(format_specs& specs, const Char*& it,
const Char* end);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
format_arg get_arg(int arg_index = -1);
// Parses argument index, flags and width and returns the argument index.
int parse_header(const Char*& it, const Char* end, format_specs& specs);
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments and
the writer are stored in the context object so make sure they have
appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args), parse_ctx_(format_str) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
format_arg arg(int id) const { return args_.get(id); }
basic_format_parse_context<Char>& parse_context() { return parse_ctx_; }
FMT_CONSTEXPR void on_error(const char* message) {
parse_ctx_.on_error(message);
}
/** Formats stored arguments and writes the output to the range. */
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
OutputIt format();
};
template <typename OutputIt, typename Char>
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
const Char*& it,
const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
specs.align = align::left;
break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill[0] = '0';
break;
case ' ':
specs.sign = sign::space;
break;
case '#':
specs.alt = true;
break;
default:
return;
}
}
}
template <typename OutputIt, typename Char>
typename basic_printf_context<OutputIt, Char>::format_arg
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return internal::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
int basic_printf_context<OutputIt, Char>::parse_header(
const Char*& it, const Char* end, format_specs& specs) {
int arg_index = -1;
char_type c = *it;
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
internal::error_handler eh;
int value = parse_nonnegative_int(it, end, eh);
if (it != end && *it == '$') { // value is an argument index
++it;
arg_index = value;
} else {
if (c == '0') specs.fill[0] = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
specs.width = value;
return arg_index;
}
}
}
parse_flags(specs, it, end);
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
internal::error_handler eh;
specs.width = parse_nonnegative_int(it, end, eh);
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
internal::printf_width_handler<char_type>(specs), get_arg()));
}
}
return arg_index;
}
template <typename OutputIt, typename Char>
template <typename ArgFormatter>
OutputIt basic_printf_context<OutputIt, Char>::format() {
auto out = this->out();
const Char* start = parse_ctx_.begin();
const Char* end = parse_ctx_.end();
auto it = start;
while (it != end) {
char_type c = *it++;
if (c != '%') continue;
if (it != end && *it == c) {
out = std::copy(start, it, out);
start = ++it;
continue;
}
out = std::copy(start, it - 1, out);
format_specs specs;
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs);
if (arg_index == 0) on_error("argument index out of range");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
internal::error_handler eh;
specs.precision = parse_nonnegative_int(it, end, eh);
} else if (c == '*') {
++it;
specs.precision =
static_cast<int>(visit_format_arg(internal::printf_precision_handler(), get_arg()));
} else {
specs.precision = 0;
}
}
format_arg arg = get_arg(arg_index);
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic())
specs.align = align::numeric;
else
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0;
using internal::convert_arg;
switch (c) {
case 'h':
if (t == 'h') {
++it;
t = it != end ? *it : 0;
convert_arg<signed char>(arg, t);
} else {
convert_arg<short>(arg, t);
}
break;
case 'l':
if (t == 'l') {
++it;
t = it != end ? *it : 0;
convert_arg<long long>(arg, t);
} else {
convert_arg<long>(arg, t);
}
break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<std::size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default:
--it;
convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) FMT_THROW(format_error("invalid format string"));
specs.type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
switch (specs.type) {
case 'i':
case 'u':
specs.type = 'd';
break;
case 'c':
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
arg);
break;
}
}
start = it;
// Format argument.
visit_format_arg(ArgFormatter(out, specs, *this), arg);
}
return std::copy(start, it, out);
}
template <typename Char>
using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>;
/**
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::printf_args`.
\endrst
*/
template <typename... Args>
inline format_arg_store<printf_context, Args...> make_printf_args(
const Args&... args) {
return {args...};
}
/**
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst
*/
template <typename... Args>
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
const Args&... args) {
return {args...};
}
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vsprintf(
const S& format, basic_format_args<basic_printf_context_t<Char>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
return to_string(buffer);
}
/**
\rst
Formats arguments and returns the result as a string.
**Example**::
std::string message = fmt::sprintf("The answer is %d", 42);
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vsprintf(to_string_view(format), {make_format_args<context>(args...)});
}
template <typename S, typename Char = char_t<S>>
inline int vfprintf(std::FILE* f, const S& format,
basic_format_args<basic_printf_context_t<Char>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
std::size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
/**
\rst
Prints formatted data to the file *f*.
**Example**::
fmt::fprintf(stderr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(f, to_string_view(format),
{make_format_args<context>(args...)});
}
template <typename S, typename Char = char_t<S>>
inline int vprintf(const S& format,
basic_format_args<basic_printf_context_t<Char>> args) {
return vfprintf(stdout, to_string_view(format), args);
}
/**
\rst
Prints formatted data to ``stdout``.
**Example**::
fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(internal::is_string<S>::value)>
inline int printf(const S& format_str, const Args&... args) {
using context = basic_printf_context_t<char_t<S>>;
return vprintf(to_string_view(format_str),
{make_format_args<context>(args...)});
}
template <typename S, typename Char = char_t<S>>
inline int vfprintf(std::basic_ostream<Char>& os, const S& format,
basic_format_args<basic_printf_context_t<Char>> args) {
basic_memory_buffer<Char> buffer;
printf(buffer, to_string_view(format), args);
internal::write(os, buffer);
return static_cast<int>(buffer.size());
}
/** Formats arguments and writes the output to the range. */
template <typename ArgFormatter, typename Char,
typename Context =
basic_printf_context<typename ArgFormatter::iterator, Char>>
typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out,
basic_string_view<Char> format_str,
basic_format_args<Context> args) {
typename ArgFormatter::iterator iter(out);
Context(iter, format_str, args).template format<ArgFormatter>();
return iter;
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::fprintf(cerr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(os, to_string_view(format_str),
{make_format_args<context>(args...)});
}
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_

View file

@ -0,0 +1,365 @@
// Formatting library for C++ - experimental range support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
//
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
// All Rights Reserved
// {fmt} support for ranges, containers and types tuple interface.
#ifndef FMT_RANGES_H_
#define FMT_RANGES_H_
#include <type_traits>
#include "format.h"
// output only up to N items from the range.
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
#endif
FMT_BEGIN_NAMESPACE
template <typename Char> struct formatting_base {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
template <typename Char, typename Enable = void>
struct formatting_range : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
// range.
Char prefix;
Char delimiter;
Char postfix;
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
template <typename Char, typename Enable = void>
struct formatting_tuple : formatting_base<Char> {
Char prefix;
Char delimiter;
Char postfix;
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
namespace internal {
template <typename RangeT, typename OutputIterator>
OutputIterator copy(const RangeT& range, OutputIterator out) {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIterator>
OutputIterator copy(const char* str, OutputIterator out) {
while (*str) *out++ = *str++;
return out;
}
template <typename OutputIterator>
OutputIterator copy(char ch, OutputIterator out) {
*out++ = ch;
return out;
}
/// Return true value if T has std::string interface, like std::string_view.
template <typename T> class is_like_std_string {
template <typename U>
static auto check(U* p)
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
template <typename> static void check(...);
public:
static FMT_CONSTEXPR_DECL const bool value =
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Char>
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
template <typename T>
struct is_range_<
T, conditional_t<false,
conditional_helper<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>,
void>> : std::true_type {};
#endif
/// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p)
-> decltype(std::tuple_size<U>::value,
(void)std::declval<typename std::tuple_element<0, U>::type>(),
int());
template <typename> static void check(...);
public:
static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
// Check for integer_sequence
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
template <std::size_t N>
using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
};
template <std::size_t... N>
using index_sequence = integer_sequence<std::size_t, N...>;
template <typename T, std::size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
#endif
template <class Tuple, class F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
using std::get;
// using free function get<I>(T) now.
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
(void)_; // blocks warnings
}
template <class T>
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
T const&) {
return {};
}
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
const auto indexes = get_indexes(tup);
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
}
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
return add_space ? " {}" : "{}";
}
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
return add_space ? " \"{}\"" : "\"{}\"";
}
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
return add_space ? " \"{}\"" : "\"{}\"";
}
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
return add_space ? L" \"{}\"" : L"\"{}\"";
}
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
return add_space ? " '{}'" : "'{}'";
}
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
}
} // namespace internal
template <typename T> struct is_tuple_like {
static FMT_CONSTEXPR_DECL const bool value =
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
};
template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
private:
// C++11 generic lambda for format()
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) {
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
out = internal::copy(formatting.delimiter, out);
}
out = format_to(out,
internal::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
++i;
}
formatting_tuple<Char>& formatting;
std::size_t& i;
typename std::add_lvalue_reference<decltype(
std::declval<FormatContext>().out())>::type out;
};
public:
formatting_tuple<Char> formatting;
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx);
}
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
std::size_t i = 0;
internal::copy(formatting.prefix, out);
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
internal::copy(formatting.postfix, out);
return ctx.out();
}
};
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
internal::is_range_<T>::value &&
!internal::is_like_std_string<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<internal::std_string_view<Char>, T>::value;
};
template <typename RangeT, typename Char>
struct formatter<RangeT, Char,
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
formatting_range<Char> formatting;
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx);
}
template <typename FormatContext>
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
auto out = internal::copy(formatting.prefix, ctx.out());
std::size_t i = 0;
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
if (i > 0) {
if (formatting.add_prepostfix_space) *out++ = ' ';
out = internal::copy(formatting.delimiter, out);
}
out = format_to(out,
internal::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
out = format_to(out, " ... <other elements>");
break;
}
}
if (formatting.add_prepostfix_space) *out++ = ' ';
return internal::copy(formatting.postfix, out);
}
};
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple{t}, sep{s} {}
};
template <typename Char, typename... T>
struct formatter<tuple_arg_join<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
}
private:
template <typename FormatContext, size_t... N>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
internal::index_sequence<N...>) {
return format_args(value, ctx, std::get<N>(value.tuple)...);
}
template <typename FormatContext>
typename FormatContext::iterator format_args(
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
// NOTE: for compilers that support C++17, this empty function instantiation
// can be replaced with a constexpr branch in the variadic overload.
return ctx.out();
}
template <typename FormatContext, typename Arg, typename... Args>
typename FormatContext::iterator format_args(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
const Arg& arg, const Args&... args) {
using base = formatter<typename std::decay<Arg>::type, Char>;
auto out = ctx.out();
out = base{}.format(arg, ctx);
if (sizeof...(Args) > 0) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
return format_args(value, ctx, args...);
}
return out;
}
};
/**
\rst
Returns an object that formats `tuple` with elements separated by `sep`.
**Example**::
std::tuple<int, char> t = {1, 'a'};
fmt::print("{}", fmt::join(t, ", "));
// Output: "1, a"
\endrst
*/
template <typename... T>
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
string_view sep) {
return {tuple, sep};
}
template <typename... T>
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
wstring_view sep) {
return {tuple, sep};
}
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_

176
ext/fmt-6.1.2/src/format.cc Normal file
View file

@ -0,0 +1,176 @@
// Formatting library for C++
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace internal {
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
#endif
// Suppress the warning about nonliteral format string.
auto snprintf_ptr = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
struct sprintf_specs {
int precision;
char type;
bool alt : 1;
template <typename Char>
constexpr sprintf_specs(basic_format_specs<Char> specs)
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
constexpr bool has_precision() const { return precision >= 0; }
};
// This is deprecated and is kept only to preserve ABI compatibility.
template <typename Double>
char* sprintf_format(Double value, internal::buffer<char>& buf,
sprintf_specs specs) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
// Build format string.
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
char format[max_format_size];
char* format_ptr = format;
*format_ptr++ = '%';
if (specs.alt || !specs.type) *format_ptr++ = '#';
if (specs.precision >= 0) {
*format_ptr++ = '.';
*format_ptr++ = '*';
}
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
char type = specs.type;
if (type == '%')
type = 'f';
else if (type == 0 || type == 'n')
type = 'g';
#if FMT_MSC_VER
if (type == 'F') {
// MSVC's printf doesn't support 'F'.
type = 'f';
}
#endif
*format_ptr++ = type;
*format_ptr = '\0';
// Format using snprintf.
char* start = nullptr;
char* decimal_point_pos = nullptr;
for (;;) {
std::size_t buffer_size = buf.capacity();
start = &buf[0];
int result =
format_float(start, buffer_size, format, specs.precision, value);
if (result >= 0) {
unsigned n = internal::to_unsigned(result);
if (n < buf.capacity()) {
// Find the decimal point.
auto p = buf.data(), end = p + n;
if (*p == '+' || *p == '-') ++p;
if (specs.type != 'a' && specs.type != 'A') {
while (p < end && *p >= '0' && *p <= '9') ++p;
if (p < end && *p != 'e' && *p != 'E') {
decimal_point_pos = p;
if (!specs.type) {
// Keep only one trailing zero after the decimal point.
++p;
if (*p == '0') ++p;
while (p != end && *p >= '1' && *p <= '9') ++p;
char* where = p;
while (p != end && *p == '0') ++p;
if (p == end || *p < '0' || *p > '9') {
if (p != end) std::memmove(where, p, to_unsigned(end - p));
n -= static_cast<unsigned>(p - where);
}
}
}
}
buf.resize(n);
break; // The buffer is large enough - continue with formatting.
}
buf.reserve(n + 1);
} else {
// If result is negative we ask to increase the capacity by at least 1,
// but as std::vector, the buffer grows exponentially.
buf.reserve(buf.capacity() + 1);
}
}
return decimal_point_pos;
}
} // namespace internal
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
sprintf_specs);
template FMT_API char* internal::sprintf_format(long double,
internal::buffer<char>&,
sprintf_specs);
template struct FMT_API internal::basic_data<void>;
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, internal::float_specs,
internal::buffer<char>&) =
internal::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
#endif
// Explicit instantiations for char.
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
template FMT_API char internal::thousands_sep_impl(locale_ref);
template FMT_API char internal::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<char>::append(const char*, const char*);
template FMT_API void internal::arg_map<format_context>::init(
const basic_format_args<format_context>& args);
template FMT_API std::string internal::vformat<char>(
string_view, basic_format_args<format_context>);
template FMT_API format_context::iterator internal::vformat_to(
internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API int internal::snprintf_float(double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::snprintf_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(double, int, internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
// Explicit instantiations for wchar_t.
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*);
template FMT_API std::wstring internal::vformat<wchar_t>(
wstring_view, basic_format_args<wformat_context>);
FMT_END_NAMESPACE

237
ext/fmt-6.1.2/src/posix.cc Normal file
View file

@ -0,0 +1,237 @@
// A C++ interface to POSIX functions.
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
// Disable bogus MSVC warnings.
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS
#endif
#include "fmt/posix.h"
#include <climits>
#if FMT_USE_FCNTL
#include <sys/stat.h>
#include <sys/types.h>
#ifndef _WIN32
# include <unistd.h>
#else
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <io.h>
# include <windows.h>
# define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC
# ifndef S_IRUSR
# define S_IRUSR _S_IREAD
# endif
# ifndef S_IWUSR
# define S_IWUSR _S_IWRITE
# endif
# ifdef __MINGW32__
# define _SH_DENYNO 0x40
# endif
#endif // _WIN32
#endif // FMT_USE_FCNTL
#ifdef fileno
# undef fileno
#endif
namespace {
#ifdef _WIN32
// Return type of read and write functions.
using RWResult = int;
// On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#else
// Return type of read and write functions.
using RWResult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; }
#endif
} // namespace
FMT_BEGIN_NAMESPACE
buffered_file::~buffered_file() FMT_NOEXCEPT {
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
report_system_error(errno, "cannot close file");
}
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
nullptr);
if (!file_)
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
}
void buffered_file::close() {
if (!file_) return;
int result = FMT_SYSTEM(fclose(file_));
file_ = nullptr;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
}
// A macro used to prevent expansion of fileno on broken versions of MinGW.
#define FMT_ARGS
int buffered_file::fileno() const {
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
return fd;
}
#if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) {
int mode = S_IRUSR | S_IWUSR;
#if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
#else
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
#endif
if (fd_ == -1)
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
}
file::~file() FMT_NOEXCEPT {
// Don't retry close in case of EINTR!
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
report_system_error(errno, "cannot close file");
}
void file::close() {
if (fd_ == -1) return;
// Don't retry close in case of EINTR!
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_));
fd_ = -1;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
}
long long file::size() const {
#ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds.
// Both functions support large file sizes.
DWORD size_upper = 0;
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError();
if (error != NO_ERROR)
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
}
unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
#else
using Stat = struct stat;
Stat file_stat = Stat();
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
FMT_THROW(system_error(errno, "cannot get file attributes"));
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
"return type of file::size is not large enough");
return file_stat.st_size;
#endif
}
std::size_t file::read(void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
return internal::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
return internal::to_unsigned(result);
}
file file::dup(int fd) {
// Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
if (new_fd == -1)
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
return file(new_fd);
}
void file::dup2(int fd) {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) {
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
fd_, fd));
}
}
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) ec = error_code(errno);
}
void file::pipe(file& read_end, file& write_end) {
// Close the descriptors first to make sure that assignments don't throw
// and there are no leaks.
read_end.close();
write_end.close();
int fds[2] = {};
#ifdef _WIN32
// Make the default pipe capacity same as on Linux 2.6.11+.
enum { DEFAULT_CAPACITY = 65536 };
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
#else
// Don't retry as the pipe function doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
int result = FMT_POSIX_CALL(pipe(fds));
#endif
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
// The following assignments don't throw because read_fd and write_fd
// are closed.
read_end = file(fds[0]);
write_end = file(fds[1]);
}
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
buffered_file bf(f);
fd_ = -1;
return bf;
}
long getpagesize() {
#ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwPageSize;
#else
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
return size;
#endif
}
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

@ -1 +1 @@
Subproject commit 8d626a24125264dee5cfa549a59b0a7a7a732e6a
Subproject commit f944a5992515d8bbee7c2be0642a0fe37a230519

View file

@ -1,218 +0,0 @@
#include "fixhunter.hh"
#include <iostream>
#include "bits.hh"
#include "rs.hh"
#include "ephemeris.hh"
#include "galileo.hh"
using namespace std;
void FixHunter::reportInav(const vector<uint8_t>& inav, int32_t gst)
{
int wtype = getbitu(&inav[0], 0, 6);
if(wtype >= 1 && wtype <=4) {
d_latestiod = getbitu(&inav[0], 6, 10);
if(wtype == 1) inav1 = inav;
else if(wtype == 2) inav2 = inav;
else if(wtype == 3) inav3 = inav;
else if(wtype == 4) inav4 = inav;
}
else if(wtype == 16) inav16 = inav;
else if(wtype == 17) inav17 = inav;
else if(wtype == 18) inav18 = inav;
else if(wtype == 19) inav19 = inav;
else if(wtype == 20) inav20 = inav;
else return;
if(wtype == 16) {
GalileoMessage gm;
gm.parse(inav);
int32_t t0r = 1 + gst - (gst%30);
d_inav16t0r = t0r;
cout<<" redced af0red "<< 1000000000.0*ldexp(gm.af0red, -26)<<" ns, "<<3600.0*(1000000000.0/(1<<20))*ldexp(gm.af1red, -15)<<" ns/hour ("<<gm.af1red<<") t0r "<<t0r<<" ";
//(30*((nmm.gi().gnsstow()-2)/30)+1) % 604800; // page 56 of the OSS ICD 2.0
REDCEDAdaptor rca(gm, t0r);
Point pointRed;
cout<<"eyred "<<gm.eyred<<" exred "<<gm.exred<<"\nlambda0red in rad "<< ldexp(M_PI*gm.lambda0red, -22)<<" atan2 " <<atan2(1.0*gm.eyred, 1.0*gm.exred)<<" deltaAred "<<gm.deltaAred<<endl;
getCoordinates(gst, rca, &pointRed, false);
cout<<"Reduced coordinates: "<<pointRed<<endl;
}
tryFix(gst);
}
void FixHunter::tryFix(int32_t gst)
{
RSCodec rsc({0, 2, 3, 4, 8}, 1, 1, 60, 137);
string in;
vector<unsigned int> corr;
int cnt=0;
for(const auto& theinav : {inav4, inav3, inav2, inav1, inav20, inav19, inav18, inav17})
if(!theinav.empty()) ++cnt;
if(cnt < 4)
return;
auto blankOut = [&in, &corr](int amount) {
for(int p=0; p < amount ; ++p) {
corr.push_back(in.size());
in.append(1, (char)0); // we just don't have the information
}
};
int wtype=4;
int navcount=0;
for(const auto& theinav : {inav4, inav3, inav2}) {
if(!theinav.empty()) {
++navcount;
cout<<"Have wtype "<<wtype<<endl;
unsigned char tmp[14];
for(int i=0; i < 14; i++)
setbitu(tmp, i*8, 8, getbitu(&theinav[0], 16+i*8, 8));
std::reverse(begin(tmp), end(tmp));
in.append((char*)tmp, 14);
}
else {
cout<<"Blanking wtype "<<wtype<<endl;
blankOut(14);
}
wtype--;
}
if(!inav1.empty()) {
cout<<"Have wtype "<<wtype<<endl;
++navcount;
unsigned char tmp[16];
setbitu(tmp, 0, 6, 1); // wtype 1 somehow
setbitu(tmp, 6, 2, getbitu(&inav1[0], 14, 2)); // last 2 bits of iodnav
setbitu(tmp, 8, 8, getbitu(&inav1[0], 6, 8)); // first 8 bits of iodnav
for(int i=0; i < 14; ++i)
setbitu(tmp, 16+i*8, 8, getbitu(&inav1[0], 16+i*8, 8));
std::reverse(begin(tmp), end(tmp));
in.append((char*)tmp, 16);
}
else {
cout<<"Blanking wtype "<< wtype<<", which is special"<<endl;
blankOut(14); // 14 symbols we don't have
unsigned char tmp[2];
// and two that we can fake:
setbitu(tmp, 0, 6, 1); // wtype 1 somehow
setbitu(tmp, 6, 2, d_latestiod & 3); // last 2 bits of iodnav
setbitu(tmp, 8, 8, d_latestiod >> 2);
std::reverse(begin(tmp), end(tmp));
in.append((char*)tmp, 2);
}
// now the parity bits
wtype=20;
for(const auto& theinav : {inav20, inav19, inav18, inav17}) {
if(!theinav.empty()) {
cout<<"Have wtype "<<wtype<<endl;
GalileoMessage gm;
gm.parse(theinav);
std::reverse(gm.rsparity.begin(), gm.rsparity.end());
in += gm.rsparity;
}
else {
cout<<"Blanking wtype "<<wtype<<endl;
blankOut(15);
}
--wtype;
}
if(corr.size() >= 60 && navcount != 4) {
cout<<"Too many erasures ("<<corr.size()<<"), can't correct"<<endl;
return;
}
for(auto& c : corr)
c = 137 + c;
string out;
cout<<"Input size "<<in.size()<<", corrections/erasure size "<<corr.size()<<", decode status: "<<endl;
try {
if(corr.size() < 60) {
cout<<"Got a reconstructed ephemeris! With "<<rsc.decode(in, out, &corr)<<" corrections"<<endl;
for(const auto& c : corr) {
cout<<c-137<<" "; // padding!
}
cout<<endl;
}
else {
cout<<"Had all 4 nav words but no parity, not doing corrections"<<endl;
out=in;
}
struct GalileoMessage gm=fillGMFromRS(out);
Point point;
getCoordinates(gst, gm, &point, false);
cout<<"full coordinates: "<<point<<endl;
if(!inav16.empty()) {
GalileoMessage gm16;
gm16.parse(inav16);
REDCEDAdaptor rca(gm16, d_inav16t0r);
Point pointRed;
cout<<"eyred "<<gm.eyred<<" exred "<<gm.exred<<"\nlambda0red in rad "<< ldexp(M_PI*gm.lambda0red, -22)<<" atan2 " <<atan2(1.0*gm.eyred, 1.0*gm.exred)<<" deltaAred "<<gm.deltaAred<<endl;
getCoordinates(gst, rca, &pointRed);
cout<<"Reduced coordinates: "<<pointRed<<", distance: ";
Vector dist(pointRed, point);
cout<<"Distance: "<<dist<<", length "<<dist.length()<<", clockdiff "<<
(rca.getAtomicOffset(gst).first - gm.getAtomicOffset(gst).first)/3<<"m"<<endl;
}
}
catch(...)
{
cout << "failed"<<endl;
}
}
struct GalileoMessage FixHunter::fillGMFromRS(const std::string& out)
{
// we need to reconstruct words 1, 2, 3 and 4 and feed them to the parser
vector<uint8_t> inav[5];
string inavraw[5];
inavraw[4] = out.substr(0, 14);
inavraw[3] = out.substr(14, 14);
inavraw[2] = out.substr(28, 14);
inavraw[1] = out.substr(42, 16);
for(int n=1; n<=4; ++n)
reverse(inavraw[n].begin(), inavraw[n].end());
uint8_t tmp[16];
setbitu(tmp, 0, 6, 1); // wtype 1
setbitu(tmp, 6, 2, getbitu((unsigned char*) inavraw[1].c_str(), 14, 2));
setbitu(tmp, 8, 8, getbitu((unsigned char*) inavraw[1].c_str(), 6, 8));
for(int n=0; n < 14; ++n)
setbitu(tmp, 16 + n*8, 8, getbitu((unsigned char*) inavraw[1].c_str(), 16 + n*8, 8));
inav[1]= makeVec(tmp, 16);
struct GalileoMessage gm={};
gm.parse(inav[1]);
cout<<"wtype: "<<(int)gm.wtype<<", iod "<<gm.iodnav<<endl;
cout<<"T0e "<<gm.getT0e()<<" e "<<gm.getE()<<" sqrtA " << gm.getSqrtA() << endl;
for(int i = 2; i <= 4; ++i) {
setbitu(tmp, 0, 6, i);
setbitu(tmp, 6, 10, getbitu((unsigned char*) inav[1].c_str(), 6, 10)); // fake in IOD from inav1
for(int n=0; n < 14; ++n)
setbitu(tmp, 16 + n*8, 8, getbitu((unsigned char*) inavraw[i].c_str(), n*8, 8));
inav[i]= makeVec(tmp, 16);
gm.parse(inav[i]);
cout<<"wtype: "<<(int)gm.wtype<<", iod "<<gm.iodnav<<endl;
}
return gm;
}

View file

@ -1,15 +0,0 @@
#pragma once
#include <string>
#include "galileo.hh"
class FixHunter
{
public:
void reportInav(const std::vector<uint8_t>& inav, int32_t gst);
private:
void tryFix(int32_t gst);
struct GalileoMessage fillGMFromRS(const std::string& out);
std::vector<uint8_t> inav1, inav2, inav3, inav4, inav16, inav17, inav18, inav19, inav20;
int d_latestiod;
uint32_t d_inav16t0r;
};

View file

@ -1,7 +1,7 @@
#include "bits.hh"
#include "galileo.hh"
bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t *wn)
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn)
{
unsigned int wtype = getbitu(&inav[0], 0, 6);
if(wtype==0) {
@ -24,139 +24,3 @@ bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t
return false;
}
int GalileoMessage::parseFnav(const std::vector<uint8_t>& page)
{
const uint8_t* ptr = &page[0];
int offset=0;
auto gbum=[&ptr, &offset](int bits) {
unsigned int ret = getbitu(ptr, offset, bits);
offset += bits;
return ret;
};
auto gbsm=[&ptr, &offset](int bits) {
int ret = getbits(ptr, offset, bits);
offset += bits;
return ret;
};
wtype = gbum(6);
if(wtype == 1) {
/*int sv = */ (void)gbum(6);
iodnav = gbum(10);
t0c = gbum(14);
af0 = gbsm(31);
af1 = gbsm(21);
af2 = gbsm(6);
sisa = gbum(8);
ai0 = gbum(11);
ai1 = gbsm(11);
ai2 = gbsm(14);
sf1 = gbum(1);
sf2 = gbum(1);
sf3 = gbum(1);
sf4 = gbum(1);
sf5 = gbum(1);
BGDE1E5a = gbsm(10);
e5ahs = gbum(2);
wn = gbum(12);
tow = gbum(20);
e5advs=gbum(1);
}
else if(wtype==2) {
iodnav = gbum(10);
m0 = gbsm(32);
omegadot = gbsm(24);
e = gbum(32);
sqrtA = gbum(32);
omega0 = gbsm(32);
idot = gbsm(14);
wn = gbum(12);
tow = gbum(20);
}
else if(wtype == 3) {
iodnav = gbum(10);
i0 = gbsm(32);
omega = gbsm(32);
deltan = gbsm(16);
cuc = gbsm(16);
cus = gbsm(16);
crc =gbsm(16);
crs = gbsm(16);
t0e = gbum(14);
wn = gbum(12);
tow = gbum(20);
}
else if(wtype == 4) {
iodnav = gbum(10);
cic = gbsm(16);
cis = gbsm(16);
a0 = gbsm(32);
a1 = gbsm(24);
dtLS= gbsm(8);
t0t = gbum(8);
wn0t = gbum(8);
wnLSF = gbum(8);
dn = gbum(3);
dtLSF = gbsm(8);
t0g = gbum(8);
a0g = gbsm(16);
a1g = gbsm(12);
wn0g = gbum(6);
tow = gbum(20);
}
else if(wtype == 5) { // almanac1, containing 1.5 satellites
ioda = gbum(4);
alma1.wnalmanac = gbum(2);
alma1.t0almanac = gbum(10);
alma1.svid = gbum(6);
alma1.deltaSqrtA = gbsm(13);
alma1.e = gbum(11);
alma1.omega = gbum(16);
alma1.deltai = gbum(11);
alma1.Omega0 = gbum(16);
alma1.Omegadot = gbum(11);
alma1.M0 = gbum(16);
alma1.af0 = gbsm(16);
alma1.af1 = gbsm(13);
alma1.e5ahs = gbum(2);
alma2.svid = gbum(6);
alma2.deltaSqrtA = gbsm(13);
alma2.e = gbum(11);
alma2.omega = gbum(16);
alma2.deltai = gbum(11);
alma2.Omega0 = gbum(4);
// omega02 .. is partial
}
else if(wtype == 6) { // almanac2, containing 1.5 satellites
ioda = gbum(4);
alma2.Omega0 = gbum(12); // PARTIAL, does not really work like this
alma2.Omegadot = gbum(11);
alma2.M0 = gbum(16);
alma2.af0 = gbsm(16);
alma2.af1 = gbsm(13);
alma2.e5ahs = gbum(2);
alma3.svid = gbum(6);
alma3.deltaSqrtA = gbsm(13);
alma3.e = gbum(11);
alma3.omega = gbum(16);
alma3.deltai = gbum(11);
alma3.Omega0 = gbum(4);
alma3.M0 = gbum(16);
alma3.af0 = gbsm(16);
alma3.af1 = gbsm(13);
alma3.e5ahs = gbum(2);
}
return wtype;
}

View file

@ -2,52 +2,44 @@
#include <stdint.h>
#include <string>
#include <vector>
#include <map>
#include <functional>
#include "ephemeris.hh"
#include "bits.hh"
bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t *wn);
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn);
struct GalileoMessage : GPSLikeEphemeris
{
uint8_t wtype;
typedef void (GalileoMessage::*func_t)(const std::vector<uint8_t>& page);
std::map<int, func_t> parsers{
{0, &GalileoMessage::parse0},
{1, &GalileoMessage::parse1},
{2, &GalileoMessage::parse2},
{3, &GalileoMessage::parse3},
{4, &GalileoMessage::parse4},
{5, &GalileoMessage::parse5},
{6, &GalileoMessage::parse6},
{7, &GalileoMessage::parse7},
{8, &GalileoMessage::parse8},
{9, &GalileoMessage::parse9},
{10, &GalileoMessage::parse10},
{16, &GalileoMessage::parse16},
{17, &GalileoMessage::parseRS},
{18, &GalileoMessage::parseRS},
{19, &GalileoMessage::parseRS},
{20, &GalileoMessage::parseRS}
};
typedef void (GalileoMessage::*func_t)(std::basic_string_view<uint8_t> page);
std::vector<func_t> parsers{
&GalileoMessage::parse0,
&GalileoMessage::parse1,
&GalileoMessage::parse2,
&GalileoMessage::parse3,
&GalileoMessage::parse4,
&GalileoMessage::parse5,
&GalileoMessage::parse6,
&GalileoMessage::parse7,
&GalileoMessage::parse8,
&GalileoMessage::parse9,
&GalileoMessage::parse10
};
int parse(const std::vector<uint8_t>& page)
int parse(std::basic_string_view<uint8_t> page)
{
wtype = getbitu(&page[0], 0, 6);
if(!parsers.count(wtype)) {
if(wtype >= parsers.size()) {
// std::cerr<<"Asked for impossible galileo type "<<(int)wtype<<std::endl;
return wtype;
}
std::invoke(parsers[wtype], this, page);
std::invoke(parsers.at(wtype), this, page);
return wtype;
}
int parseFnav(const std::vector<uint8_t>& page);
uint8_t sparetime{0};
uint16_t wn{0};
uint32_t tow{0};
@ -58,7 +50,7 @@ struct GalileoMessage : GPSLikeEphemeris
// spare word, only contains a WN and a TOW, but only if the 'time' field is set to 2
void parse0(const std::vector<uint8_t>& page)
void parse0(std::basic_string_view<uint8_t> page)
{
sparetime = getbitu(&page[0], 6, 2);
if(sparetime == 2) {
@ -71,13 +63,13 @@ struct GalileoMessage : GPSLikeEphemeris
{
}
uint8_t e5ahs{0}, e5bhs{0}, e1bhs{0};
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 e5advs{false}, e5bdvs{false}, e1bdvs{false};
bool e5bdvs{false}, e1bdvs{false};
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
//
@ -107,29 +99,17 @@ struct GalileoMessage : GPSLikeEphemeris
uint16_t iodnav;
int16_t deltaAred; // 2^8 meters
int16_t exred; // 2^-22 dimensionless
int16_t eyred; // 2^-22 dimensionless
int32_t deltai0red; // 2^-22 semi-circles
int32_t omega0red; // 2^-22 semi-circles
int32_t lambda0red; // 2^-22 semi-circles
int32_t af0red; // 2^-26 s
int32_t af1red; // 2^-35 s/s
uint8_t rs2bitiod;
std::string rsparity;
int getIOD() const
{
return iodnav;
}
int ioda{-1}; // iod almanac
struct Almanac
{
int svid{-1};
int t0almanac, wnalmanac;
int af0, af1;
int e1bhs, e5bhs, e5ahs;
int e1bhs, e5bhs;
uint32_t e, deltaSqrtA;
int32_t M0, Omega0, deltai, omega, Omegadot;
@ -164,7 +144,7 @@ struct GalileoMessage : GPSLikeEphemeris
// an ephemeris word
void parse1(const std::vector<uint8_t>& page)
void parse1(std::basic_string_view<uint8_t> page)
{
iodnav = getbitu(&page[0], 6, 10);
t0e = getbitu(&page[0], 16, 14);
@ -174,7 +154,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// another ephemeris word
void parse2(const std::vector<uint8_t>& page)
void parse2(std::basic_string_view<uint8_t> page)
{
iodnav = getbitu(&page[0], 6, 10);
omega0 = getbits(&page[0], 16, 32);
@ -184,7 +164,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// yet another ephemeris word
void parse3(const std::vector<uint8_t>& page)
void parse3(std::basic_string_view<uint8_t> page)
{
iodnav = getbitu(&page[0], 6, 10);
omegadot = getbits(&page[0], 16, 24);
@ -242,9 +222,7 @@ struct GalileoMessage : GPSLikeEphemeris
// pair of nanosecond, nanosecond/s
std::pair<double, double> getGPSOffset(int tow, int wn) const
{
int dw = (int)(wn%64) - (int)(wn0g%64);
if(dw > 31)
dw = dw - 64;
int dw = (int)(uint8_t)wn - (int)(uint8_t) wn0g;
int delta = dw*7*86400 + tow - getT0g(); // NOT ephemeris age tricks
// 2^-35 2^-51 3600
@ -264,7 +242,7 @@ struct GalileoMessage : GPSLikeEphemeris
// can't get enough of that ephemeris
void parse4(const std::vector<uint8_t>& page)
void parse4(std::basic_string_view<uint8_t> page)
{
iodnav = getbitu(&page[0], 6, 10);
cic = getbits(&page[0], 22, 16);
@ -277,7 +255,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// ionospheric disturbance, health, group delay, time
void parse5(const std::vector<uint8_t>& page)
void parse5(std::basic_string_view<uint8_t> page)
{
ai0 = getbitu(&page[0], 6, 11);
ai1 = getbits(&page[0], 17, 11); // ai1 & 2 are signed, 0 not
@ -288,8 +266,8 @@ struct GalileoMessage : GPSLikeEphemeris
sf3 = getbitu(&page[0], 44, 1);
sf4 = getbitu(&page[0], 45, 1);
sf5 = getbitu(&page[0], 46, 1);
BGDE1E5a = getbits(&page[0], 47, 10); // 2^-32 s
BGDE1E5b = getbits(&page[0], 57, 10); // 2^-32 s
BGDE1E5a = getbits(&page[0], 47, 10);
BGDE1E5b = getbits(&page[0], 57, 10);
e5bhs = getbitu(&page[0], 67, 2);
e1bhs = getbitu(&page[0], 69, 2);
@ -300,7 +278,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// time stuff
void parse6(const std::vector<uint8_t>& page)
void parse6(std::basic_string_view<uint8_t> page)
{
a0 = getbits(&page[0], 6, 32);
a1 = getbits(&page[0], 38, 24);
@ -315,7 +293,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// almanac
void parse7(const std::vector<uint8_t>& page)
void parse7(std::basic_string_view<uint8_t> page)
{
iodalmanac = getbitu(&page[0], 6, 4);
alma1.wnalmanac = wnalmanac = getbitu(&page[0], 10, 2);
@ -331,7 +309,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// almanac
void parse8(const std::vector<uint8_t>& page)
void parse8(std::basic_string_view<uint8_t> page)
{
iodalmanac = getbitu(&page[0], 6, 4);
alma1.af0 = getbits(&page[0], 10, 16);
@ -350,7 +328,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// almanac
void parse9(const std::vector<uint8_t>& page)
void parse9(std::basic_string_view<uint8_t> page)
{
iodalmanac = getbitu(&page[0], 6, 4);
alma2.wnalmanac = wnalmanac = getbitu(&page[0], 10, 2);
@ -371,7 +349,7 @@ struct GalileoMessage : GPSLikeEphemeris
}
// almanac + more time stuff (GPS)
void parse10(const std::vector<uint8_t>& page)
void parse10(std::basic_string_view<uint8_t> page)
{
iodalmanac = getbitu(&page[0], 6, 4);
alma3.Omega0 = getbits(&page[0], 10, 16);
@ -389,35 +367,6 @@ struct GalileoMessage : GPSLikeEphemeris
wn0g = getbitu(&page[0], 122, 6);
}
// reduced clock and ephemeris data (redced)
void parse16(const std::vector<uint8_t>& page)
{
deltaAred = getbits(&page[0], 6, 5);
exred = getbits(&page[0], 11, 13);
eyred = getbits(&page[0], 24, 13);
deltai0red = getbits(&page[0], 37, 17);
omega0red = getbits(&page[0], 54, 23);
lambda0red = getbits(&page[0], 77, 23);
af0red = getbits(&page[0], 100, 22);
af1red = getbits(&page[0], 122, 6);
}
// reed-solomon data
void parseRS(const std::vector<uint8_t>& page)
{
// see 5.1.13.2 of the Galileo SIS ICD 2.0
rs2bitiod = getbitu(&page[0], 6+8, 2);
rsparity.clear();
rsparity.append(1, getbitu(&page[0], 6, 8)); // first octet is different
for(int n = 0; n < 14; ++n)
rsparity.append(1, getbitu(&page[0], 16+n*8, 8));
}
double getMu() const
{
return 3.986004418 * pow(10.0, 14.0);
@ -441,53 +390,6 @@ struct GalileoMessage : GPSLikeEphemeris
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
};
struct REDCEDAdaptor
{
REDCEDAdaptor(const GalileoMessage& gm, int32_t t0r) : d_gm(gm), d_t0r(t0r)
{}
double getMu() const
{
return 3.986004418 * pow(10.0, 14.0);
} // 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 d_t0r; }
static constexpr double Anominal{29600000.0};
double getSqrtA() const { return sqrt(Anominal + ldexp(1.0*d_gm.deltaAred, 8)); }
double getE() const { return ldexp(sqrt(1.0*d_gm.exred*d_gm.exred + 1.0*d_gm.eyred*d_gm.eyred), -22); }
double getCuc() const { return 0; } // radians
double getCus() const { return 0; } // radians
double getCrc() const { return 0; } // meters
double getCrs() const { return 0; } // meters
double getM0() const { return ldexp(M_PI * d_gm.lambda0red, -22) - getOmega(); } // lambda0red - omega, both radians
double getDeltan()const { return 0; } //radians/s
static constexpr double iNominal{56.0};
double getI0() const { return M_PI*iNominal/180.0 + ldexp(d_gm.deltai0red * M_PI, -22); } // radians
double getCic() const { return 0; } // radians
double getCis() const { return 0; } // radians
double getOmegadot() const { return 0; } // radians/s
double getOmega0() const { return ldexp(d_gm.omega0red * M_PI, -22); } // radians
double getIdot() const { return 0; } // radians/s
double getOmega() const { return atan2(d_gm.eyred, d_gm.exred); } // radians
// pair of nanosecond, nanosecond/s
std::pair<double, double> getAtomicOffset(int tow) const
{
int delta = ephAge(tow, d_t0r);
// 2^-26 2^-35
double cur = d_gm.af0red + ldexp(1.0*delta*d_gm.af1red, -9);
double trend = ldexp(d_gm.af1red, -9);
// now in units of 2^-26 seconds, which are ~14.9 nanoseconds each
double factor = ldexp(1000000000.0, -26);
return {factor * cur, factor * trend};
}
const GalileoMessage d_gm;
int32_t d_t0r;
};

View file

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

View file

@ -3,7 +3,6 @@
#include <string.h>
#include <chrono>
#include <iostream>
#include "navmon.hh"
using std::cout;
using std::endl;
@ -13,7 +12,7 @@ static const double J2 = 1082625.75E-9; // IERS: 1.0826359
static const double oe = 7.2921151467E-5; // rad/s // IERS: 7.292115
// this strips out spare bits + parity, and leaves 10 clean 24 bit words
std::vector<uint8_t> getGlonassMessage(const std::vector<uint8_t>& payload)
std::basic_string<uint8_t> getGlonassMessage(std::basic_string_view<uint8_t> payload)
{
uint8_t buffer[4*4];
@ -21,7 +20,7 @@ std::vector<uint8_t> getGlonassMessage(const std::vector<uint8_t>& payload)
setbitu(buffer, 32*w, 32, getbitu(&payload[0], w*32, 32));
}
return makeVec(buffer, 16);
return std::basic_string<uint8_t>(buffer, 16);
}
@ -111,16 +110,16 @@ static double passedMsec(const Clock::time_point& then, const Clock::time_point&
return std::chrono::duration_cast<std::chrono::microseconds>(now - then).count()/1000.0;
}
#if 0
static double passedMsec(const Clock::time_point& then)
{
return passedMsec(then, Clock::now());
}
#endif
double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
{
// auto start = Clock::now();
auto start = Clock::now();
double y0[6] = {ldexp(eph.x, -11), ldexp(eph.y, -11), ldexp(eph.z, -11),
ldexp(eph.dx, -20), ldexp(eph.dy, -20), ldexp(eph.dz, -20)};
@ -138,7 +137,7 @@ double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
rk4step (A, y0, h);
*p = Point (1E3*y0[0], 1E3*y0[1], 1E3*y0 [2]);
// static double total=0;
static double total=0;
// cout<<"Took: "<<(total+=passedMsec(start))<<" ms" <<endl;
return 0;
}

View file

@ -4,17 +4,14 @@
#include "bits.hh"
#include <iostream>
#include <math.h>
#include <stdint.h>
#include <vector>
#include "minivec.hh"
std::vector<uint8_t> getGlonassessage(const std::vector<uint8_t>& payload);
std::basic_string<uint8_t> getGlonassessage(std::basic_string_view<uint8_t> payload);
struct GlonassMessage
{
uint8_t strtype;
int parse(const std::vector<uint8_t>& gstr)
int parse(std::basic_string_view<uint8_t> gstr)
{
strtype = getbitu(&gstr[0], 1, 4);
if(strtype == 1) {
@ -62,7 +59,7 @@ struct GlonassMessage
double getRadius() { return sqrt(getX()*getX() + getY()*getY() + getZ()*getZ()); }
void parse1(const std::vector<uint8_t>& gstr)
void parse1(std::basic_string_view<uint8_t> gstr)
{
hour = getbitu(&gstr[0], 9, 5);
minute = getbitu(&gstr[0], 14, 6);
@ -80,7 +77,7 @@ struct GlonassMessage
An interval is 15 minutes long, plus a spacer of length described by P1. If P1 is zero, there is no spacer.
*/
void parse2(const std::vector<uint8_t>& gstr)
void parse2(std::basic_string_view<uint8_t> gstr)
{
Bn = getbitu(&gstr[0], 85-80, 3); // Health bit, only look at MSB, ignore the rest. 0 is ok.
Tb = getbitu(&gstr[0], 85-76, 7);
@ -95,7 +92,7 @@ struct GlonassMessage
bool l_n;
bool P, P3;
uint16_t gamman;
void parse3(const std::vector<uint8_t>& gstr)
void parse3(std::basic_string_view<uint8_t> gstr)
{
z = getbitsglonass(&gstr[0], 85-35, 27); // 2^-11
dz = getbitsglonass(&gstr[0], 85-64, 24); // 2^-20
@ -123,7 +120,7 @@ struct GlonassMessage
return 1000*ldexp(1000000.0*taun, -30);
}
void parse4(const std::vector<uint8_t>& gstr)
void parse4(std::basic_string_view<uint8_t> gstr)
{
NT = getbitu(&gstr[0], 85-26, 11);
FT = getbitu(&gstr[0], 85-33, 4);
@ -159,7 +156,7 @@ struct GlonassMessage
int32_t taugps;
int32_t tauc;
void parse5(const std::vector<uint8_t>& gstr)
void parse5(std::basic_string_view<uint8_t> gstr)
{
n4=getbitu(&gstr[0], 85-36, 5);
taugps = getbitsglonass(&gstr[0], 85-31, 22);
@ -177,7 +174,7 @@ struct GlonassMessage
return ldexp(tlambdana, -5);
}
void parse7_9_11_13_15(const std::vector<uint8_t>& gstr)
void parse7_9_11_13_15(std::basic_string_view<uint8_t> gstr)
{
l_n = getbitu(&gstr[0], 85 - 9, 1);
omegana = getbitsglonass(&gstr[0], 85-80, 16);
@ -209,7 +206,7 @@ struct GlonassMessage
return M_PI*63.0/180 + ldexp(M_PI* deltaina, -20);
}
void parse6_8_10_12_14(const std::vector<uint8_t>& gstr)
void parse6_8_10_12_14(std::basic_string_view<uint8_t> gstr)
{
CnA = getbitu(&gstr[0], 85-80, 1);
nA = getbitu(&gstr[0], 85-77, 5);

View file

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

14
gps.cc
View file

@ -1,20 +1,20 @@
#include "gps.hh"
// this strips out spare bits + parity, and leaves 10 clean 24 bit words
std::vector<uint8_t> getCondensedGPSMessage(const std::vector<uint8_t>& payload)
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload)
{
uint8_t buffer[10*24/8];
// ingests 32 bit words, per word ignores first 2 bits, and then takes 24
for(int w = 0 ; w < 10; ++w) {
setbitu(buffer, 24*w, 24, getbitu(&payload[0], 2 + w*32, 24));
}
return std::vector<uint8_t>(buffer, buffer+30);
return std::basic_string<uint8_t>(buffer, 30);
}
// expects input as 24 bit read to to use messages, returns frame number
int GPSState::parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr)
int GPSState::parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr)
{
using namespace std;
int frame = getbitu(&cond[0], 24+19, 3);
@ -38,8 +38,6 @@ int GPSState::parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr
wn = 2048 + getbitu(&cond[0], 2*24, 10);
ura = getbitu(&cond[0], 2*24+12, 4);
gpshealth = getbitu(&cond[0], 2*24+16, 6);
iodc = getbitu(&cond[0], 2*24 +22, 2) * 256;
iodc += getbitu(&cond[0], 7*24, 8);
// cerr<<"GPS Week Number: "<< wn <<", URA: "<< (int)ura<<", health: "<<
// (int)gpshealth <<endl;
@ -98,12 +96,8 @@ int GPSState::parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr
t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
wn0t = getbitu(&cond[0], 7*24 + 16, 8);
dtLS = getbits(&cond[0], 8*24, 8);
wnLSF= getbitu(&cond[0], 8*24 + 8, 8);
dn = getbitu(&cond[0], 8*24 + 16, 8);
dtLSF = getbits(&cond[0], 9*24, 8);
// cerr<<": a0: "<<a0<<", a1: "<<a1<<", t0t: "<< t0t * (1<<12) <<", wn0t: "<< wn0t<<", rough offset: "<<ldexp(a0, -30)<<endl;
// cerr<<"deltaTLS: "<< (int)dtLS<<", post "<< (int)dtLSF<<endl;

7
gps.hh
View file

@ -6,9 +6,8 @@
#include <iostream>
#include <math.h>
#include "ephemeris.hh"
#include <vector>
std::vector<uint8_t> getCondensedGPSMessage(const std::vector<uint8_t>& payload);
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload);
struct GPSAlmanac : GPSLikeEphemeris
@ -134,17 +133,15 @@ struct GPSState : GPSLikeEphemeris
uint16_t wnLSF{0};
uint8_t dn; // leap second day number
// 1 2^-31 2^-43 2^-55 16 second
int iodc;
int ura;
int gpsiod{-1};
int getIOD() const
{
return gpsiod;
}
int parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr=0);
int parseGPSMessage(std::basic_string_view<uint8_t> cond, uint8_t* pageptr=0);
};
template<typename T>

View file

@ -1,7 +1,6 @@
#pragma once
#include <bitset>
#include <string>
#include <vector>
#include <map>
#include "bits.hh"
#include <iostream>
@ -186,7 +185,7 @@ std::pair<double, double> getGPSCNavUTCOffset(int tow, int wn, const T& eph)
template<typename T>
int parseGPSCNavMessage(const std::vector<uint8_t>& msg, T& out)
int parseGPSCNavMessage(std::basic_string_view<uint8_t> msg, T& out)
{
using namespace std;
int type = getbitu(&msg[0], 14, 6);

View file

@ -14,7 +14,7 @@ function maketable(str, arr)
enter().
append("tr");
var columns = ["sv", "iod", "eph-age-m", "orbit-disco", "time-disco", "sisa", "health", "alma-dist", "osnma", "impinav", "sources", "db", "rtcm-eph-delta-cm","rtcm-clock-dclock0", "prres", "elev", "last-seen-s"];
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "alma-dist", "delta-utc", "sources", "hqsources", "db", "rtcm-eph-delta-cm","prres", "elev", "last-seen-s"];
// append the header row
thead.append("tr")
@ -27,9 +27,6 @@ function maketable(str, arr)
return "ΔHz";
if(column == "rtcm-eph-delta-cm")
return "Δrtcm";
if(column == "rtcm-clock-dclock0")
return "Δclk";
if(column == "delta-gps")
return "ΔGPS ns";
if(column == "delta-utc")
@ -61,7 +58,7 @@ function maketable(str, arr)
else if(row["gnssid"] == 6)
img='ext/glo.png';
ret.value = '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
ret.value = '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
// ret.value="";
ret.value += "&nbsp;<a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
}
@ -71,18 +68,6 @@ function maketable(str, arr)
else
ret.value="";
}
else if(column == "rtcm-clock-dclock0") {
if(row[column] != null) {
if(Math.abs(row[column]) > 150)
ret.color="#ff2222";
else if(Math.abs(row[column]) > 100)
ret.color="#ff4444";
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"];
@ -130,19 +115,6 @@ function maketable(str, arr)
else if(column == "norad") {
ret.value = row["best-tle-norad"];
}
else if(column == "osnma" && row["osnma"] != null) {
if(row["osnma"]==true)
ret.value="✅";
else
ret.value="";
}
else if(column == "impinav" && row["impinav"] != null) {
if(row["impinav"]==true)
ret.value="✅";
else
ret.value="";
}
else if(column == "delta-utc" && row["delta-utc"] != null) {
ret.value = row["delta-utc"]+'<span class="CellComment">a0: '+row["a0"]+'<br/>a1: '+row["a1"]+'<br/>wn0t: ' + row["wn0t"]+'<br/>t0t: '+row["t0t"]+'</span>';
ret.Class = 'CellWithComment';
@ -173,7 +145,7 @@ function maketable(str, arr)
ret.color="red";
}
}
else if(column == "orbit-disco" && row[column] != null)
else if(column == "latest-disco" && row[column] != null)
ret.value = ((100*row[column]).toFixed(1))+" cm";
else if(column == "time-disco" && row[column] != null)
ret.value = row[column].toFixed(1)+" ns";
@ -301,8 +273,6 @@ function updateSats()
let wantIt = false;
if(d3.select("#GalE1").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 1)
wantIt = true;
if(d3.select("#GalE5a").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 6)
wantIt = true;
if(d3.select("#GalE5b").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 5)
wantIt = true;
if(d3.select("#GPSL1CA").property("checked") && arr[n].gnssid==0 && arr[n].sigid == 0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

View file

@ -4,14 +4,12 @@
<meta charset="utf-8">
<title>galmon.eu</title>
<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>. 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;
<input type="checkbox" id="GalE5a" onclick="updateSats();"> <label for="GalE5a">Galileo E5a</label> &nbsp;&nbsp;
<input type="checkbox" id="GalE5b" onclick="updateSats();"> <label for="GalE5b">Galileo E5b</label> &nbsp;&nbsp;
<input type="checkbox" id="BeiDouB1I" onclick="updateSats();"> <label for="BeiDouB1I">BeiDou B1I</label> &nbsp;&nbsp;
<input type="checkbox" id="BeiDouB2I" onclick="updateSats();"> <label for="BeiDouB2I">BeiDou B2I</label> &nbsp;&nbsp;
@ -21,7 +19,6 @@
<input type="checkbox" id="GPSL2C" onclick="updateSats();"> <label for="GPSL2C">GPS L2C</label>
</div>
<hr/>
<a rel="me" href="https://bot.country/@GNSS_Changes">Mastodon</a>
<table id="svs"></table>
<hr>
<p>
@ -30,14 +27,16 @@
Stale:<br/>
<table id="svsstale"></table>
<p>
Source code of this website &amp; the whole system is available on
<a href="https://github.com/berthubert/galmon">GitHub</a>.
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States, Tonga, Brazil, Singapore, Austria, India and Uruguay.
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
</p>
<p>
Join us on our IRC channel (chat) via the
Some technical detail behind this setup can be found in <a href="https://berthub.eu/articles/posts/galileo-notes/">this post</a>.
</p>
<p>
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.
There is also a LIVE <a href="https://bot.country/@GNSS_Changes">bot on
Mastodon</a> that posts updates on relevant satellite changes.
</p>
<p>
The meaning of the fields is explained in <a href="https://berthub.eu/articles/posts/gps-gnss-how-do-they-work/">this document</a> and can be summarised as follows:
@ -62,20 +61,19 @@
<tr><td>lastseens</td><td>Time since we've last received from this SV.</td></tr>
</table>
<p>
The official Galileo constellation status can be found on the <a href="https://www.gsc-europa.eu/system-service-status/constellation-information">European GNSS Service Centre page</a>, which also lists "NAGUs", <a href="https://www.gsc-europa.eu/system-status/user-notifications">notifications about outages or changes</a>.
The official Galileo constellation status can be found on the <a href="https://www.gsc-europa.eu/system-status/Constellation-Information">European GNSS Service Centre page</a>, which also lists "NAGUs", <a href="https://www.gsc-europa.eu/system-status/user-notifications">notifications about outages or changes</a>.
</p>
<p>
Official GLONASS status can be found on <a href="https://www.glonass-iac.ru/en/sostavOG/">this page</a> from the Russian Information and Analysis Center for Positioning, Navigation and Timing.
Official GLONASS status can be found on <a href="https://www.glonass-iac.ru/en/GLONASS/">this page</a> from the Russian Information and Analysis Center for Positioning, Navigation and Timing.
</p>
<p>
Status updates on GPS can be found on <a
href="https://www.navcen.uscg.gov/gps-constellation">this page</a>.
Sadly reduced status updates on GPS can be found on <a href="https://www.navcen.uscg.gov/?Do=constellationStatus">this page</a> from the US Department of Homeland Security.
</p>
<p>
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&amp;ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.
</p>
<p>
Feedback is very welcome on bert@hubertnet.nl or <a href="https://twitter.com/bert_hu_bert">@bert_hu_bert</a>.
Feedback is very welcome on bert@hubertnet.nl or <a href="https://twitter.com/PowerDNS_Bert">@PowerDNS_Bert</a>.
</p>
<script src="d3.v4.min.js"></script>
<script src="ext/moment-with-locales.js"></script>

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ function makeTable(str, arr)
enter().
append("tr");
var columns= ["id", "last-seen", "latitude", "longitude", "owner", "remark", "serialno", "hwversion", "swversion", "impinav", "mods", "githash", "uptime", "clockdriftns", "clockacc", "freqacc", "h", "acc", "satellites"];
var columns= ["id", "last-seen", "latitude", "longitude", "owner", "remark", "serialno", "hwversion", "swversion", "mods", "githash", "uptime", "clockdriftns", "clockacc", "freqacc", "h", "acc", "satellites"];
// append the header row
thead.append("tr")
@ -41,12 +41,6 @@ function makeTable(str, arr)
if(column == "id") {
ret.value='<a href="observer.html?observer='+row[column]+'">'+row[column]+"</a>";
}
else if(column == "impinav") {
if(row["impinav"]==true)
ret.value="✅";
else
ret.value="";
}
else if(column == "last-seen") {
ret.value = moment(1000*row["last-seen"]).fromNow();
let lastSeen = moment(1000*row["last-seen"]);

View file

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

View file

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

View file

@ -1,276 +0,0 @@
var repeat;
moment.relativeTimeThreshold('m', 120);
function maketable(str, arr)
{
var table=d3.select(str);
table.html("");
var thead=table.append("thead");
var tbody=table.append("tbody");
var rows=tbody.selectAll("tr").
data(arr).
enter().
append("tr");
var columns = ["sv", "health", "sources", "db", "last-do-not-use", "last-seen-s"];
// append the header row
thead.append("tr")
.selectAll("th")
.data(columns)
.enter()
.append("th")
.html(function(column) {
if(column == "delta_hz_corr")
return "ΔHz";
if(column == "delta-gps")
return "ΔGPS ns";
if(column == "delta-utc")
return "ΔUTC ns";
if(column == "sources")
return '<a href="observers.html">sources</a>';
if(column == "alma-dist")
return '<a href="almanac.html">alma-dist</a>';
else
return column;
});
var cells = rows.selectAll("td").
data(function(row) {
return columns.map(function(column) {
var ret={};
ret.column = column;
ret.color=null;
ret.Class = null;
if(column == "last-do-not-use") {
if(row["last-type-0-s"] < 30*86400) {
var b = moment.duration(-row["last-type-0-s"], 's');
ret.value = b.humanize(true);
}
else ret.value="";
}
else if(column == "sv") {
var img="";
var sv = row["sv"];
var sbas="";
if(sv == 138 || sv == 131 || sv == 133) {
img = 'ext/gps.png';
sbas = "WAAS";
}
else if(sv== 126 || sv == 136 || sv == 123 ) {
img='ext/gal.png';
sbas = "EGNOS";
}
else if(sv == 140 || sv == 125 || sv == 141) {
img='ext/glo.png';
sbas = "SDCM";
}
else if(sv == 127 || sv == 128 || sv == 138) {
img='ext/gagan.png';
sbas ="GAGAN";
}
ret.value = sbas + "&nbsp;";
if(img != "")
ret.value += '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
else
ret.value += "";
// ret.value="";
ret.value += "&nbsp;<a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
}
else if(column == "aodc/e") {
if(row["aodc"] != null && row["aode"] != null)
ret.value = row["aodc"]+"/"+row["aode"];
else if(row["aode"] != null)
ret.value = row["aode"];
else
ret.value="";
}
else if(column == "eph-age-m") {
if(row["gnssid"]==0 && Math.abs(row[column]) > 120 && row["last-seen-s"] < 120)
ret.color="orange";
if(row["gnissid"]==2 && Math.abs(row[column]) > 60 && row["last-seen-s"] < 120)
ret.color="orange";
if(Math.abs(row[column]) >120 && row["last-seen-s"] < 120)
ret.color="#ff4444";
if(Math.abs(row[column]) > 4*60 && row["last-seen-s"] < 120)
ret.color="#ff2222";
if(row[column] != null) {
var b = moment.duration(-row[column], 'm');
ret.value = b.humanize(true);
}
else
ret.value="";
}
else if(row[column] != null && (column == "delta_hz_corr" || column =="delta_hz")) {
ret.value = row[column];
}
else if((column == "tle-dist")) {
if(row["best-tle-dist"] != null) {
ret.value = row["best-tle-dist"].toFixed(1) + " km";
if (Math.abs(row["best-tle-dist"]) > 10000)
ret.color="red";
else if (Math.abs(row["best-tle-dist"]) > 1000)
ret.color="#ffaaaa";
}
}
else if(column == "last-seen-s") {
var b = moment.duration(row[column], 's');
ret.value = b.humanize();
}
else if(column == "health") {
ret.value = "<small>"+row[column]+"</small>";
if(row["healthissue"] == 1) {
ret.color="orange";
}
if(row["healthissue"] == 2) {
ret.color="red";
}
}
else {
ret.value= row[column];
}
if(column == "sisa" && parseInt(row[column]) > 312)
ret.color="#ffaaaa";
var myRe = RegExp('[0-9]* m');
if(column == "sisa" && myRe.test(row[column]))
ret.color="#ff2222";
if(column == "sisa" && row[column]=="NO SISA AVAILABLE")
ret.color="#ff2222";
return ret;
})
})
.enter().append("td").attr("class", function(d) { return d.Class; }).html(function(d) { return d.value; }).attr("align", "right").style("background-color", function(d) {
return d.color;
});
}
var sats={};
var lastseen=null;
function updateSats()
{
var arr=[];
Object.keys(sats).forEach(function(e) {
var o = sats[e];
o.sv=e;
o.sources="";
o.db="";
o.elev="";
o.delta_hz_corr="";
o.sources="";
let prrestot = 0, prresnum=0, dbtot=0, hztot=0, hznum=0;
let mindb=1000, maxdb=0;
let minelev=90, maxelev=-1;
Object.keys(o.perrecv).forEach(function(k) {
if(o.perrecv[k]["last-seen-s"] < 30) {
o.sources += k+" ";
dbtot += o.perrecv[k].db;
if(o.perrecv[k].db != null) {
let db=o.perrecv[k].db;
if(db > maxdb)
maxdb = db;
if(db <mindb)
mindb =db;
}
if(o.perrecv[k].elev != null) {
let elev=o.perrecv[k].elev;
if(elev > maxelev)
maxelev = elev;
if(elev <minelev)
minelev =elev;
}
}
});
if(mindb != 1000)
o.db = mindb+" - " +maxdb;
else
o.db = "-";
if(maxelev != -1)
o.elev = minelev.toFixed(0)+" - " +maxelev.toFixed(0);
else
o.elev = "-";
if(o.hqsources > 2) {
if(prresnum)
o.prres = (prrestot / prresnum).toFixed(2);
else
o.prres ="-";
if(hznum)
o.delta_hz_corr = (hztot / hznum).toFixed(2);
else
o.delta_hz_corr ="-";
}
arr.push(o);
});
// sort it on SV
arr.sort(function(a, b) {
if(Number(a.sv) < Number(b.sv))
return -1;
if(Number(b.sv) < Number(a.sv))
return 1;
return 0;
});
var livearr=[], stalearr=[];
for(n = 0 ; n < arr.length; n++)
{
if(arr[n]["last-seen-s"] < 600)
livearr.push(arr[n]);
else
stalearr.push(arr[n]);
}
maketable("#sbas", livearr);
maketable("#sbasstale", stalearr);
}
function update()
{
var seconds = 2;
clearTimeout(repeat);
repeat=setTimeout(update, 1000.0*seconds);
if(lastseen != null)
d3.select("#freshness").html(lastseen.fromNow());
d3.json("global.json", function(d) {
lastseen = moment(1000*d["last-seen"]);
d3.select("#freshness").html(lastseen.fromNow());
});
d3.json("sbas.json", function(d) {
// put data in an array
sats=d;
updateSats();
});
}
repeat=update();

View file

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

View file

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

View file

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

View file

@ -13,8 +13,9 @@ InfluxPusher::InfluxPusher(std::string_view dbname) : d_dbname(dbname)
void InfluxPusher::queueValue(const std::string& line)
{
if(!d_buffer.insert(line).second)
d_numdedupmsmts++;
d_buffer.insert(line);
// if(d_buffer.insert(line).second)
// cout<<line;
checkSend();
}
@ -40,9 +41,6 @@ void InfluxPusher::addValueObserver(int src, string_view name, const initializer
buffer+= " ";
bool lefirst=true;
for(const auto& v : values) {
if(!v.first) // trick to null out certain fields
continue;
d_numvalues++;
if(!lefirst) {
buffer +=",";
}
@ -50,33 +48,18 @@ void InfluxPusher::addValueObserver(int src, string_view name, const initializer
buffer += string(v.first) + "="+to_string(v.second);
}
buffer += " " + to_string((uint64_t)(t* 1000000000))+"\n";
d_nummsmts++;
d_msmtmap[(string)name]++;
queueValue(buffer);
}
void InfluxPusher::addValue(const SatID& id, string_view name, const initializer_list<pair<const char*, var_t>>& values, double t, std::optional<int> src, std::optional<string> tag)
{
vector<pair<string,var_t>> tags{{"sv", id.sv}, {"gnssid", id.gnss}, {"sigid", id.sigid}};
if(src) {
tags.push_back({*tag, *src});
}
addValue(tags, name, values, t);
}
void InfluxPusher::addValue(const vector<pair<string,var_t>>& tags, string_view name, const initializer_list<pair<const char*, var_t>>& values, double t)
{
if(d_mute)
return;
if(t > 2200000000 || t < 0) {
cerr<<"Unable to store item "<<name<<" for ";
// for(const auto& t: tags)
// cerr<<t.first<<"="<<t.second<<" ";
cerr<<": time out of range "<<t<<endl;
cerr<<"Unable to store item "<<name<<" for sv "<<id.gnss<<","<<id.sv<<": time out of range "<<t<<endl;
return;
}
for(const auto& p : values) {
@ -85,29 +68,13 @@ void InfluxPusher::addValue(const vector<pair<string,var_t>>& tags, string_view
return;
}
string buffer = string(name);
for(const auto& t : tags) {
buffer += ","+t.first + "=";
std::visit([&buffer](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, string>) {
// string tags really don't need a "
buffer += arg;
}
else {
// resist the urge to do integer tags, it sucks
buffer += to_string(arg);
}
}, t.second);
}
string buffer = string(name) +",gnssid="+to_string(id.gnss)+",sv=" +to_string(id.sv)+",sigid="+to_string(id.sigid);
if(src)
buffer += ","+*tag+"="+to_string(*src);
buffer+= " ";
bool lefirst=true;
for(const auto& v : values) {
if(!v.first) // trick to null out certain fields
continue;
d_numvalues++;
if(!lefirst) {
buffer +=",";
}
@ -116,19 +83,14 @@ void InfluxPusher::addValue(const vector<pair<string,var_t>>& tags, string_view
std::visit([&buffer](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, string>)
buffer += "\""+arg+"\"";
else {
buffer += to_string(arg);
if constexpr (!std::is_same_v<T, double>)
buffer+="i";
}
buffer += to_string(arg);
if constexpr (!std::is_same_v<T, double>)
buffer+="i";
}, v.second);
}
buffer += " " + to_string((uint64_t)(t*1000000000))+"\n";
d_nummsmts++;
d_msmtmap[(string)name]++;
queueValue(buffer);
}

View file

@ -8,13 +8,11 @@
struct InfluxPusher
{
typedef std::variant<double, int32_t, uint32_t, int64_t, string> var_t;
typedef std::variant<double, int32_t, uint32_t, int64_t> var_t;
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*, var_t>>& values, double t, std::optional<int> src = std::optional<int>(), std::optional<string> tag = std::optional<string>("src"));
void addValue(const vector<pair<string, var_t>>& tags, string_view name, const initializer_list<pair<const char*, var_t>>& values, double t);
void checkSend();
void doSend(const std::set<std::string>& buffer);
~InfluxPusher();
@ -24,8 +22,4 @@ struct InfluxPusher
time_t d_lastsent{0};
string d_dbname;
bool d_mute{false};
int64_t d_nummsmts{0};
int64_t d_numvalues{0};
int64_t d_numdedupmsmts{0};
map<std::string, int64_t> d_msmtmap;
};

View file

@ -52,8 +52,7 @@ MiniCurl::MiniCurl(const string& useragent)
MiniCurl::~MiniCurl()
{
if(d_host_list)
curl_slist_free_all(d_host_list);
// NEEDS TO CLEAN HOSTLIST
curl_easy_cleanup(d_curl);
}
@ -66,7 +65,7 @@ size_t MiniCurl::write_callback(char *ptr, size_t size, size_t nmemb, void *user
using namespace std;
string extractHostFromURL(const std::string& url)
static string extractHostFromURL(const std::string& url)
{
auto pos = url.find("://");
if(pos == string::npos)
@ -82,10 +81,7 @@ string extractHostFromURL(const std::string& url)
void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
{
if(rem) {
if(d_host_list) {
curl_slist_free_all(d_host_list);
d_host_list = nullptr;
}
struct curl_slist *hostlist = nullptr; // THIS SHOULD BE FREED
// url = http://hostname.enzo/url
string host4=extractHostFromURL(str);
@ -101,76 +97,40 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const C
}
for (const auto& port : ports) {
string hcode = fmt::sprintf("%s:%u:[%s]", host4 , port , rem->toString());
// fmt::print("hcode: {}\n", hcode);
d_host_list = curl_slist_append(d_host_list, hcode.c_str());
string hcode = fmt::format("%s:%u:%s", host4 , port , rem->toString());
hostlist = curl_slist_append(hostlist, hcode.c_str());
}
curl_easy_setopt(d_curl, CURLOPT_RESOLVE, d_host_list);
curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
}
// should be a setting
curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, 1L);
if(src) {
curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString().c_str());
}
curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
/* only allow HTTP and HTTPS */
curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS_STR, "http,https");
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, true);
curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, false);
// curl_easy_setopt(d_curl, CURLOPT_FAILONERROR, true);
curl_easy_setopt(d_curl, CURLOPT_URL, str.c_str());
curl_easy_setopt(d_curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, this);
curl_easy_setopt(d_curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(d_curl, CURLOPT_CERTINFO, 1L);
curl_easy_setopt(d_curl, CURLOPT_FILETIME, 1L);
if(src) {
curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString().c_str());
// XXX report errors!!
// fmt::print("Setting interface to '{}', ret {}\n", src->toString().c_str(),
// ret);
}
clearHeaders();
d_data.clear();
}
std::string MiniCurl::getURL(const std::string& str, const bool nobody, MiniCurl::certinfo_t* ciptr, const ComboAddress* rem, const ComboAddress* src)
std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
{
setupURL(str, rem, src);
if (nobody)
curl_easy_setopt(d_curl, CURLOPT_NOBODY, 1L);
auto res = curl_easy_perform(d_curl);
if(d_host_list) {
curl_slist_free_all(d_host_list);
d_host_list = nullptr;
}
if(res != CURLE_OK) {
throw std::runtime_error("Unable to retrieve URL "+str+ " - "+string(curl_easy_strerror(res)));
}
long http_code = 0;
curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
d_filetime=-1;
curl_easy_getinfo(d_curl, CURLINFO_FILETIME, &d_filetime);
if(ciptr) {
struct curl_certinfo *ci;
res = curl_easy_getinfo(d_curl, CURLINFO_CERTINFO, &ci);
if(res) {
throw std::runtime_error(fmt::format("URL: {}, Error: {}\n", str, curl_easy_strerror(res)));
}
int i;
for(i = 0; i < ci->num_of_certs; i++) {
struct curl_slist *slist;
for(slist = ci->certinfo[i]; slist; slist = slist->next) {
string data = slist->data;
if(auto pos = data.find(':'); pos != string::npos)
(*ciptr)[i][data.substr(0, pos)] = data.substr(pos+1);
}
}
if(res != CURLE_OK || http_code != 200) {
throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
}
d_http_code = 0;
curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &d_http_code);
std::string ret=d_data;
d_data.clear();
return ret;

View file

@ -42,23 +42,16 @@ public:
MiniCurl(const string& useragent="MiniCurl/0.0");
~MiniCurl();
MiniCurl& operator=(const MiniCurl&) = delete;
typedef std::map<int, std::map<std::string, std::string>> certinfo_t;
std::string getURL(const std::string& str, const bool nobody=0, certinfo_t* ciptr=0, const ComboAddress* rem=0, const ComboAddress* src=0);
std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
std::string postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers);
std::string urlEncode(std::string_view str);
CURL *d_curl;
time_t d_filetime=-1;
long d_http_code=-1;
private:
std::string d_data;
CURL *d_curl;
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
std::string d_data;
struct curl_slist* d_header_list = nullptr;
struct curl_slist *d_host_list = nullptr;
void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
void setHeaders(const MiniCurlHeaders& headers);
void clearHeaders();
};
std::string extractHostFromURL(const std::string& url);

View file

@ -1,6 +1,5 @@
#pragma once
#include <math.h>
#include <iostream>
struct Point
{
@ -11,13 +10,6 @@ struct Point
double x, y, z;
};
inline std::ostream& operator<<(std::ostream& os, const Point& p)
{
os << '(' << p.x << ", " << p.y << ", " << p.z <<')';
return os;
}
struct Vector
{
Vector() : x{0}, y{0}, z{0} {}
@ -47,9 +39,4 @@ struct Vector
}
};
inline std::ostream& operator<<(std::ostream& os, const Vector& p)
{
os << '(' << p.x << ", " << p.y << ", " << p.z <<')';
return os;
}

173
navcat.cc
View file

@ -16,7 +16,7 @@
#include <dirent.h>
#include <inttypes.h>
#include "navmon.hh"
// #include <execution>
#include "CLI/CLI.hpp"
#include "version.hh"
@ -26,19 +26,40 @@ using namespace std;
extern const char* g_gitHash;
// get all stations (numerical) from a directory
static vector<uint64_t> getSources(string_view dirname)
time_t parseTime(std::string_view in)
{
shared_ptr<DIR> dir;
if(auto ptr = opendir(&dirname[0]))
dir=shared_ptr<DIR>(ptr, closedir);
else
time_t now=time(0);
vector<string> formats({"%Y-%m-%d %H:%M", "%Y%m%d %H%M", "%H:%M", "%H%M"});
for(const auto& f : formats) {
struct tm tm;
memset(&tm, 0, sizeof(tm));
localtime_r(&now, &tm);
tm.tm_isdst = -1;
tm.tm_sec = 0;
char* res = strptime(&in[0], f.c_str(), &tm);
if(res && !*res) {
cerr<<"Matched on "<<f<<endl;
return mktime(&tm);
}
}
throw runtime_error("Can only parse %Y-%m-%d %H:%M");
}
vector<uint64_t> getSources(string_view dirname)
{
DIR *dir = opendir(&dirname[0]);
if(!dir)
unixDie("Listing metrics from statistics storage "+(string)dirname);
struct dirent *result=0;
vector<uint64_t> ret;
for(;;) {
errno=0;
if(!(result = readdir(dir.get()))) {
if(!(result = readdir(dir))) {
closedir(dir);
if(errno)
unixDie("Reading directory entry "+(string)dirname);
else
@ -55,77 +76,53 @@ static vector<uint64_t> getSources(string_view dirname)
return ret;
}
static bool operator==(const timespec& a, const timespec& b)
{
return a.tv_sec == b.tv_sec && a.tv_nsec && b.tv_nsec;
}
// send protobuf data from the named directories and stations, between start and stoptime
void sendProtobuf(const vector<string>& dirs, vector<uint64_t> stations, time_t startTime, time_t stopTime=0)
void sendProtobuf(string_view dir, time_t startTime, time_t stopTime=0)
{
timespec start;
start.tv_sec = startTime;
start.tv_nsec = 0;
// so we have a ton of files, and internally these are not ordered
map<string,uint32_t> fpos;
vector<pair<timespec,string> > rnmms;
for(;;) {
auto srcs = getSources(dir);
rnmms.clear();
for(const auto& dir : dirs) {
cerr<<"Gathering data from "<<humanTime(start.tv_sec)<<" from "<<dir<<".. ";
vector<uint64_t> srcs = stations.empty() ? getSources(dir) : stations;
int count=0;
for(const auto& src: srcs) {
string fname = getPath(dir, start.tv_sec, src);
FILE* ptr = fopen(fname.c_str(), "r");
shared_ptr<FILE> fp;
if(ptr) {
fp = shared_ptr<FILE>(ptr, fclose);
}
else {
fname+=".zst";
struct stat statbuf;
if(stat(fname.c_str(), &statbuf) == 0) {
ptr = popen(("zstdcat "+fname).c_str(), "r");
if(!ptr)
continue;
fp = shared_ptr<FILE>(ptr, pclose);
}
else
continue;
}
string msg;
struct timespec ts;
unsigned int offset=0;
while(getRawNMM(fp.get(), ts, msg, offset)) {
// don't drop data that is only 5 seconds too old
if(make_pair(ts.tv_sec + 5, ts.tv_nsec) >= make_pair(start.tv_sec, start.tv_nsec)) {
rnmms.push_back({ts, msg});
++count;
}
}
// cerr<<"Harvested "<<rnmms.size()<<" events out of "<<looked<<endl;
// fp will close itself
for(const auto& src: srcs) {
string fname = getPath(dir, start.tv_sec, src);
FILE* fp = fopen(fname.c_str(), "r");
if(!fp)
continue;
uint32_t offset= fpos[fname];
if(fseek(fp, offset, SEEK_SET) < 0) {
cerr<<"Error seeking: "<<strerror(errno) <<endl;
fclose(fp);
continue;
}
cerr<<" added "<<count<<endl;
cerr <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
uint32_t looked=0;
string msg;
struct timespec ts;
while(getRawNMM(fp, ts, msg, offset)) {
// don't drop data that is only 5 seconds too old
if(make_pair(ts.tv_sec + 5, ts.tv_nsec) >= make_pair(start.tv_sec, start.tv_nsec)) {
rnmms.push_back({ts, msg});
}
++looked;
}
cerr<<"Harvested "<<rnmms.size()<<" events out of "<<looked<<endl;
fpos[fname]=offset;
fclose(fp);
}
// cerr<<"Sorting data"<<endl;
// sort(execution::par, rnmms.begin(), rnmms.end(), [](const auto& a, const auto& b)
sort(rnmms.begin(), rnmms.end(), [](const auto& a, const auto& b)
{
return std::tie(a.first.tv_sec, a.first.tv_nsec)
< std::tie(b.first.tv_sec, b.first.tv_nsec);
});
auto newend=unique(rnmms.begin(), rnmms.end());
cerr<<"Removed "<<rnmms.end() - newend <<" duplicates, ";
rnmms.erase(newend, rnmms.end());
cerr<<"sending data"<<endl;
unsigned int count=0;
for(const auto& nmm: rnmms) {
if(nmm.first.tv_sec > stopTime)
break;
@ -135,9 +132,7 @@ void sendProtobuf(const vector<string>& dirs, vector<uint64_t> stations, time_t
buf += nmm.second;
//fwrite(buf.c_str(), 1, buf.size(), stdout);
writen2(1, buf.c_str(), buf.size());
++count;
}
cerr<<"Done sending " << count<<" messages"<<endl;
if(3600 + start.tv_sec - (start.tv_sec%3600) < stopTime)
start.tv_sec = 3600 + start.tv_sec - (start.tv_sec%3600);
else {
@ -150,46 +145,32 @@ void sendProtobuf(const vector<string>& dirs, vector<uint64_t> stations, time_t
int main(int argc, char** argv)
{
bool doVERSION{false};
/*
CLI::App app(program);
string beginarg, endarg;
vector<string> storages;
int galwn{-1};
app.add_option("--storage,-s", storages, "Locations of storage files");
vector<uint64_t> stations;
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--begin,-b", beginarg, "Begin time (2020-01-01 00:00, or 12:30)");
app.add_option("--end,-e", endarg, "End time. Now if omitted");
app.add_option("--stations", stations, "only send data from listed stations");
app.add_option("--gal-wn", galwn, "Galileo week number to report on");
CLI11_PARSE(app, argc, argv);
app.add_flag("--version", doVERSION, "show program version and copyright");
app.allow_extras(true); // allow bare positional parameters
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
return app.exit(e);
}
if(doVERSION) {
showVersion(program, g_gitHash);
exit(0);
}
time_t startTime, stopTime;
if(galwn >=0) {
startTime=utcFromGST(galwn, 0);
stopTime=startTime + 7*86400;
}
else if(!beginarg.empty()) {
startTime = parseTime(beginarg);
stopTime = endarg.empty() ? time(0) : parseTime(endarg);
}
else {
cerr<<"No time range specified, use -b or --gal-wn"<<endl;
return 1;
*/
signal(SIGPIPE, SIG_IGN);
if(argc < 3) {
cout<<"Syntax: navcat storage start stop"<<endl;
cout<<"Example: ./navcat storage \"2020-01-01 00:00\" \"2020-01-02 00:00\" | ./navdump "<<endl;
return(EXIT_FAILURE);
}
time_t startTime = parseTime(argv[2]);
time_t stopTime = parseTime(argv[3]);
cerr<<"Emitting from "<<humanTime(startTime) << " to " << humanTime(stopTime) << endl;
if(!stations.empty()) {
cerr<<"Restricting to stations:";
for(const auto& s : stations)
cerr<<" "<<s;
cerr<<endl;
}
sendProtobuf(storages, stations, startTime, stopTime);
sendProtobuf(argv[1], startTime, stopTime);
}

View file

@ -188,7 +188,7 @@ int main(int argc, char** argv)
static map<int, GalileoMessage> gms;
GalileoMessage& gm = gms[nmm.gi().gnsssv()];
auto inav = makeVec((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
basic_string<uint8_t> inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
int wtype =gm.parse(inav);
wk.emitLine(sv, "src "+to_string(nmm.sourceid())+" wtype " + to_string(wtype));
// wk.setStatus(sv, "Hlth: "+std::to_string(getbitu(&inav[0], 67, 2)) +", el="+to_string(g_svstats[sv].el)+", db="+to_string(g_svstats[sv].db) );

View file

@ -3,7 +3,6 @@
#include <iostream>
#include <arpa/inet.h>
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/printf.h"
#include "CLI/CLI.hpp"
#include <fstream>
@ -26,14 +25,12 @@
#include "tle.hh"
#include "sp3.hh"
#include "ubx.hh"
#include <optional>
#include <unistd.h>
#include "sbas.hh"
#include "version.hh"
#include "gpscnav.hh"
#include "rinex.hh"
#include "rtcm.hh"
#include "fixhunter.hh"
static char program[]="navdump";
@ -260,22 +257,12 @@ try
bool doReceptionData{false};
bool doRFData{true};
bool doObserverPosition{false};
bool doObserverDetails{false};
bool doTimeOffsets{false};
bool doVERSION{false};
bool doJammingData{false};
string rinexfname;
string osnmafname;
app.add_option("--svs", svpairs, "Listen to specified svs. '0' = gps, '2' = Galileo, '2,1' is E01");
app.add_option("--stations", stations, "Listen to specified stations.");
app.add_option("--positions,-p", doObserverPosition, "Print out observer positions (or not)");
app.add_option("--rfdata,-r", doRFData, "Print out RF data (or not)");
app.add_option("--observerdetails,-o", doObserverDetails, "Print out observer detail data (or not)");
app.add_option("--timeoffsets,-t", doTimeOffsets, "Print out timeoffset data (or not)");
app.add_option("--recdata", doReceptionData, "Print out reception data (or not)");
app.add_option("--jamdata", doJammingData, "Print out jamming data (or not)");
app.add_option("--rinex", rinexfname, "If set, emit ephemerides to this filename");
app.add_option("--osnma", osnmafname, "If set, emit OSNMA CSV to this filename");
app.add_flag("--version", doVERSION, "show program version and copyright");
try {
@ -312,18 +299,7 @@ try
sp3csv<<"timestamp gnssid sv ephAge sp3X sp3Y sp3Z ephX ephY ephZ sp3Clock ephClock distance along clockDelta E speed"<<endl;
std::optional<RINEXNavWriter> rnw;
if(!rinexfname.empty())
rnw = RINEXNavWriter(rinexfname);
std::optional<ofstream> osnmacsv;
if(!osnmafname.empty()) {
osnmacsv = ofstream(osnmafname);
(*osnmacsv)<<"wn,tow,wtype,sv,osnma\n";
}
// RINEXNavWriter rnw("test.rnx");
for(;;) {
char bert[4];
int res = readn2(0, bert, 4);
@ -334,23 +310,7 @@ try
// I am so sorry
if(bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') {
cerr<<"Bad magic: "<<makeHexDump(string(bert, 4))<<endl;
int res;
for(int s=0;;++s) {
cerr<<"Skipping character hunting for good magic.. "<<s<<endl;
bert[0] = bert[1];
bert[1] = bert[2];
bert[2] = bert[3];
res = readn2(0, bert + 3, 1);
if(res != 1)
break;
if(bert[0]=='b' && bert[1]=='e' && bert[2] =='r' && bert[3]=='t')
break;
}
if(res != 1) {
cerr<<"EOF2, res = "<<res<<endl;
break;
}
cerr<<"Bad magic"<<endl;
}
uint16_t len;
@ -387,11 +347,9 @@ try
}
}
else if(nmm.type() == NavMonMessage::GalileoInavType) {
auto inav = makeVec((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
basic_string<uint8_t> inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
static map<int, GalileoMessage> gms;
static map<pair<int, int>, GalileoMessage> gmwtypes;
static map<int, FixHunter> fixhunters;
static map<int, GalileoMessage> oldgm4s;
int sv = nmm.gi().gnsssv();
if(!svfilter.check(2, sv, nmm.gi().sigid()))
@ -412,42 +370,21 @@ try
GalileoMessage& gm = gms[sv];
int wtype = gm.parse(inav);
if(wtype != 0 && wtype != 5 && wtype != 6)
gm.tow = nmm.gi().gnsstow();
bool isnew = gmwtypes[{nmm.gi().gnsssv(), wtype}].tow != gm.tow;
gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm;
// fixhunters[nmm.gi().gnsssv()].reportInav(inav,nmm.gi().gnsstow() );
gm.tow = nmm.gi().gnsstow();
gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm;
static map<int,GalileoMessage> oldEph;
cout << "gal inav wtype "<<wtype<<" for "<<nmm.gi().gnssid()<<","<<nmm.gi().gnsssv()<<","<<nmm.gi().sigid()<<" pbwn "<<nmm.gi().gnsswn()<<" pbtow "<< nmm.gi().gnsstow();
if(nmm.gi().has_ssp()) {
cout<<" ssp "<<(unsigned int) nmm.gi().ssp();
}
static uint32_t tow;
if(osnmacsv && isnew)
(*osnmacsv)<<nmm.gi().gnsswn()<<","<<gm.tow<<","<<wtype<<","<<nmm.gi().gnsssv()<<","<<makeHexDump(nmm.gi().reserved1())<<endl;
if(wtype >=1 && wtype <= 5) {
if(nmm.gi().has_reserved1()) {
cout<<" res1 "<<makeHexDump(nmm.gi().reserved1());
}
}
if(wtype == 4) {
// 2^-34 2^-46
cout <<" iodnav "<<gm.iodnav <<" af0 "<<gm.af0 <<" af1 "<<gm.af1 <<", af2 "<<(int)gm.af2<<" scaled: "<<1000000000.0*ldexp(1.0*gm.af0, 19-34)/(1<<19) <<" ns, "<<ldexp(1.0*gm.af1, 38-46);
cout << " t0g " << gm.t0g <<" a0g " << gm.a0g <<" a1g " << gm.a1g <<" WN0g " << gm.wn0g;
cout <<" iodnav "<<gm.iodnav <<" af0 "<<gm.af0 <<" af1 "<<gm.af1 <<", scaled: "<<ldexp(1.0*gm.af0, 19-34)<<", "<<ldexp(1.0*gm.af1, 38-46);
if(tow && oldgm4s.count(nmm.gi().gnsssv()) && oldgm4s[nmm.gi().gnsssv()].iodnav != gm.iodnav) {
auto& oldgm4 = oldgm4s[nmm.gi().gnsssv()];
auto oldOffset = oldgm4.getAtomicOffset(tow);
auto newOffset = gm.getAtomicOffset(tow);
cout<<" Timejump: "<<oldOffset.first - newOffset.first<<" ns after "<<(gm.getT0c() - oldgm4.getT0c() )<<" seconds";
cout<<" Timejump: "<<oldOffset.first - newOffset.first<<" after "<<(gm.getT0c() - oldgm4.getT0c() )<<" seconds";
}
oldgm4s[nmm.gi().gnsssv()] = gm;
@ -473,7 +410,7 @@ try
if(!haveSeen.count({sv, bestSP3->t})) {
haveSeen.insert({sv, bestSP3->t});
Point newPoint;
double E=getCoordinates(gm.tow + (bestSP3->t - start), gm, &newPoint);
double E=getCoordinates(gm.tow + (bestSP3->t - start), gm, &newPoint, false);
Point sp3Point(bestSP3->x, bestSP3->y, bestSP3->z);
Vector dist(newPoint, sp3Point);
@ -500,9 +437,7 @@ try
if(!oldEph[sv].sqrtA)
oldEph[sv] = gm;
else if(oldEph[sv].iodnav != gm.iodnav) {
if(rnw)
rnw->emitEphemeris(sid, gm);
// gm.toJSON();
// rnw.emitEphemeris(sid, gm);
cout<<" disco! "<< oldEph[sv].iodnav << " - > "<<gm.iodnav <<", "<< (gm.getT0e() - oldEph[sv].getT0e())/3600.0 <<" hours-jump insta-age "<<ephAge(gm.tow, gm.getT0e())/3600.0<<" hours";
Point oldPoint, newPoint;
@ -537,7 +472,7 @@ try
}
if(wtype == 0 || wtype == 5 || wtype == 6) {
if(wtype != 0 || gm.sparetime == 2) {
cout << " tow "<< gm.tow << " (%30=" << (gm.tow%30)<<") ";
cout << " tow "<< gm.tow;
tow = gm.tow;
}
}
@ -547,9 +482,7 @@ try
}
if(wtype == 6) {
cout<<" a0 " << gm.a0 <<" a1 " << gm.a1 <<" t0t "<<gm.t0t << " dtLS "<<(int)gm.dtLS;
cout <<" wnLSF "<< (unsigned int)gm.wnLSF<<" dn " << (unsigned int)gm.dn<< " dtLSF "<<(int)gm.dtLSF;
}
// if(wtype < 7)
// gm = GalileoMessage{};
@ -586,165 +519,11 @@ try
if(gm.alma3.e1bhs != 0) {
cout <<" gm.tow "<<gm.tow<<" gmwtypes.tow "<< gmwtypes[{sv,9}].tow <<" ";
}
cout<<" alma3.sv "<<gmwtypes[{sv,9}].alma3.svid <<" af0 "<<gm.alma3.af0<<" af1 "<< gm.alma3.af1 <<" e5bhs "<< gm.alma3.e5bhs<<" e1bhs "<< gm.alma3.e1bhs <<" a0g " << gm.a0g <<" a1g "<< gm.a1g <<" t0g "<<gm.t0g <<" wn0g "<<gm.wn0g <<" delta-gps "<< gm.getGPSOffset(gm.tow, gm.wn).first<<"ns";
int dw = (int)(gm.wn%64) - (int)(gm.wn0g);
if(dw > 31)
dw = 31- dw;
int delta = dw*7*86400 + gm.tow - gm.getT0g(); // NOT ephemeris age tricks
cout<<" wn%64 "<< (gm.wn%64) << " dw " << dw <<" delta " << delta;
cout<<" "<<gmwtypes[{sv,9}].alma3.svid <<" af0 "<<gm.alma3.af0<<" af1 "<< gm.alma3.af1 <<" e5bhs "<< gm.alma3.e5bhs<<" e1bhs "<< gm.alma3.e1bhs <<" a0g " << gm.a0g <<" a1g "<< gm.a1g <<" t0g "<<gm.t0g <<" wn0g "<<gm.wn0g;
}
else if(wtype==16) {
// was -35
cout<<" redced af0red "<< 1000000000.0*ldexp(gm.af0red, -26)<<" ns, "<<3600.0*(1000000000.0/(1<<20))*ldexp(gm.af1red, -15)<<" ns/hour ("<<gm.af1red<<")";
int32_t t0r = 1+nmm.gi().gnsstow() - ((nmm.gi().gnsstow()-2)%30) -2;
cout<<" t0r "<<t0r<<" ";
//(30*((nmm.gi().gnsstow()-2)/30)+1) % 604800; // page 56 of the OSS ICD 2.0
REDCEDAdaptor rca(gm, t0r);
#if 0
static REDCEDAdaptor* orig=0;
if(!orig)
orig = new REDCEDAdaptor(gm, t0r);
static auto ofs = fmt::output_file("error.csv");
static auto redcedcsv = fmt::output_file("redced.csv");
static bool csvInit;
if(!csvInit) {
ofs.print("{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
"tow", "gnssid", "iod", "t0r", "red_x", "red_y", "red_z", "full_x", "full_y", "full_z", "redorig_x", "redorig_y", "redorig_z", "dist");
redcedcsv.print("gnssid,tow,t0r,deltaAred,exred,eyred,lambda0red,deltai0red,omega0red,af0red,af1red,nsec,fixedtow,x,y,z,radcor\n");
csvInit = true;
}
auto fixedtow = t0r + 300;
fixedtow -= (fixedtow % 200);
Point fixedredpoint;
getCoordinates(fixedtow, rca, &fixedredpoint);
redcedcsv.print("{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
nmm.gi().gnsssv(),
nmm.gi().gnsstow(),
t0r,
gm.deltaAred,
gm.exred,
gm.eyred,
gm.lambda0red,
gm.deltai0red,
gm.omega0red,
gm.af0red,
gm.af1red,
rca.getAtomicOffset(fixedtow).first,
fixedtow,
fixedredpoint.x, fixedredpoint.y, fixedredpoint.z,
sqrt(fixedredpoint.x*fixedredpoint.x + fixedredpoint.y*fixedredpoint.y + fixedredpoint.z*fixedredpoint.z) - rca.getAtomicOffset(fixedtow).first/3.0
);
redcedcsv.flush();
#endif
cout<<"eyred "<<gm.eyred<<" exred "<<gm.exred<<"\nlambda0red in rad "<< ldexp(M_PI*gm.lambda0red, -22)<<" atan2 " <<atan2(1.0*gm.eyred, 1.0*gm.exred)<<" deltaAred "<<gm.deltaAred;
Point pointRed;
getCoordinates(nmm.gi().gnsstow(), rca, &pointRed);
cout<<" reduced coordinates: "<<pointRed;
#if 0
for(int letow = nmm.gi().gnsstow(); letow < nmm.gi().gnsstow() + 120; ++letow) {
getCoordinates(letow, *orig, &pointOrigRed);
cout<<"Reduced coordinates original CED: "<<pointOrigRed<<endl;
getCoordinates(letow + gm.getAtomicOffset(letow).first/1000000000.0, gm, &point);
cout<<"Full coordinates: "<<point<<endl;
Vector dist(pointRed, point);
ofs.print("{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
letow,
nmm.gi().gnsssv(),
gm.iodnav,
t0r,
pointRed.x,
pointRed.y,
pointRed.z,
point.x,
point.y,
point.z,
pointOrigRed.x,
pointOrigRed.y,
pointOrigRed.z,
dist.length());
auto ourpos=g_srcpos[nmm.sourceid()];
Vector range1(ourpos, pointRed), range2(ourpos, point);
cout<<"Distance: "<<dist<<", length "<<dist.length()<<", range difference: "<<range1.length() - range2.length()<< " " <<ourpos<<endl;
}
#endif
}
else if(wtype>=17 && wtype <=20) {
cout<<" reed-solomon 2iod "<< (int) gm.rs2bitiod;
}
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GalileoCnavType) {
basic_string<uint8_t> cnav((uint8_t*)nmm.gc().contents().c_str(), nmm.gc().contents().size());
int sv = nmm.gc().gnsssv();
if(!svfilter.check(2, sv, nmm.gc().sigid()))
continue;
etstamp();
cout << "C/NAV for " << nmm.gc().gnssid()<<","<<nmm.gc().gnsssv()<<","<<nmm.gc().sigid() <<": header ";
cout<<fmt::sprintf("%02x%02x%02x (status %d, MT %d, MID %d, MS %d, PID %d) rest ",
getbitu(cnav.c_str(), 14, 8),
getbitu(cnav.c_str(), 22, 8),
getbitu(cnav.c_str(), 30, 8),
getbitu(cnav.c_str(), 14+0, 2), // status
getbitu(cnav.c_str(), 14+4, 2), // MT
getbitu(cnav.c_str(), 14+6, 5), // MID
getbitu(cnav.c_str(), 14+11, 5), // MIS
getbitu(cnav.c_str(), 14+16, 8) // PID
);
for(int n=0; n < 51; ++n)
cout << fmt::sprintf("%02x ", getbitu(cnav.c_str(), 38 +n *8, 8));
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GalileoFnavType) {
auto fnav = makeVec((uint8_t*)nmm.gf().contents().c_str(), nmm.gf().contents().size());
int sv = nmm.gf().gnsssv();
if(!svfilter.check(2, sv, nmm.gf().sigid()))
continue;
etstamp();
GalileoMessage gm;
gm.parseFnav(fnav);
cout<<"gal F/NAV wtype "<< (int)gm.wtype << " for "<<nmm.gf().gnssid()<<","<<nmm.gf().gnsssv()<<","<<nmm.gf().sigid();
if(gm.wtype ==1 || gm.wtype == 2 || gm.wtype == 3 || gm.wtype == 4)
cout<<" iodnav " <<gm.iodnav <<" tow " << gm.tow;
if(gm.wtype == 1) {
cout <<" af0 "<<gm.af0 << " af1 "<<gm.af1 <<" af2 "<< (int)gm.af2;
}
if(gm.wtype == 2) {
cout <<" sqrtA "<<gm.sqrtA;
}
if(gm.wtype == 3) {
cout <<" t0e "<<gm.t0e;
}
if(gm.wtype == 4) {
cout <<" dtLS "<<(int)gm.dtLS <<" wnLSF "<< (unsigned int)gm.wnLSF<<" dn " << (unsigned int)gm.dn<< " dtLSF "<<(int)gm.dtLSF;
}
if(gm.wtype == 5) {
cout <<" iodalma "<<gm.ioda <<" svid1 "<< gm.alma1.svid << " e5ahs "<<(int)gm.alma1.e5ahs << " svid2 " <<gm.alma2.svid;
}
if(gm.wtype == 6) {
cout <<" iodalma "<<gm.ioda <<" svid2-e5ahs " << (int)gm.alma2.e5ahs<< " svid3 "<< gm.alma3.svid << " e5ahs "<<(int)gm.alma1.e5ahs;
}
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GPSInavType) {
int sv = nmm.gpsi().gnsssv();
@ -752,7 +531,7 @@ try
continue;
etstamp();
auto cond = getCondensedGPSMessage(makeVec((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size()));
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;
@ -766,7 +545,7 @@ try
if(frame == 1) {
gpswn = gs.wn;
cout << "gpshealth = "<<(int)gs.gpshealth<<", wn "<<gs.wn << " t0c "<<gs.t0c << " af0 " << gs.af0 << " af1 " << gs.af1 <<" af2 " << (int)gs.af2;
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) {
auto oldOffset = getGPSAtomicOffset(gs.tow, iter->second);
auto newOffset = getGPSAtomicOffset(gs.tow, gs);
@ -805,7 +584,7 @@ try
}
int start = utcFromGPS(gpswn, (int)gs.tow);
cout<<" sp3 start: "<<start<<" wn " << gpswn<<" tow " << 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);
@ -851,7 +630,7 @@ try
cout<<" 2nd-match "<<second.name << " dist "<<second.distance/1000<<" km t0e "<<gs.gpsalma.getT0e() << " t " <<nmm.localutcseconds();
}
if(page == 18)
cout << " wnLSF "<< (int)gs.wnLSF <<" dn " << (int)gs.dn << " t0t " << (int)gs.t0t <<" wn0t "<<(int)gs.wn0t<<" dtLS " << (int)gs.dtLS <<" dtLSF "<< (int)gs.dtLSF;
cout << " dtLS " << (int)gs.dtLS <<" dtLSF "<< (int)gs.dtLSF;
}
else if(frame == 5) {
if(gs.gpsalma.sv <= 24) {
@ -874,72 +653,17 @@ try
etstamp();
RTCMMessage rm;
rm.parse(nmm.rm().contents());
cout<<" rtcm-msg "<<rm.type<<" ";
if(rm.type == 1057 || rm.type == 1240) {
cout<<"iod-ssr "<<rm.ssrIOD<<" ";
for(const auto& ed : rm.d_ephs) {
cout<<makeSatIDName(ed.id)<<": iode "<< ed.iod<<" ("<< ed.radial<<", "<<ed.along<<", "<<ed.cross<<") mm -> (";
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) {
cout<<"iod-ssr "<<rm.ssrIOD<<" ";
for(const auto& cd : rm.d_clocks) {
cout<<makeSatIDName(cd.id)<<": dclock0 "<< cd.dclock0 <<" dclock1 " << cd.dclock1 <<" dclock2 "<< cd.dclock2 << endl;
cout<<makeSatPartialName(cd.id)<<": dclock0 "<< cd.dclock0 <<" dclock1 " << cd.dclock1 <<" dclock2 "<< cd.dclock2 << endl;
}
}
else if (rm.type == 1060 || rm.type == 1243) {
for(const auto& ed : rm.d_ephs) {
cout<<makeSatIDName(ed.id)<<": iode "<< ed.iod<<" ("<< ed.radial<<", "<<ed.along<<", "<<ed.cross<<") mm -> (";
cout<< ed.dradial<<", "<<ed.dalong<<", "<<ed.dcross<< ") mm/s\n";
}
for(const auto& cd : rm.d_clocks) {
cout<<makeSatIDName(cd.id)<<": dclock0 "<< cd.dclock0 <<" dclock1 " << cd.dclock1 <<" dclock2 "<< cd.dclock2 << endl;
}
}
else if(rm.type == 1045 || rm.type == 1046) {
static ofstream af0inavstr("af0inav.csv"), af0fnavstr("af0fnav.csv"), bgdstr("bgdstr.csv");
static bool first{true};
if(first) {
af0inavstr<<"timestamp sv wn t0c af0 af1\n";
af0fnavstr<<"timestamp sv wn t0c af0 af1\n";
first=false;
}
SatID sid;
sid.gnss = 2;
sid.sv = rm.d_sv;
sid.sigid = (rm.type == 1045) ? 5 : 1;
cout<< makeSatIDName(sid)<<" ";
if(rm.type == 1045) {
af0fnavstr << nmm.localutcseconds()<<" " << rm.d_sv <<" " << rm.d_gm.wn<<" "<< rm.d_gm.t0c << " " << rm.d_gm.af0 << " " << rm.d_gm.af1<<"\n";
cout<<"F/NAV";
}
else {
af0inavstr << nmm.localutcseconds() <<" " << rm.d_sv <<" " << rm.d_gm.wn<<" "<<rm.d_gm.t0c<<" "<< rm.d_gm.af0 << " " << rm.d_gm.af1<<"\n";
bgdstr << nmm.localutcseconds() <<" " << rm.d_sv<<" " <<rm.d_gm.BGDE1E5a <<" " << rm.d_gm.BGDE1E5b << "\n";
cout <<"I/NAV";
}
cout <<" iode " << rm.d_gm.iodnav << " sisa " << (unsigned int) rm.d_gm.sisa << " t0c " << rm.d_gm.t0c << " af0 "<<rm.d_gm.af0 <<" af1 " << rm.d_gm.af1 <<" af2 " << (int) rm.d_gm.af2 << " BGDE1E5a " << rm.d_gm.BGDE1E5a;
if(rm.type == 1046) // I/NAV
cout <<" BGDE1E5b "<< rm.d_gm.BGDE1E5b;
cout<<endl;
}
else if(rm.type == 1059 || rm.type==1242) {
cout<<"\n";
for(const auto& dcb : rm.d_dcbs) {
cout<<" "<<makeSatIDName(dcb.first)<<": "<<dcb.second<<" meters\n";
}
cout<<endl;
}
else {
cout<<" len " << nmm.rm().contents().size() << endl;
}
}
else if(nmm.type() == NavMonMessage::GPSCnavType) {
@ -951,7 +675,7 @@ try
static map<int, GPSCNavState> states;
auto& state = states[sv];
int type = parseGPSCNavMessage(
makeVec((uint8_t*)nmm.gpsc().contents().c_str(),
std::basic_string<uint8_t>((uint8_t*)nmm.gpsc().contents().c_str(),
nmm.gpsc().contents().size()),
state);
@ -969,9 +693,9 @@ try
continue;
etstamp();
std::vector<uint8_t> cond;
std::basic_string<uint8_t> cond;
try {
cond = getCondensedBeidouMessage(makeVec((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size()));
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;
@ -991,7 +715,7 @@ try
cout<<" Timejump: "<<oldOffset.first - newOffset.first<<" after "<<(bm.getT0c() - msgs[sv].getT0c() )<<" seconds";
}
msgs[sv]=bm;
cout<<" wn "<<bm.wn<<" t0c "<<(int)bm.t0c<<" aodc "<< (int)bm.aodc <<" aode "<< (int)bm.aode <<" sath1 "<< (int)bm.sath1 << " urai "<<(int)bm.urai << " af0 "<<bm.a0 <<" af1 " <<bm.a1 <<" af2 "<< (int)bm.a2;
cout<<" wn "<<bm.wn<<" t0c "<<(int)bm.t0c<<" aodc "<< (int)bm.aodc <<" aode "<< (int)bm.aode <<" sath1 "<< (int)bm.sath1 << " urai "<<(int)bm.urai << " af0 "<<bm.a0 <<" af1 " <<bm.a1 <<" af2 "<<bm.a2;
auto offset = bm.getAtomicOffset();
cout<< ", "<<offset.first<<"ns " << (offset.second * 3600) <<" ns/hour "<< ephAge(bm.sow, bm.t0c*8);
}
@ -1003,6 +727,8 @@ try
cout<<" best-tle-match "<<match.name <<" dist "<<match.distance /1000<<" km";
cout<<" norad " <<match.norad <<" int-desig " << match.internat;
cout<<" 2nd-match "<<second.name << " dist "<<second.distance/1000<<" km";
}
else if((fraid == 4 && 1<= pageno && pageno <= 24) ||
(fraid == 5 && 1<= pageno && pageno <= 6) ||
@ -1033,7 +759,7 @@ try
cout<<" WNa "<<getbitu(&cond[0], beidouBitconv(190), 8)<<" t0a "<<getbitu(&cond[0], beidouBitconv(198), 8);
}
else if(bm.fraid == 5 && pageno==10) {
cout <<" dTLS "<< (int)bm.deltaTLS << " dTLSF " << (int) bm.deltaTLSF <<" wnLSF " << (unsigned int)bm.wnLSF <<" dn "<<(unsigned int) bm.dn<<endl;
cout <<" dTLS "<< (int)bm.deltaTLS;
}
else if(bm.fraid == 5 && pageno==24) {
int AmID= getbitu(&cond[0], beidouBitconv(216), 2);
@ -1050,7 +776,7 @@ try
continue;
etstamp();
auto cond = getCondensedBeidouMessage(makeVec((uint8_t*)nmm.bid2().contents().c_str(), nmm.bid2().contents().size()));
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid2().contents().c_str(), nmm.bid2().contents().size()));
BeidouMessage bm;
uint8_t pageno;
int fraid = bm.parse(cond, &pageno);
@ -1065,7 +791,7 @@ try
static map<int, GlonassMessage> gms;
auto& gm = gms[nmm.gloi().gnsssv()];
int strno = gm.parse(makeVec((uint8_t*)nmm.gloi().contents().c_str(), nmm.gloi().contents().size()));
int strno = gm.parse(std::basic_string<uint8_t>((uint8_t*)nmm.gloi().contents().c_str(), nmm.gloi().contents().size()));
cout<<"Glonass R"<<nmm.gloi().gnsssv()<<" @ "<< ((int)nmm.gloi().freq()-7) <<" strno "<<strno;
if(strno == 1) {
@ -1234,34 +960,30 @@ try
}
else if(nmm.type() == NavMonMessage::ObserverDetailsType) {
if(doObserverDetails) {
etstamp();
cout<<"vendor "<<nmm.od().vendor()<<" hwversion " <<nmm.od().hwversion()<<" modules "<<nmm.od().modules()<<" swversion "<<nmm.od().swversion();
cout<<" serial "<<nmm.od().serialno();
if(nmm.od().has_owner())
cout<<" owner "<<nmm.od().owner();
if(nmm.od().has_clockoffsetdriftns())
cout<<" drift "<<nmm.od().clockoffsetdriftns();
if(nmm.od().has_clockaccuracyns())
cout<<" clock-accuracy "<<nmm.od().clockaccuracyns();
cout<<endl;
}
etstamp();
cout<<"vendor "<<nmm.od().vendor()<<" hwversion " <<nmm.od().hwversion()<<" modules "<<nmm.od().modules()<<" swversion "<<nmm.od().swversion();
cout<<" serial "<<nmm.od().serialno();
if(nmm.od().has_owner())
cout<<" owner "<<nmm.od().owner();
if(nmm.od().has_clockoffsetdriftns())
cout<<" drift "<<nmm.od().clockoffsetdriftns();
if(nmm.od().has_clockaccuracyns())
cout<<" clock-accuracy "<<nmm.od().clockaccuracyns();
cout<<endl;
}
else if(nmm.type() == NavMonMessage::UbloxJammingStatsType) {
if(doJammingData) {
etstamp();
cout<<"noisePerMS "<<nmm.ujs().noiseperms() << " agcCnt "<<
nmm.ujs().agccnt()<<" flags "<<nmm.ujs().flags()<<" jamind "<<
nmm.ujs().jamind()<<endl;
}
etstamp();
cout<<"noisePerMS "<<nmm.ujs().noiseperms() << " agcCnt "<<
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();
auto sbas = makeVec((uint8_t*)nmm.sbm().contents().c_str(), nmm.sbm().contents().size());
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:
@ -1315,12 +1037,10 @@ try
}
else if(nmm.type() == NavMonMessage::DebuggingType) {
auto res = parseTrkMeas(makeVec((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size()));
auto res = parseTrkMeas(basic_string<uint8_t>((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size()));
if(res.empty())
continue;
etstamp();
cout<<"ublox debug message ";
uint64_t maxt=0;
for(const auto& sv : res) {
if(sv.gnss != 2) continue;
@ -1417,20 +1137,9 @@ try
hexstring+=fmt::sprintf("%x", (int)getbitu((unsigned char*)id.c_str(), 4 + 4*n, 4));
cout<<"SAR RLM type "<< nmm.sr().type() <<" from gal sv ";
cout<<" SAR RLM type "<< nmm.sr().type() <<" from gal sv ";
cout<< nmm.sr().gnsssv() << " beacon "<<hexstring <<" code "<<(int)nmm.sr().code()<<" params "<< makeHexDump(nmm.sr().params()) <<endl;
}
else if(nmm.type() == NavMonMessage::TimeOffsetType) {
if(doTimeOffsets) {
etstamp();
cout<<" got a time-offset message with "<< nmm.to().offsets().size()<<" offsets: ";
for(const auto& o : nmm.to().offsets()) {
cout << "gnssid "<<o.gnssid()<<" offset " << o.offsetns() << " +- "<<o.tacc()<<" ("<<o.valid()<<") , ";
}
cout<<endl;
}
}
else {
etstamp();
cout<<"Unknown type "<< (int)nmm.type()<<endl;

View file

@ -1,286 +0,0 @@
#include "sclasses.hh"
#include "swrappers.hh"
#include <map>
#include "navmon.hh"
#include "navmon.pb.h"
#include <thread>
#include <signal.h>
#include "fmt/format.h"
#include "fmt/printf.h"
#include "nmmsender.hh"
#include "CLI/CLI.hpp"
#include "version.hh"
using namespace std;
static char program[]="navmerge";
/* ubxtool/rtcmtool/septool deliver data to one of several `navrecv` instances
This means we need a 'merge' tool.
The merge tool should be able to stream data from multiple `navnexus` instances
(that correspond to the `navrecv` instances)
Currently, `navnexus` is really simple - it will send you a feed from x hours back, where
you don't get to pick x.
The simplest navmerge implementation does nothing but connect to a few navnexus instances
and it mixes them together.
Every message "should" only appear on one of the upstreams, but you never know.
On initial connection, the different navnexuses may start up from a different time, currently.
Let us state that This Should Not Happen.
On initial connect, a navnexus might take dozens of seconds before it starts coughing up data.
Initial goal for navmerge is: only make sure realtime works.
Every upstream has a thread that loops trying to connect
If a new message comes in, it is stored in a shared data structure
If a new connect is made, set a "don't send" marker for a whole minute
There is a sender thread that periodically polls this data structure
Any data that is older than the previous high-water mark gets sent out & removed from structure
However, transmission stops 10 seconds before realtime
If a "don't send" marker is set, we don't do a thing
*/
multimap<pair<uint64_t, uint64_t>, string> g_buffer;
std::mutex g_mut;
set<int> g_bsset;
// navmerge can also dedup its output, we keep track of recent messages here
// this means each Galileo message will only get set once
map<tuple<uint32_t, std::string, uint32_t, std::string, int16_t>, time_t> g_seen;
bool g_inavdedup{false}, g_gpsdedup{false};
/* Goal: do a number of TCP operations that have a combined timeout.
maybe some helper:
auto deadline = xSecondsFromNow(1.5);
SConnectWithDeadline(sock, addr, deadline);
resp=SReadWithDeadline(sock, 2, deadline);
..
resp2=SReadWithDeadline(sock, num, deadline);
//
// not getting 'num' bytes -> error
// exceeding timeout before getting 'num' bytes -> error
*/
static auto xSecondsFromNow(double seconds)
{
auto now = chrono::steady_clock::now();
now += std::chrono::milliseconds((unsigned int)(seconds*1000));
return now;
}
static int msecLeft(const std::chrono::steady_clock::time_point& deadline)
{
auto now = chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now).count();
}
void recvSession(ComboAddress upstream)
{
for(;;) {
try {
Socket sock(upstream.sin4.sin_family, SOCK_STREAM);
cerr<<"Connecting to "<< upstream.toStringWithPort()<<" to source data..";
SConnectWithTimeout(sock, upstream, 5);
cerr<<" done"<<endl;
for(int count=0;;++count) {
auto deadline = xSecondsFromNow(600); //
string part=SReadWithDeadline(sock, 4, deadline);
if(part.empty()) {
cerr<<"EOF from "<<upstream.toStringWithPort()<<endl;
break;
}
if(part != "bert") {
cerr << "Message "<<count<<", wrong magic from "<<upstream.toStringWithPort()<<": "<<makeHexDump(part)<<endl;
break;
}
if(!count)
cerr<<"Receiving messages from "<<upstream.toStringWithPort()<<endl;
string out=part;
part = SReadWithDeadline(sock, 2, deadline);
out += part;
uint16_t len;
memcpy(&len, part.c_str(), 2);
len = htons(len);
part = SReadWithDeadline(sock, len, deadline); // XXXXX ???
if(part.size() != len) {
cerr<<"Mismatch, "<<part.size()<<", len "<<len<<endl;
// XX AND THEN WHAT??
}
out += part;
// if(msecLeft(deadline)/1000.0 < 119)
// cerr<<"Done with "<<msecLeft(deadline)/1000.0<<" seconds left\n";
NavMonMessage nmm;
nmm.ParseFromString(part);
if(g_bsset.count(nmm.sourceid()))
continue;
if(g_inavdedup) {
if(nmm.type() == NavMonMessage::GalileoInavType) {
std::lock_guard<std::mutex> mut(g_mut);
decltype(g_seen)::key_type tup(nmm.gi().gnsssv(),
nmm.gi().contents(),
nmm.gi().sigid(),
nmm.gi().reserved1(),
nmm.gi().has_ssp() ? nmm.gi().ssp() : -1);
if(!g_seen.count(tup))
g_buffer.insert({{nmm.localutcseconds(), nmm.localutcnanoseconds()}, part});
g_seen[tup]=time(0);
}
}
if(g_gpsdedup) {
if(nmm.type() == NavMonMessage::GPSInavType) {
std::lock_guard<std::mutex> mut(g_mut);
decltype(g_seen)::key_type tup(nmm.gpsi().gnsssv(),
nmm.gpsi().contents(),
nmm.gpsi().sigid(),
"",
0);
if(!g_seen.count(tup))
g_buffer.insert({{nmm.localutcseconds(), nmm.localutcnanoseconds()}, part});
g_seen[tup]=time(0);
}
}
if(!g_gpsdedup && !g_inavdedup) {
std::lock_guard<std::mutex> mut(g_mut);
g_buffer.insert({{nmm.localutcseconds(), nmm.localutcnanoseconds()}, part});
}
}
}
catch(std::exception& e) {
cerr<<"Error in receiving thread: "<<e.what()<<endl;
sleep(1);
}
}
cerr<<"Thread for "<<upstream.toStringWithPort()<< " exiting"<<endl;
}
static void cleanFilter()
{
time_t lim = time(0) - 60;
std::lock_guard<std::mutex> mut(g_mut);
for(auto iter = g_seen.begin(); iter!= g_seen.end() ;) {
if(iter->second < lim)
iter = g_seen.erase(iter);
else
++iter;
}
}
int main(int argc, char** argv)
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
vector<string> destinations;
vector<string> sources;
vector<string> listeners;
vector<int> badstations;
bool doVERSION{false}, doSTDOUT{false};
CLI::App app(program);
app.add_option("--source", sources, "Connect to these IP address:port to source protobuf");
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
app.add_option("--drop-stations", badstations, "Drop these station numbers");
app.add_option("--listener,-l", listeners, "Make data available on this IPv4/v6 address");
app.add_flag("--inavdedup", g_inavdedup, "Only pass on Galileo I/NAV, and dedeup");
app.add_flag("--gpsdedup", g_gpsdedup, "Only pass on GPS, and dedeup");
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_flag("--stdout", doSTDOUT, "Emit output to stdout");
CLI11_PARSE(app, argc, argv);
if(doVERSION) {
showVersion(program, g_gitHash);
exit(0);
}
if(sources.empty()) {
cerr<< "No sources defined. Exiting."<<endl;
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));
}
for(const auto& l : listeners) {
ComboAddress ca(l, 29604);
cerr<<"Adding listener on "<<ca.toStringWithPort()<<endl;
ns.addListener(l); // ComboAddress(s, 29603));
}
if(doSTDOUT)
ns.addDestination(1);
for(const auto& bs : badstations) {
g_bsset.insert(bs);
cerr<<"Dropping station "<<bs<<endl;
}
for(const auto& s : sources) {
ComboAddress oneaddr(s, 29601);
std::thread one(recvSession, oneaddr);
one.detach();
}
ns.launch();
time_t start=time(0);
int counter=0;
for(;;) {
usleep(500000);
vector<string> tosend;
{
std::lock_guard<std::mutex> mut(g_mut);
time_t now = time(0);
if(now - start < 30) { // was 30
cerr<<"Have "<<g_buffer.size()<<" messages"<<endl;
continue;
}
for(auto iter = g_buffer.begin(); iter != g_buffer.end(); ) {
if(iter->first.first > (uint64_t)now - 5)
break;
tosend.push_back(iter->second);
iter = g_buffer.erase(iter);
}
}
// cerr<<"Have "<<tosend.size()<<" messages to send, "<<g_buffer.size()<<" left in queue"<<endl;
std::string buf;
for(const auto& m : tosend) {
if(!((counter++) % 32768))
cleanFilter();
ns.emitNMM(m);
}
}
}

View file

@ -9,7 +9,7 @@
#include <signal.h>
#include <sys/poll.h>
#include <iostream>
#include <vector>
using namespace std;
using Clock = std::chrono::steady_clock;
@ -159,18 +159,6 @@ std::string humanTimeShort(time_t t)
return buffer;
}
// influx ascii time format, in UTC
std::string influxTime(time_t t)
{
struct tm tm={0};
gmtime_r(&t, &tm);
char buffer[80];
std::string fmt = "%Y-%m-%d %H:%M:%S";
strftime(buffer, sizeof(buffer), fmt.c_str(), &tm);
return buffer;
}
std::string humanTime(time_t t, uint32_t nanoseconds)
{
@ -292,33 +280,8 @@ std::string makeSatPartialName(const SatID& satid)
return fmt::sprintf("%c%02d", getGNSSChar(satid.gnss), satid.sv);
}
int g_dtLS{18}, g_dtLSBeidou{4};
void getGPSDateFromUTC(time_t t, int& wn, int& tow)
{
t -= 315964800;
t += 18; // XXXXXX LEAP SECOND
wn = t/(7*86400);
tow = t%(7*86400);
}
void getGalDateFromUTC(time_t t, int& wn, int& tow)
{
t -= 935280000;
t += 18; // XXXXXXX LEAP SECOND
wn = t/(7*86400);
tow = t%(7*86400);
}
void getBeiDouDateFromUTC(time_t t, int&wn, int& sow)
{
// Week number count started from zero at 00:00:00 on Jan. 1, 2006 of BDT
t -= 1136070000;
t+= g_dtLSBeidou;
wn = t/(7*86400);
sow = t%(7*86400);
}
uint64_t utcFromGST(int wn, int tow)
{
return (935280000 + wn * 7*86400 + tow - g_dtLS);
@ -347,19 +310,6 @@ string makeHexDump(const string& str)
return ret;
}
string makeHexDump(const vector<uint8_t>& str)
{
char tmp[5];
string ret;
ret.reserve((int)(str.size()*2.2));
for(string::size_type n=0;n<str.size();++n) {
snprintf(tmp, sizeof(tmp), "%02x ", (unsigned char)str[n]);
ret+=tmp;
}
return ret;
}
std::string sbasName(int prn)
{
string sbas;
@ -406,46 +356,3 @@ void unixDie(const std::string& reason)
{
throw std::runtime_error(reason+": "+strerror(errno));
}
time_t parseTime(std::string_view in)
{
time_t now=time(0);
vector<string> formats({"%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S", "%Y%m%d %H%M", "%H:%M", "%H%M"});
for(const auto& f : formats) {
struct tm tm;
memset(&tm, 0, sizeof(tm));
localtime_r(&now, &tm);
tm.tm_isdst = -1;
tm.tm_sec = 0;
char* res = strptime(&in[0], f.c_str(), &tm);
if(res && !*res) {
// cerr<<"Matched on "<<f<<endl;
return mktime(&tm);
}
}
string err="Can only parse on";
for(const auto& f : formats)
err += " '"+ f+"'";
throw runtime_error(err);
}
std::string string_replace(const std::string& str, const std::string& match,
const std::string& replacement, unsigned int max_replacements)
{
size_t pos = 0;
std::string newstr = str;
unsigned int replacements = 0;
while ((pos = newstr.find(match, pos)) != std::string::npos
&& replacements < max_replacements)
{
newstr.replace(pos, match.length(), replacement);
pos += replacement.length();
replacements++;
}
return newstr;
}

View file

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

View file

@ -17,9 +17,6 @@ message NavMonMessage {
SBASMessageType = 13;
GPSCnavType = 14;
RTCMMessageType = 15;
TimeOffsetType = 16;
GalileoFnavType = 17;
GalileoCnavType = 18;
}
required uint64 sourceID = 1;
@ -35,32 +32,7 @@ message NavMonMessage {
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
optional uint32 sigid = 6;
optional bytes reserved1 = 7;
optional bytes reserved2 = 8;
optional bytes sar = 9;
optional bytes spare = 10;
optional bytes crc = 11;
optional uint32 ssp = 12;
}
message GalileoFnav {
required uint32 gnssWN =1;
required uint32 gnssTOW =2; // INTEGERS!
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
required uint32 sigid = 6;
}
message GalileoCnav {
required uint32 gnssWN =1;
required uint32 gnssTOW =2; // INTEGERS!
required uint32 gnssID =3;
required uint32 gnssSV =4;
required bytes contents =5;
required uint32 sigid = 6;
optional uint32 sigid = 6;
}
message GPSInav {
@ -200,32 +172,10 @@ message GalileoFnav {
required uint32 sigid = 6;
}
message RTCMMessage {
required bytes contents =5;
}
message GNSSOffset
{
required uint32 gnssid = 1;
required int32 offsetNS = 2;
required uint32 tAcc = 3;
required bool valid = 4;
optional int32 leapS = 5;
required uint32 tow = 6;
optional uint32 wn = 7;
optional uint32 nT = 8;
optional uint32 n4 = 9;
}
message TimeOffsetMessage
{
required uint32 itow = 1;
repeated GNSSOffset offsets = 2;
}
optional GalileoInav gi=5;
@ -242,8 +192,5 @@ message GalileoFnav {
optional UbloxJammingStats ujs = 16;
optional SBASMessage sbm = 17;
optional GPSCnav gpsc = 18;
optional RTCMMessage rm = 19;
optional TimeOffsetMessage to = 20;
optional GalileoFnav gf=21;
optional GalileoCnav gc=22;
optional RTCMMessage rm = 19;
}

View file

@ -35,7 +35,6 @@ void unixDie(const std::string& str)
throw std::runtime_error(str+string(": ")+string(strerror(errno)));
}
// station numbers
vector<uint64_t> getSources()
{
DIR *dir = opendir(g_storage.c_str());
@ -89,7 +88,7 @@ try
close(fd);
continue;
}
// cout <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
cout <<"Seeked to position "<<fpos[fname]<<" of "<<fname<<endl;
NavMonMessage nmm;
uint32_t looked=0;
@ -99,10 +98,6 @@ try
while(getRawNMM(fd, ts, msg, offset)) {
// don't drop data that is only 5 seconds too old
if(make_pair(ts.tv_sec + 5, ts.tv_nsec) >= make_pair(start.tv_sec, start.tv_nsec)) {
if(ts.tv_sec > time(0)) {
cout << "station "<<src <<" is living in the future, skipping message\n";
}
else
rnmms.push_back({ts, msg});
}
++looked;
@ -111,15 +106,15 @@ try
fpos[fname]=offset;
close(fd);
}
// cout<<"Sorting.. ";
// cout.flush();
cout<<"Sorting.. ";
cout.flush();
sort(rnmms.begin(), rnmms.end(), [](const auto& a, const auto& b)
{
return std::tie(a.first.tv_sec, a.first.tv_nsec)
< std::tie(b.first.tv_sec, b.first.tv_nsec);
});
// cout<<"Sending.. ";
// cout.flush();
cout<<"Sending.. ";
cout.flush();
for(const auto& nmm: rnmms) {
std::string buf="bert";
uint16_t len = htons(nmm.second.size());
@ -127,13 +122,12 @@ try
buf += nmm.second;
SWriten(clientfd, buf);
}
// cout<<"Done"<<endl;
cout<<"Done"<<endl;
if(3600 + start.tv_sec - (start.tv_sec % 3600) < time(0))
start.tv_sec = 3600 + start.tv_sec - (start.tv_sec % 3600);
else {
if(!rnmms.empty()) // start off where we left it
if(!rnmms.empty())
start = {rnmms.rbegin()->first.tv_sec, rnmms.rbegin()->first.tv_nsec};
// This is a bit iffy as it relies on the station furthest ahead in time
sleep(1);
}
}

File diff suppressed because it is too large Load diff

View file

@ -49,12 +49,6 @@ struct SVStat
GalileoMessage ephgalmsg, galmsg, oldephgalmsg;
// internal
map<int, GalileoMessage> galmsgTyped;
bool osnma{false};
time_t osnmaTime{0};
bool impinav{false};
time_t impinavTime{0};
// Glonass
GlonassMessage ephglomsg, glonassMessage, oldephglomsg;
@ -67,7 +61,6 @@ struct SVStat
map<int, SBASCombo> sbas;
RTCMMessage::EphemerisDelta rtcmEphDelta;
RTCMMessage::ClockDelta rtcmClockDelta;
const GPSLikeEphemeris& liveIOD() const;
const GPSLikeEphemeris& prevIOD() const;

View file

@ -16,7 +16,6 @@
#include "version.hh"
#include <netinet/tcp.h>
#include "navmon.hh"
#include <mutex>
static char program[]="navrecv";
@ -103,7 +102,9 @@ int getfd(const char* path, int mode, int permission)
std::advance(end, toErase);
fds.erase(fds.begin(), end);
}
FileID fid({path, mode, permission});
// cout<<"Request for "<<path<<endl;
auto iter = fds.find(fid);
@ -119,7 +120,7 @@ int getfd(const char* path, int mode, int permission)
if(fd < 0) {
throw FatalException("Unable to open file for storage: "+string(strerror(errno)));
}
// cout<<"Opened fd "<<fd<<" for path "<<path<<endl;
cout<<"Opened fd "<<fd<<" for path "<<path<<endl;
fds.emplace(fid, FDID(fd));
return fd;
}
@ -139,97 +140,11 @@ void writeToDisk(time_t s, uint64_t sourceid, std::string_view message)
}
}
// This is used to report clients, so we can log them
// The idea is that cleanup runs from the Sentinel which, when destroyed, will remove the entry
struct ClientKeeper
{
struct ClientStatus
{
bool oldProtocol;
time_t lastMessage;
int station;
uint64_t messages{0};
};
struct Sentinel
{
Sentinel(ClientKeeper* parent, const ComboAddress& us) : d_parent(parent), d_us(us)
{
}
Sentinel(Sentinel&& s)
{
// cerr<<"Moved!"<<endl;
d_parent = s.d_parent;
d_us = s.d_us;
s.d_parent=0;
}
~Sentinel()
{
// cerr<<"Destructor"<<endl;
if(d_parent) {
d_parent->remove(d_us);
}
else
; //cerr<<" but we were moved already!\n";
}
void update(int station, bool oldProtocol)
{
time_t now = time(0);
std::lock_guard<std::mutex> l(d_parent->d_mut);
ClientStatus& cs = d_parent->d_clients[d_us];
cs.station = station;
cs.lastMessage = now;
cs.messages++;
cs.oldProtocol = oldProtocol;
}
ClientKeeper* d_parent;
ComboAddress d_us;
};
Sentinel reportClient(const ComboAddress& client)
{
Sentinel s2(this, client);
std::lock_guard<std::mutex> l(d_mut);
d_clients[client];
return s2;
}
void remove(const ComboAddress& client)
{
std::lock_guard<std::mutex> l(d_mut);
d_clients.erase(client);
}
void dump()
{
std::lock_guard<std::mutex> l(d_mut);
string format("{:<50}{:<5}{:<10}{:<10}{:<10}\n");
ofstream out("clients.bak");
time_t now=time(0);
out<< fmt::format(format, "IP Address", "ID", "Protocol", "Messages", "Age");
for(const auto& c : d_clients) {
out << fmt::format(format, c.first.toStringWithPort(), c.second.station, c.second.oldProtocol ? "Old" : "New", c.second.messages, now-c.second.lastMessage);
}
out.close();
unlink("clients.txt");
rename("clients.bak", "clients.txt");
}
map<ComboAddress, ClientStatus> d_clients;
std::mutex d_mut;
};
ClientKeeper g_ckeeper;
// note that this moves the socket
void recvSession2(Socket&& uns, ComboAddress client, ClientKeeper::Sentinel& sentinel)
void recvSession2(Socket&& uns, ComboAddress client)
{
string secret = SRead(uns, 8); // ignored for now
cerr << client.toStringWithPort()<< " Entering compressed session"<<endl;
cerr << "Entering compressed session for "<<client.toStringWithPort()<<endl;
ZStdReader zsr(uns);
int s = zsr.getFD();
// time_t start = time(0);
@ -240,7 +155,7 @@ void recvSession2(Socket&& uns, ComboAddress client, ClientKeeper::Sentinel& sen
// sleep(10);
string num=SRead(s, 4);
if(num.empty()) {
cerr<<client.toStringWithPort()<<" EOF"<<endl;
cerr<<"EOF from "<<client.toStringWithPort()<<endl;
break;
}
string out="bert";
@ -262,13 +177,13 @@ void recvSession2(Socket&& uns, ComboAddress client, ClientKeeper::Sentinel& sen
memcpy(&denum, num.c_str(), 4);
denum = htonl(denum);
// cerr<<"Received message "<<denum<< " "<<nmm.localutcseconds()<<" " << nmm.localutcnanoseconds()/1000000000.0<<endl;
sentinel.update(nmm.sourceid(), false);
writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
if(first) {
cerr<<client.toStringWithPort() <<" station: "<<nmm.sourceid() << endl;
cerr<<"\tstation: "<<nmm.sourceid() << endl;
first=false;
}
#ifdef __linux__
SSetsockopt(uns, IPPROTO_TCP, TCP_CORK, 1 );
@ -284,21 +199,17 @@ void recvSession(int s, ComboAddress client)
try {
Socket sock(s); // this closes on destruction
SSetsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 1); // saves file descriptors
cerr<<client.toStringWithPort()<<" New connection\n";
cerr.flush();
cerr<<"Receiving messages from "<<client.toStringWithPort()<<endl;
bool first=true;
ClientKeeper::Sentinel sentinel=g_ckeeper.reportClient(client);
for(int count=0;;++count) {
string part=SRead(sock, 4);
if(part.empty()) {
cerr<<client.toStringWithPort()<<" EOF"<<endl;
cerr<<"EOF from "<<client.toStringWithPort()<<endl;
break;
}
if(part != "bert") {
if(part == "RNIE")
return recvSession2(std::move(sock), client, sentinel); // protocol v2, socket is moved cuz cleanup is special
return recvSession2(std::move(sock), client); // protocol v2, socket is moved cuz cleanup is special
cerr << "Message "<<count<<", wrong magic from "<<client.toStringWithPort()<<": "<<makeHexDump(part)<<endl;
break;
}
@ -314,24 +225,22 @@ void recvSession(int s, ComboAddress client)
part = SRead(s, len);
if(part.size() != len) {
cerr<<"Mismatch, "<<part.size()<<", len "<<len<<endl;
// XX AND THEN WHAT??
}
out += part;
NavMonMessage nmm;
nmm.ParseFromString(part);
if(first) {
cerr<<client.toStringWithPort()<<" station "<<nmm.sourceid() << endl;
cerr<<"\tstation: "<<nmm.sourceid() << endl;
first=false;
}
sentinel.update(nmm.sourceid(), true);
writeToDisk(nmm.localutcseconds(), nmm.sourceid(), out);
}
}
catch(std::exception& e) {
cout<<client.toStringWithPort()<<" error in receiving thread: "<<e.what()<<endl;
cout<<"Error in receiving thread: "<<e.what()<<endl;
}
cout<<client.toStringWithPort()<< " thread exiting"<<endl;
cout<<"Thread for "<<client.toStringWithPort()<< " exiting"<<endl;
}
void recvListener(Socket&& s, ComboAddress local)
@ -376,10 +285,8 @@ int main(int argc, char** argv)
thread recvThread(recvListener, std::move(receiver), recvaddr);
recvThread.detach();
sleep(5);
for(;;) {
g_ckeeper.dump();
sleep(10);
sleep(1);
}
}

View file

@ -1,98 +1,14 @@
#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::sendLoop(Destination* d, SocketCommunicator& sc, std::unique_ptr<ZStdCompressor>& zsc, Socket& s, map<uint32_t, string>& unacked, time_t connStartTime)
{
bool hadMessage=false;
int msgnum = 0;
for(;;) {
uint32_t num;
// read acks
for(; zsc ;) { // only do this for compressed protocol
try {
readn2(s, &num, 4); // this will give us 4, or throw
num = ntohl(num);
unacked.erase(num);
}
catch(EofException& ee) {
throw std::runtime_error("EOF while reading acks");
}
catch(std::exception& e) {
if(errno != EAGAIN)
unixDie("Reading acknowledgements in nmmsender");
break;
}
}
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 ("+to_string(unacked.size())+"), recycling connection");
}
hadMessage = false;
if(d_pleaseQuit)
return;
usleep(100000);
#if defined(TCP_CORK)
/* linux-only: has an implied 200ms timeout */
SSetsockopt(s, IPPROTO_TCP, TCP_CORK, 1 );
#elif defined(TCP_NOPUSH)
/*
* freebsd/osx: buffers until buffer full/connection closed, so
* we toggle it every other loop through
*/
static bool push_toggle;
if (push_toggle) {
SSetsockopt(s, IPPROTO_TCP, TCP_NOPUSH, 0 );
SSetsockopt(s, IPPROTO_TCP, TCP_NOPUSH, 1 );
}
push_toggle = !push_toggle;
#endif
}
}
}
// this does all kinds of resolving based on a *string* destination
void NMMSender::sendTCPThread(Destination* d)
{
struct NameError{};
@ -139,8 +55,88 @@ void NMMSender::sendTCPThread(Destination* d)
// the 00000000 is a placeholder for a "secret" we might implement later
zsc = std::make_unique<ZStdCompressor>(emit, 9);
}
bool hadMessage=false;
int msgnum = 0;
sendLoop(d, sc, zsc, s, unacked, connStartTime);
for(;;) {
uint32_t num;
// read acks
for(; zsc ;) { // only do this for compressed protocol
try {
readn2(s, &num, 4); // this will give us 4, or throw
num = ntohl(num);
unacked.erase(num);
}
catch(EofException& ee) {
throw std::runtime_error("EOF while reading acks");
}
catch(std::exception& e) {
if(errno != EAGAIN)
unixDie("Reading acknowledgements in nmmsender");
break;
}
}
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 ("+to_string(unacked.size())+"), recycling connection");
}
hadMessage = false;
if(d_pleaseQuit)
return;
usleep(100000);
#if defined(TCP_CORK)
/* linux-only: has an implied 200ms timeout */
SSetsockopt(s, IPPROTO_TCP, TCP_CORK, 1 );
#elif defined(TCP_NOPUSH)
/*
* freebsd/osx: buffers until buffer full/connection closed, so
* we toggle it every other loop through
*/
static bool push_toggle;
if (push_toggle) {
SSetsockopt(s, IPPROTO_TCP, TCP_NOPUSH, 0 );
SSetsockopt(s, IPPROTO_TCP, TCP_NOPUSH, 1 );
}
push_toggle = !push_toggle;
#endif
}
}
}
}
catch(NameError&) {
@ -179,142 +175,25 @@ void NMMSender::sendTCPThread(Destination* d)
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);
emitNMM(out);
}
void NMMSender::emitNMM(const std::string& out)
{
std::lock_guard<std::mutex> l(d_destslock);
for(auto& d : d_dests) {
d->emitNMM(out, d_compress);
}
}
/* the listener design. The listener has a thread that waits for connections.
the listener is a normal 'destination'.
It consumes its queue, and forwards messages to any connections made to it.
*/
void NMMSender::acceptorThread(Destination *d)
try
{
cerr<<"Start of acceptor thread"<<endl;
ComboAddress ca(d->dst);
Socket l(ca.sin4.sin_family, SOCK_STREAM);
SSetsockopt(l, SOL_SOCKET, SO_REUSEADDR, 1 );
SBind(l, ca);
SListen(l, 128);
cerr<<"Made a listener on "<<ca.toStringWithPort()<<endl;
NMMSender ns;
std::thread t(&NMMSender::forwarderThread, this, d, &ns);
t.detach();
for(;;) {
ComboAddress remote=ca;
int fd = SAccept(l, remote);
cout<<"Had a new connection from "<<remote.toStringWithPort()<<" on fd "<<fd<<endl;
auto nd = std::make_unique<Destination>();
nd->dst="source";
std::lock_guard<std::mutex> l(ns.d_destslock);
ns.d_dests.push_back(std::move(nd));
std::thread t(&NMMSender::sendTCPListenerThread, &ns, ns.d_dests.rbegin()->get(), fd, remote);
t.detach();
}
}
catch(std::exception& e) {
cerr<<"Acceptor thread dying: "<<e.what()<<endl;
}
void NMMSender::forwarderThread(Destination *d, NMMSender* there)
{
// cout<<"Forwarder thread launched, this " << (void*)this<<" -> "<<(void*)there<<endl;
std::string msg;
for(;;) {
{
std::lock_guard<std::mutex> mut(d->mut);
while(!d->queue.empty()) {
// cerr<<"Forwarded a message to "<< (void*)there<<endl;
msg = d->queue.front();
there->emitNMM(msg);
d->queue.pop_front();
}
}
usleep(100000);
}
}
void NMMSender::sendTCPListenerThread(Destination* d, int fd, ComboAddress addr)
{
cerr<<"sendTCPListenerThread launched on fd "<<fd<<" for "<<addr.toStringWithPort()<<", d_compress "<<d_compress<<endl;
try {
Socket s(fd);
SocketCommunicator sc(s);
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, 9);
}
map<uint32_t, string> unacked;
// cerr<<"Entering sendloop"<<endl;
sendLoop(d, sc, zsc, s, unacked, connStartTime);
}
catch(std::exception& e) {
if (d_debug) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst<<" via "<<addr.toStringWithPort()<<" had error: "<<e.what()<<endl; }
}
catch(...) {
if (d_debug) { cerr<<humanTimeNow()<<" Sending thread for "<<d->dst <<" via "<< addr.toStringWithPort()<<" had error"; }
}
std::lock_guard<std::mutex> l(d_destslock);
d_dests.erase(remove_if(d_dests.begin(), d_dests.end(), [d](const auto& a)
{
// cerr<<(void*) a.get()<< " ==? " <<(void*) d <<endl;
return a.get() == d;
}), d_dests.end());
cerr<<"Done with serving client "<<addr.toStringWithPort()<<": "<<d_dests.size() <<" destinations left"<<endl;
// cerr<<"Size now: "<<d_dests.size()<<endl;
// some kind of cleanup
}
void NMMSender::Destination::emitNMM(const std::string& out, bool compressed)
{
string msg;
// this bit is exceptionally tricky. We support multiple output formats
// and somehow we do work on that here. This is very stupid.
if(!listener) {
if(dst.empty() || !compressed)
msg="bert";
uint16_t len = htons(out.size());
msg.append((char*)&len, 2);
}
if(dst.empty() || !compressed)
msg="bert";
uint16_t len = htons(out.size());
msg.append((char*)&len, 2);
msg.append(out);
if(!dst.empty() || listener) {
if(!dst.empty()) {
std::lock_guard<std::mutex> l(mut);
queue.push_back(msg);
}

View file

@ -1,15 +1,10 @@
#pragma once
#include <string>
#include <deque>
#include <map>
#include <atomic>
#include "navmon.pb.h"
#include <thread>
#include <mutex>
#include "zstdwrap.hh"
#include "comboaddress.hh"
#include "swrappers.hh"
#include "sclasses.hh"
class NMMSender
{
@ -18,11 +13,10 @@ class NMMSender
int fd{-1};
std::string dst;
std::string fname;
bool listener{false};
std::deque<std::string> queue;
std::mutex mut;
void emitNMM(const std::string& out, bool compress);
std::vector<Destination> clients;
void emitNMM(const NavMonMessage& nmm, bool compress);
};
public:
@ -30,44 +24,27 @@ public:
{
auto d = std::make_unique<Destination>();
d->fd = fd;
std::lock_guard<std::mutex> l(d_destslock);
d_dests.push_back(std::move(d));
}
void addDestination(const std::string& dest)
{
auto d = std::make_unique<Destination>();
d->dst = dest;
std::lock_guard<std::mutex> l(d_destslock);
d_dests.push_back(std::move(d));
}
void addListener(const std::string& dest)
{
auto d = std::make_unique<Destination>();
d->dst = dest;
d->listener = true;
std::lock_guard<std::mutex> l(d_destslock);
d_dests.push_back(std::move(d));
}
void launch()
{
for(auto& d : d_dests) {
if(d->listener) {
d_thread.emplace_back(std::move(std::make_unique<std::thread>(&NMMSender::acceptorThread, this, d.get())));
}
else if(!d->dst.empty()) {
if(!d->dst.empty()) {
d_thread.emplace_back(std::move(std::make_unique<std::thread>(&NMMSender::sendTCPThread, this, d.get())));
}
}
}
void sendTCPThread(Destination* d);
void acceptorThread(Destination* d);
void forwarderThread(Destination* d, NMMSender* there);
void sendTCPListenerThread(Destination* d, int fd, ComboAddress remote);
void sendLoop(Destination* d, SocketCommunicator& sc, std::unique_ptr<ZStdCompressor>& zsc, Socket& s, std::map<uint32_t, std::string>& unacked, time_t connStartTime);
void emitNMM(const NavMonMessage& nmm);
void emitNMM(const std::string& out);
bool d_debug{false};
bool d_compress{false}; // set BEFORE launch
bool d_pleaseQuit{false};
@ -81,7 +58,6 @@ public:
}
private:
std::mutex d_destslock;
std::vector<std::unique_ptr<Destination>> d_dests;
std::vector<std::unique_ptr<std::thread>> d_thread;
};

View file

@ -4,13 +4,9 @@
#include "navmon.hh"
#include "fmt/format.h"
#include "fmt/printf.h"
#include "galileo.hh"
#include "gps.hh"
#include "CLI/CLI.hpp"
#include "version.hh"
#include "ephemeris.hh"
#include "influxpush.hh"
#include "sp3.hh"
static char program[]="reporter";
@ -29,7 +25,7 @@ public:
struct Results
{
vector<double> d;
mutable bool dirty{false};
bool dirty{false};
double median() const
{
return quantile(0.5);
@ -45,7 +41,7 @@ public:
}
};
const Results& done() const
const Results& done()
{
if(results.dirty) {
sort(results.d.begin(), results.d.end());
@ -53,12 +49,9 @@ public:
}
return results;
}
bool empty() const
{
return results.d.empty();
}
private:
mutable Results results;
Results results;
};
/*
@ -84,20 +77,16 @@ private:
struct IntervalStat
{
std::optional<int> unhealthy;
std::optional<int> dataunhealthy;
std::optional<int> osnma;
std::optional<int> sisa;
bool ripe{false};
bool expired{false};
double rtcmDist{-1};
std::optional<double> rtcmDClock;
};
map<SatID, map<time_t,IntervalStat>> g_stats;
int main(int argc, char **argv)
try
{
MiniCurl mc;
MiniCurl::MiniCurlHeaders mch;
@ -111,22 +100,12 @@ try
CLI::App app(program);
string periodarg("1d");
string beginarg, endarg;
string sp3src("default");
int gnssid=2;
int rtcmsrc=300;
int galwn=-1;
string influxserver="http://127.0.0.1:8086";
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--period,-p", periodarg, "period over which to report (1h, 1w)");
app.add_option("--begin,-b", beginarg, "Beginning");
app.add_option("--end,-e", endarg, "End");
app.add_option("--gal-wn", galwn, "Galileo week number to report on");
app.add_option("--sp3src", sp3src, "Identifier of SP3 source");
app.add_option("--rtcmsrc", rtcmsrc, "Identifier of RTCM source");
app.add_option("--sigid,-s", sigid, "Signal identifier. 1 or 5 for Galileo.");
app.add_option("--gnssid,-g", gnssid, "gnssid, 0 GPS, 2 Galileo");
app.add_option("--influxdb", influxDBName, "Name of influxdb database");
app.add_option("--influxserver", influxserver, "Address of influx server");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
@ -138,11 +117,7 @@ try
exit(0);
}
if(galwn>= 0) {
time_t w = utcFromGST(galwn, 0);
period = "time >= '"+influxTime(w)+"' and time < '"+influxTime(w+7*86400) +"'";
}
else if(beginarg.empty() && endarg.empty())
if(beginarg.empty() && endarg.empty())
period = "time > now() - "+periodarg;
else {
period = "time > '"+ beginarg +"' and time <= '" + endarg +"'";
@ -153,16 +128,12 @@ try
// auto res = mc.getURL(url + mc.urlEncode("select distinct(value) from sisa where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
if(influxserver.find("http"))
influxserver="http://"+influxserver;
if(influxserver.empty() || influxserver[influxserver.size()-1]!='/')
influxserver+="/";
string url=influxserver+"query?db="+influxDBName+"&epoch=s&q=";
string sisaname = (gnssid==2) ? "sisa" : "gpsura";
string query="select distinct(value) from "+sisaname+" where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)";
string url="http://127.0.0.1:8086/query?db="+influxDBName+"&epoch=s&q=";
string query="select distinct(value) from sisa where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)";
cout<<"query: "<<query<<endl;
cout<<"url: "<<(url + mc.urlEncode(query))<<endl;
auto res = mc.getURL(url + mc.urlEncode(query));
auto j = nlohmann::json::parse(res);
@ -179,59 +150,27 @@ try
}
}
string healthname = (gnssid == 2) ? "galhealth" : "gpshealth";
string healthfieldname = (gnssid==2) ? "e1bhs" : "value";
res = mc.getURL(url + mc.urlEncode("select distinct("+healthfieldname+") from "+healthname+" where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
res = mc.getURL(url + mc.urlEncode("select distinct(e1bhs) from galhealth where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto healthy = (int)v[1];
g_stats[id][(int)v[0]].unhealthy = healthy; // hngg
g_stats[id][(int)v[0]].unhealthy = healthy;
}
}
if(gnssid == 2) {
res = mc.getURL(url + mc.urlEncode("select distinct(e1bdvs) from galhealth where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto dhealthy = (int)v[1]; // if true, "working without guarantee"
g_stats[id][(int)v[0]].dataunhealthy = dhealthy;
}
}
}
res = mc.getURL(url + mc.urlEncode("select count(\"field\") from osnma where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])};
for(const auto& v : sv["values"]) {
auto osnma = (int)v[1];
if(!g_stats[id][(int)v[0]].osnma)
g_stats[id][(int)v[0]].osnma = osnma;
else
(*g_stats[id][(int)v[0]].osnma) += osnma;
}
}
res = mc.getURL(url + mc.urlEncode("select max(\"eph-age\") from ephemeris where "+period+" and sigid='"+to_string(sigid)+"' group by gnssid,sv,sigid,time(10m)"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
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) {
@ -248,351 +187,32 @@ try
}
}
///////////////////// rtcm-eph
/////////////////////
string rtcmQuery = "select mean(\"radial\") from \"rtcm-eph-correction\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and src='"+to_string(rtcmsrc)+"' group by gnssid,sv,sigid,time(10m)";
cout<<"rtcmquery: "<<rtcmQuery<<endl;
res = mc.getURL(url + mc.urlEncode(rtcmQuery));
res = mc.getURL(url + mc.urlEncode("select mean(\"total-dist\") from \"rtcm-eph-correction\" 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"]) {
try {
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"]) {
try {
auto val = (double)v[1]; // might trigger exception
g_stats[id][(int)v[0]].rtcmDist = val;
}
catch(...){ continue; }
}
}
catch(...) {
continue;
}
}
/////////////////////
///////////////////// rtcm-clock
string rtcmClockQuery = "select mean(\"dclock0\") from \"rtcm-clock-correction\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and src='"+to_string(rtcmsrc)+"' group by gnssid,sv,sigid,time(10m)";
cout<<"rtcmquery: "<<rtcmClockQuery<<endl;
res = mc.getURL(url + mc.urlEncode(rtcmClockQuery));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
try {
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"]) {
try {
auto val = (double) v[1]; // might trigger an exception
if(g_stats.count(id)) // we have some bad data it appears
g_stats[id][(int)v[0]].rtcmDClock = val;
}
catch(...){ continue; }
}
}
catch(...) {
continue;
}
}
/////////////////////
map<SatID, map<time_t, GalileoMessage>> galephs;
map<SatID, map<time_t, GPSState>> gpsephs;
res = mc.getURL(url + mc.urlEncode("select * from \"ephemeris-actual\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' group by gnssid,sv,sigid"));
j = nlohmann::json::parse(res);
for(const auto& sv : j["results"][0]["series"]) {
try {
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"])};
// cout << makeSatIDName(id) <<": "<<sv["columns"] << endl;
map<string, int> cmap;
for(const auto& c : sv["columns"]) {
cmap[c]=cmap.size();
}
for(const auto& v : sv["values"]) {
// cout << makeSatIDName(id)<<": crc "<<v[cmap["crc"]]<<" e " <<v[cmap["e"]] << endl;
if(id.gnss==2) {
GalileoMessage gm;
gm.e = v[cmap["e"]];
gm.crc = v[cmap["crc"]];
gm.crs = v[cmap["crs"]];
gm.cuc = v[cmap["cuc"]];
gm.cus = v[cmap["cus"]];
gm.cic = v[cmap["cic"]];
gm.cis = v[cmap["cis"]];
gm.sqrtA = v[cmap["sqrta"]];
gm.t0e = v[cmap["t0e"]];
gm.m0 = v[cmap["m0"]];
gm.deltan = v[cmap["deltan"]];
gm.omega0 = v[cmap["omega0"]];
gm.omegadot = v[cmap["omegadot"]];
gm.idot = v[cmap["idot"]];
gm.omega = v[cmap["omega"]];
gm.i0 = v[cmap["i0"]];
gm.t0e = v[cmap["t0e"]];
gm.iodnav = v[cmap["iod"]];
if(cmap.count("af0")) {
gm.af0 = v[cmap["af0"]];
gm.af1 = v[cmap["af1"]];
gm.af2 = v[cmap["af2"]];
gm.t0c = v[cmap["t0c"]];
}
Point pos;
galephs[id][v[cmap["time"]]]= gm;
}
else if(id.gnss==0) {
GPSState gm{};
gm.e = v[cmap["e"]];
gm.crc = v[cmap["crc"]];
gm.crs = v[cmap["crs"]];
gm.cuc = v[cmap["cuc"]];
gm.cus = v[cmap["cus"]];
gm.cic = v[cmap["cic"]];
gm.cis = v[cmap["cis"]];
gm.sqrtA = v[cmap["sqrta"]];
gm.t0e = v[cmap["t0e"]];
gm.m0 = v[cmap["m0"]];
gm.deltan = v[cmap["deltan"]];
gm.omega0 = v[cmap["omega0"]];
gm.omegadot = v[cmap["omegadot"]];
gm.idot = v[cmap["idot"]];
gm.omega = v[cmap["omega"]];
gm.i0 = v[cmap["i0"]];
gm.t0e = v[cmap["t0e"]];
gm.gpsiod = v[cmap["iod"]];
if(cmap.count("af0")) {
gm.af0 = v[cmap["af0"]];
gm.af1 = v[cmap["af1"]];
gm.af2 = v[cmap["af2"]];
gm.t0c = v[cmap["t0c"]];
}
Point pos;
gpsephs[id][v[cmap["time"]]]= gm;
}
}
}
catch(...) {
continue;
}
}
cout<<"Gathered ephemerides for "<<galephs.size()<<" galileo + "<<gpsephs.size()<<" GPS signals"<<endl;
/////////////////////
map<SatID, map<time_t, SP3Entry>> sp3s;
string spq3="select * from \"sp3\" where "+period+" and sp3src =~ /"+sp3src+"/ and gnssid='"+to_string(gnssid)+"' group by gnssid,sv,sigid";
cout<<"sp3 query: "<<spq3<<endl;
res = mc.getURL(url + mc.urlEncode(spq3));
j = nlohmann::json::parse(res);
cout<<"Gathered sp, got "<< j["results"][0]["series"].size()<< " tags"<<endl;
for(const auto& sv : j["results"][0]["series"]) {
try {
const auto& tags=sv["tags"];
SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)sigid};
// SP3 data does not have a sigid, it refers to the center of mass, not the antenna phase center
// cout << makeSatIDName(id) <<": "<<sv["columns"] << endl;
map<string, int> cmap;
for(const auto& c : sv["columns"]) {
cmap[c]=cmap.size();
}
for(const auto& v : sv["values"]) {
// cout << makeSatIDName(id)<<": time "<<v[cmap["time"]] <<" x "<<v[cmap["x"]]<<" y " <<v[cmap["y"]] << " z " << v[cmap["z"]] << endl;
SP3Entry e;
e.t = v[cmap["time"]]; // UTC!!
e.x = v[cmap["x"]];
e.y = v[cmap["y"]];
e.z = v[cmap["z"]];
e.clockBias = v[cmap["clock-bias"]];
sp3s[id][e.t]=e;
}
}
catch(std::exception& e)
{
cerr<<"Error: "<<e.what()<<endl;
}
}
ofstream csv("sp3.csv");
csv<<"timestamp gnss sv sigid zerror clkoffset"<<endl;
InfluxPusher idb(influxDBName);
map<SatID, Stats> sp3zerrors, sp3clockerrors;
/////////////////////
for(const auto& svsp3 : sp3s) {
const auto& id = svsp3.first;
const auto& es = svsp3.second;
// cout<<"Looking at SP3 for " << makeSatIDName(id)<<", have "<<es.size()<< " entries"<<endl;
for(const auto& e : es) {
// cout << humanTimeShort(e.first)<<": ";
if(id.gnss == 2) {
const auto& svephs = galephs[id];
auto iter = svephs.lower_bound(e.first);
// this logic is actually sort of wrong & ignores the last ephemeris
if(iter != svephs.end() && iter != svephs.begin()) {
--iter;
// cout << "found ephemeris from "<< humanTimeShort(iter->first)<<" iod "<<iter->second.iodnav;
// our UTC timestamp from SP3 need to be converted into a tow
int offset = id.gnss ? 935280000 : 315964800;
int sp3tow = (e.first - offset) % (7*86400);
Point epos;
getCoordinates(sp3tow, iter->second, &epos);
double clkoffset= iter->second.getAtomicOffset(sp3tow).first - e.second.clockBias;
// cout<<" "<<iter->second.getAtomicOffset(sp3tow).first <<" v " << e.second.clockBias<<" ns ";
// cout << " ("<<epos.x<<", "<<epos.y<<", "<<epos.z<<") - > ("<<e.second.x<<", "<<e.second.y<<", "<<e.second.z<<") " ;
// cout <<" ("<<epos.x - e.second.x<<", "<<epos.y - e.second.y <<", "<<epos.z - e.second.z<<")";
Point sp3pos(e.second.x, e.second.y, e.second.z);
Vector v(epos, sp3pos);
// cout<< " -> " << v.length();
Vector dir(Point(0,0,0), sp3pos);
dir.norm();
Point cv=sp3pos;
cv.x -= 0.519 * dir.x;
cv.y -= 0.519 * dir.y;
cv.z -= 0.519 * dir.z;
Vector v2(epos, cv);
// cout<< " -> " << v2.length();
// cout<<" z-error: "<<dir.inner(v);
csv << e.first << " " << id.gnss <<" " << id.sv << " " << id.sigid <<" " << dir.inner(v) << " " << clkoffset<<endl;
idb.addValue({{"gnssid", id.gnss}, {"sv", id.sv}, {"sp3src", sp3src}},
"sp3delta",
{{"ecef-dx", v.x}, {"ecef-dy", v.y}, {"ecef-dz", v.z}, {"sv-dz", dir.inner(v)}, {"dclock", clkoffset},
{"iod-nav",iter->second.iodnav}},
e.first);
sp3clockerrors[id].add(clkoffset); // nanoseconds
sp3zerrors[id].add(100*dir.inner(v) - 80); // meters -> cm
}
}
else if(id.gnss==0) {
const auto& svephs = gpsephs[id];
// this is keyed on the moment of _issue_
auto iter = svephs.lower_bound(e.first);
if(iter != svephs.end() && iter != svephs.begin()) {
--iter;
// cout << "found ephemeris from "<< humanTimeShort(iter->first)<<" iod "<<iter->second.iodnav;
// our UTC timestamp from SP3 need to be converted into a tow
int offset = 315964800;
int sp3tow = (e.first - offset) % (7*86400);
Point epos;
getCoordinates(sp3tow, iter->second, &epos);
double clkoffset= getGPSAtomicOffset(sp3tow, iter->second).first - e.second.clockBias;
// cout<<" "<<iter->second.getAtomicOffset(sp3tow).first <<" v " << e.second.clockBias<<" ns ";
// cout << " ("<<epos.x<<", "<<epos.y<<", "<<epos.z<<") - > ("<<e.second.x<<", "<<e.second.y<<", "<<e.second.z<<") " ;
// cout <<" ("<<epos.x - e.second.x<<", "<<epos.y - e.second.y <<", "<<epos.z - e.second.z<<")";
Point sp3pos(e.second.x, e.second.y, e.second.z);
Vector v(epos, sp3pos);
// cout<< " -> " << v.length();
Vector dir(Point(0,0,0), sp3pos);
dir.norm();
Point cv=sp3pos;
cv.x -= 0.519 * dir.x;
cv.y -= 0.519 * dir.y;
cv.z -= 0.519 * dir.z;
Vector v2(epos, cv);
// cout<< " -> " << v2.length();
// cout<<" z-error: "<<dir.inner(v);
csv << e.first << " " << id.gnss <<" " << id.sv << " " << id.sigid <<" " << dir.inner(v) << " " << clkoffset << endl;
idb.addValue({{"gnssid", id.gnss}, {"sv", id.sv}, {"sp3src", sp3src}},
"sp3delta",
{{"ecef-dx", v.x}, {"ecef-dy", v.y}, {"ecef-dz", v.z}, {"sv-dz", dir.inner(v)}, {"dclock", clkoffset},
{"iod-nav",iter->second.gpsiod}},
e.first);
sp3clockerrors[id].add(clkoffset); // nanoseconds
sp3zerrors[id].add(100*dir.inner(v)); // meters -> cm
}
g_stats[id][(int)v[0]].rtcmDist = v[1];
}
}
catch(...) {
continue;
}
}
/////
string dishesQuery = "select iod,sv from \"ephemeris-actual\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and iod < 128";
cout<<"dishesquery: "<<dishesQuery<<endl;
res = mc.getURL(url + mc.urlEncode(dishesQuery));
cout<<res<<endl;
j = nlohmann::json::parse(res);
map<time_t, set<int>> dishcount;
set<int> totsvs;
for(const auto& sv : j["results"][0]["series"]) {
for(const auto& v : sv["values"]) {
try {
int sv = (unsigned int)std::stoi((string)v[2]);
int t = (int)v[0];
// t &= (~31);
dishcount[t].insert(sv);
totsvs.insert(sv);
}
catch(exception& e) {
cerr<<"error: "<<e.what()<<endl;
continue;
}
}
}
map<time_t, unsigned int> maxcounts;
for(const auto& dc : dishcount) {
auto& bin = maxcounts[dc.first - (dc.first % 3600)];
if(bin < dc.second.size())
bin = dc.second.size();
cout << dc.first<<" "<<humanTimeShort(dc.first) <<", " << fmt::sprintf("%2d", dc.second.size())<<": ";
for(const auto& n : totsvs) {
if(dc.second.count(n))
cout<<fmt::sprintf("%2d ", n);
else
cout<<" ";
}
cout<<"\n";
}
ofstream hrcounts("hrcounts.csv");
hrcounts<<"timestamp,dishcount\n";
for(const auto& mc: maxcounts)
hrcounts<<mc.first<<","<<mc.second<<"\n";
/////////////////////
g_stats.erase({2,14,1});
g_stats.erase({2,18,1});
g_stats.erase({2,14,5});
g_stats.erase({2,18,5});
/*
g_stats[{2,19,1}];
*/
@ -605,17 +225,9 @@ try
if(sv.second.rbegin()->first > stop)
stop = sv.second.rbegin()->first;
}
unsigned int liveInterval=0;
for(const auto i : sv.second) {
if(i.second.sisa.has_value())
liveInterval++;
else {
// cout<<makeSatIDName(sv.first)<<": no Sisa, "<< i.second.rtcmDClock.has_value()<<" " << i.second.unhealthy.has_value()<<" " <<i.second.rtcmDist<<" ripe "<<i.second.ripe<<endl;
}
}
if(liveInterval > maxintervals)
maxintervals = liveInterval;
if(sv.second.size() > maxintervals)
maxintervals = sv.second.size();
}
cout<<"Report on "<<g_stats.size()<<" SVs from "<<humanTime(start) <<" to " <<humanTime(stop) << endl;
@ -623,22 +235,17 @@ try
int totunobserved=0;
int totripe = 0, totexpired = 0;
Stats totRTCM;
ofstream texstream("stats.tex");
for(const auto& sv : g_stats) {
int napa=0, unhealthy=0, healthy=0, testing=0, ripe=0, expired=0;
Stats rtcm, clockRtcm;
Stats rtcm;
for(const auto& i : sv.second) {
// cout<<humanTimeShort(i.first)<<" "<<((i.second.sisa.has_value()) ? "S" : " ")<<" ";
if(i.second.rtcmDist >= 0) {
rtcm.add(i.second.rtcmDist);
totRTCM.add(i.second.rtcmDist);
}
if(i.second.rtcmDClock)
clockRtcm.add(*i.second.rtcmDClock * 100);
if(i.second.ripe)
ripe++;
@ -651,10 +258,7 @@ try
else if(*i.second.unhealthy==3)
testing++;
else {
if(i.second.dataunhealthy && *i.second.dataunhealthy) { // this is 'working without guarantee'
unhealthy++;
}
else if(i.second.sisa) {
if(i.second.sisa) {
if(*i.second.sisa == 255)
napa++;
else
@ -669,93 +273,38 @@ try
napa++;
}
}
// cout<<endl;
totnapa += napa;
totunhealthy += unhealthy;
tottesting += testing;
tothealthy += healthy;
totripe += ripe;
totexpired += expired;
int liveInterval=0;
for(const auto i : sv.second)
if(i.second.sisa.has_value())
liveInterval++;
totunobserved += maxintervals - liveInterval;
cout<<fmt::sprintf("%s: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa, %6.2f%% ripe, %6.2f%% expired",
makeSatPartialName(sv.first),
100.0*(maxintervals-liveInterval)/maxintervals,
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, %6.2f%% ripe, %6.2f%% expired, %.1f - %.1f - %.1f cm",
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*ripe/maxintervals,
100.0*expired/maxintervals);
texstream << fmt::sprintf("%s & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%%\\\\",
makeSatPartialName(sv.first),
100.0*(maxintervals-liveInterval)/maxintervals,
100.0*unhealthy/maxintervals,
100.0*healthy/maxintervals,
100.0*testing/maxintervals,
100.0*napa/maxintervals,
100.0*ripe/maxintervals,
100.0*expired/maxintervals) << endl;
if(!rtcm.empty())
cout<<fmt::sprintf(", %.1f - %.1f - %.1f cm",
rtcm.done().quantile(0.10)/10, rtcm.done().median()/10, rtcm.done().quantile(0.9)/10);
if(!clockRtcm.empty())
cout<<fmt::sprintf(", c %.1f - %.1f - %.1f cm",
clockRtcm.done().quantile(0.10), clockRtcm.done().median(), clockRtcm.done().quantile(0.9));
if(!sp3zerrors[sv.first].empty()) {
const auto& z = sp3zerrors[sv.first];
cout<<fmt::sprintf(", z %.1f - %.1f - %.1f cm",
z.done().quantile(0.10), z.done().median(), z.done().quantile(0.9));
}
cout<<endl;
100.0*expired/maxintervals,
rtcm.done().quantile(0.10)/10, rtcm.done().median()/10, rtcm.done().quantile(0.9)/10
)<<endl;
}
cout<<"------------------------------------------------------------------------------------------"<<endl;
cout<<fmt::sprintf("Tot: %6.2f%% unobserved, %6.2f%% unhealthy, %6.2f%% healthy, %6.2f%% testing, %6.2f%% napa, %6.2f%% ripe, %6.2f%% expired",
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, %.1f - %.1f - %.1f cm",
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*totripe/maxintervals/g_stats.size(),
100.0*totexpired/maxintervals/g_stats.size());
texstream<<fmt::sprintf("\\hline\nTot & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%% & %6.2f\\%%\\\\",
100.0*(totunobserved)/maxintervals/g_stats.size(),
100.0*totunhealthy/maxintervals/g_stats.size(),
100.0*tothealthy/maxintervals/g_stats.size(),
100.0*tottesting/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;
if(!totRTCM.empty())
cout<<fmt::sprintf(", %.1f - %.1f - %.1f cm",
totRTCM.done().quantile(0.10)/10, totRTCM.done().median()/10, totRTCM.done().quantile(0.9)/10);
cout<<endl;
100.0*totexpired/maxintervals/g_stats.size(),
totRTCM.done().quantile(0.10)/10, totRTCM.done().median()/10, totRTCM.done().quantile(0.9)/10
)<<endl;
}
catch(exception& e)
{
cerr<<"Fatal error: "<<e.what()<<endl;
return EXIT_FAILURE;
}

View file

@ -61,22 +61,6 @@ RINEXReader::~RINEXReader()
gzclose(d_fp);
}
/* RINEX format.. is very special. This extracts a value from a line
where it should be noted values can be and often are back to back
*/
static double getRINEXValue(char* line, int offset)
{
char* ptr=line+offset+19;
char tmp = *ptr;
*ptr = 0;
double ret;
sscanf(line + offset, "%lf", &ret);
// cout<<"'"<<string(line+offset)<<"'\n";
*ptr = tmp;
return ret;
}
bool RINEXReader::get(RINEXEntry& entry)
{
char line[300];
@ -95,7 +79,7 @@ G02 2019 12 16 00 00 00-3.670863807201E-04-7.389644451905E-12 0.000000000000E+00
*/
// SV YR MN DY HR MN SS___________________===================___________________
// SV YR MN DY HR MN SS_______________====================______________________
// G02 2019 12 16 00 00 00-3.670863807201E-04-7.389644451905E-12 0.000000000000E+00
for(;;) {
@ -131,8 +115,7 @@ G02 2019 12 16 00 00 00-3.670863807201E-04-7.389644451905E-12 0.000000000000E+00
continue;
}
char tmp=line[24];
line[24]=0;
char gnss;
if(sscanf(line, "%c%02d %d %d %d %d %d %d",
@ -140,48 +123,52 @@ G02 2019 12 16 00 00 00-3.670863807201E-04-7.389644451905E-12 0.000000000000E+00
&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 8) {
throw std::runtime_error("Failed to parse '"+string(line)+"'");
}
line[24]=tmp;
bool skip=false;
/*
if(tm.tm_year != 2019 || tm.tm_mon != 7)
skip=true;
if((entry.sv == 33 || entry.sv == 36 || entry.sv == 13 || entry.sv == 15)
&& tm.tm_mon < 3)
skip = true;
*/
tm.tm_mon -= 1;
tm.tm_year -= 1900;
entry.t=timegm(&tm);
// af0, af1, af2
entry.af0 = getRINEXValue(line, 23);
entry.af1 = getRINEXValue(line, 42);
entry.af2 = getRINEXValue(line, 61);
// 5 lines of which we store a bit, store 6th
for(int n=1 ; n < 7; ++n) {
// skip 5 lines, store 6th
for(int n=0 ; n < 6; ++n) {
if(!gzgets(d_fp, line, sizeof(line)))
return false;
if(n==1) {
entry.iodnav = getRINEXValue(line, 4);
}
if(n==3) {
double toe = getRINEXValue(line, 4);
if(n==2) {
line[23]=0;
double toe;
sscanf(line+4, "%lf", &toe);
entry.toe = toe;
}
if(n==5) {
entry.clkflags = getRINEXValue(line, 23);
}
if(n==6) {
entry.BGDE1E5a = getRINEXValue(line, 42);
entry.BGDE1E5b = getRINEXValue(line, 61);
}
// cerr<<"Line "<<n<<": "<<line;
}
// line 6
entry.sisa = getRINEXValue(line, 4);
double health = getRINEXValue(line, 23);
char tmp=line[23];
line[23]=0;
sscanf(line+4, "%lf", &entry.sisa);
line[23]=tmp;
tmp=line[42];
line[42]=0;
double health;
sscanf(line+23, "%lf", &health);
entry.health = health; // yeah..
//last line, number 7
//last line
if(!gzgets(d_fp, line, sizeof(line)))
return false;
double tow = getRINEXValue(line, 4);
line[23]=0;
double tow;
sscanf(line+4, "%lf", &tow);
entry.tow = tow;
if(skip)

View file

@ -18,10 +18,6 @@ struct RINEXEntry
int health;
int toe;
int tow;
int iodnav;
double af0, af1, af2;
double clkflags;
double BGDE1E5a, BGDE1E5b;
};
class RINEXReader
@ -73,8 +69,7 @@ E01 2019 09 21 23 30 00-6.949011585675E-04-7.943867785798E-12 0.000000000000E+00
time_t then = utcFromGST(e.wn, (int)e.tow);
struct tm tm;
gmtime_r(&then, &tm);
// 0
d_ofs << makeSatPartialName(sid)<<" " << fmt::sprintf("%04d %02d %02d %02d %02d %02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
emit(ldexp(e.af0, -34));
@ -82,48 +77,39 @@ E01 2019 09 21 23 30 00-6.949011585675E-04-7.943867785798E-12 0.000000000000E+00
emit(ldexp(e.af2, -59));
d_ofs<<"\n ";
// 1
emit(e.iodnav);
emit(e.getCrs());
emit(e.getDeltan());
emit(e.getM0());
d_ofs<<"\n ";
// 2
emit(e.getCuc());
emit(e.getE());
emit(e.getCus());
emit(e.getSqrtA());
d_ofs<<"\n ";
// 3
emit(e.getT0e());
emit(e.getCic());
emit(e.getOmega0());
emit(e.getCis());
d_ofs<<"\n ";
// 4
emit(e.getI0());
emit(e.getCrc());
emit(e.getOmega());
emit(e.getOmegadot());
d_ofs<<"\n ";
// 5
emit(e.getIdot());
emit(257); // bit 0 = I/NAV E1, bit 1 = F/NAV E5a, bit 2 = I/NAV E5b
// bit 8 = E1,E5a clock aka F/NAV, bit 9 = E1,E5b aka I/NAV
emit(257);
emit(e.wn + 1024); // so it aligns with GPS
// 6
d_ofs<<"\n ";
emit(numSisa(e.sisa));
int health=0;
int health=0;
health |= e.e1bdvs;
health |= (e.e1bhs << 2);
// don't have e5advs
@ -134,7 +120,6 @@ E01 2019 09 21 23 30 00-6.949011585675E-04-7.943867785798E-12 0.000000000000E+00
emit(ldexp(e.BGDE1E5a, -32));
emit(ldexp(e.BGDE1E5b, -32));
// 7
d_ofs<<"\n ";
emit(e.tow); // XXX

View file

@ -1,52 +0,0 @@
#include <iostream>
#include "rinex.hh"
#include <map>
#include <optional>
using namespace std;
struct Value
{
optional<int> af0Inav;
optional<int> af0Fnav;
int af1;
int iod;
optional<int> BGDE1E5a;
optional<int> BGDE1E5b;
};
map<pair<time_t, int>, Value> satmap;
int main(int argc, char** argv)
{
for(int n = 1; n < argc; ++n) {
RINEXReader rr(argv[n]);
RINEXEntry e;
while(rr.get(e)) {
if(e.gnss != 2)
continue;
// cout << e.t <<" " << e.sv <<" " << (int64_t)(rint(ldexp(e.af0,34))) <<" " << (int64_t)(rint(ldexp(e.BGDE1E5a,32)))<<" " << (int64_t)(rint(ldexp(e.BGDE1E5b,32))) <<" "<<e.clkflags <<endl;
auto& s=satmap[{e.t, e.sv}];
if(((unsigned int)e.clkflags) & 512) { // I/NAV
s.af0Inav = rint(ldexp(e.af0,34));
s.af1 = rint(ldexp(e.af1,46));
s.BGDE1E5a = rint(ldexp(e.BGDE1E5a,32));
s.BGDE1E5b = rint(ldexp(e.BGDE1E5b,32));
s.iod = e.iodnav;
}
else {
s.af0Fnav = rint(ldexp(e.af0,34));
s.af1 = rint(ldexp(e.af1,46));
s.BGDE1E5a = rint(ldexp(e.BGDE1E5a,32));
// E1E5b unreliable on F/NAV somehow
}
}
}
cout<<"timestamp sv iod af0fnav af0inav af1 bgde1e5a bgde1e5b\n";
for(const auto& s : satmap) {
if(s.second.af0Fnav.has_value() && s.second.af0Inav.has_value() && s.second.BGDE1E5a.has_value() && s.second.BGDE1E5b.has_value())
cout << s.first.first<<" " <<s.first.second<<" " << s.second.iod<<" "<<
*s.second.af0Fnav << " " << *s.second.af0Inav <<" " << s.second.af1<<" " <<*s.second.BGDE1E5a <<" " << *s.second.BGDE1E5b << "\n";
}
}

View file

@ -80,11 +80,13 @@ auto worker(HanderOuter<string>* ho)
cerr<<"Error processing file "<<file<<": "<<e.what()<<endl;
}
}
return stat;
return std::move(stat);
}
int main(int argc, char** argv)
{
ifstream filefile(argv[1]);
string fname;
deque<string> files;

153
rs.cc
View file

@ -1,153 +0,0 @@
#include "rs.hh"
#include <stdexcept>
#include <string.h>
#include <iostream>
extern "C" {
#include <fec.h>
}
using namespace std;
RSCodec::RSCodec(const std::vector<unsigned int>& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad, unsigned int bits)
: d_N((1<< (bits)) - pad -1),
d_K((1<< (bits)) - pad - 1 - nroots),
d_nroots(nroots),
d_bits(bits)
{
if(d_bits > 8)
throw std::runtime_error("This encoder supports 8 bits at most");
for(const auto& r : roots)
d_gfpoly |= (1<<r);
d_rs = init_rs_char(d_bits, d_gfpoly, fcr, prim, nroots, pad);
if(!d_rs)
throw std::runtime_error("Unable to initialize RS codec");
}
void RSCodec::encode(std::string& msg)
{
if(msg.size() > d_K)
throw std::runtime_error("Can't encode message longer than "+std::to_string(d_K)+" bytes");
msg.append(d_K - msg.size(), 0);
// void encode_rs_char(void *rs,unsigned char *data,
// unsigned char *parity);
uint8_t parity[d_nroots];
encode_rs_char(d_rs, (uint8_t*)msg.c_str(), parity);
msg.append((char*)&parity[0], (char*)&parity[d_nroots]);
}
int RSCodec::decode(const std::string& in, std::string& out, vector<unsigned int>* corrs)
{
// int decode_rs_char(void *rs,unsigned char *data,int *eras_pos,
// int no_eras);
unsigned char data[in.length()];
memcpy(data, in.c_str(), in.length());
vector<int> eras_pos;
int eras_no=0;
if(corrs) {
for(const auto& c : *corrs) {
eras_pos.push_back(c);
eras_no++;
}
}
eras_pos.resize(d_nroots);
int ret = decode_rs_char(d_rs, data, &eras_pos[0], eras_no);
/*
The decoder corrects the symbols "in place", returning the number of symbols in error. If the codeword is uncorrectable, -1 is returned and the data block is unchanged. If
eras_pos is non-null, it is used to return a list of corrected symbol positions, in no particular order. This means that the array passed through this parameter must have at
least nroots elements to prevent a possible buffer overflow.
*/
if(ret < 0)
throw std::runtime_error("Could not correct message");
if(corrs)
corrs->clear();
if(ret && corrs) {
for(int n=0; n < ret; ++n)
corrs->push_back(eras_pos.at(n));
}
out.assign((char*) data, (char*)data + d_N);
return ret;
}
RSCodec::~RSCodec()
{
if(d_rs)
free_rs_char(d_rs);
}
////
RSCodecInt::RSCodecInt(const std::vector<unsigned int>& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad, unsigned int bits)
: d_N((1<< (bits)) - pad -1),
d_K((1<< (bits)) - pad - 1 - nroots),
d_nroots(nroots),
d_bits(bits)
{
if(d_bits > 32)
throw std::runtime_error("This encoder supports 32 bits at most");
for(const auto& r : roots)
d_gfpoly |= (1<<r);
d_rs = init_rs_int(d_bits, d_gfpoly, fcr, prim, nroots, pad);
if(!d_rs)
throw std::runtime_error("Unable to initialize RS codec");
}
void RSCodecInt::encode(vector<unsigned int>& msg)
{
if(msg.size() > d_K)
throw std::runtime_error("Can't encode message longer than "+std::to_string(d_K)+" bytes");
msg.resize(d_K);
vector<unsigned int> parity(d_nroots);
encode_rs_int(d_rs, (int*)&msg[0], (int*)&parity[0]);
for(const auto& i : parity)
msg.push_back(i);
}
int RSCodecInt::decode(const std::vector<unsigned int>& in, std::vector<unsigned int>& out, vector<unsigned int>* corrs)
{
// int decode_rs_char(void *rs,unsigned char *data,int *eras_pos,
// int no_eras);
vector<unsigned int> data = in;
vector<unsigned int> eras_pos;
int eras_no=0;
if(corrs) {
for(const auto& c : *corrs) {
eras_pos.push_back(c);
eras_no++;
}
}
eras_pos.resize(d_nroots);
int ret = decode_rs_int(d_rs, (int*)&data[0], (int*)&eras_pos[0], eras_no);
/*
The decoder corrects the symbols "in place", returning the number of symbols in error. If the codeword is uncorrectable, -1 is returned and the data block is unchanged. If
eras_pos is non-null, it is used to return a list of corrected symbol positions, in no particular order. This means that the array passed through this parameter must have at
least nroots elements to prevent a possible buffer overflow.
*/
if(ret < 0)
throw std::runtime_error("Could not correct message");
if(corrs)
corrs->clear();
if(ret && corrs) {
for(int n=0; n < ret; ++n)
corrs->push_back(eras_pos[n]);
}
out = data;
out.resize(d_N);
return ret;
}
RSCodecInt::~RSCodecInt()
{
if(d_rs)
free_rs_int(d_rs);
}

44
rs.hh
View file

@ -1,44 +0,0 @@
#pragma once
#include <string>
#include <vector>
class RSCodec
{
public:
RSCodec(const std::vector<unsigned int>& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad=0, unsigned int bits=8);
void encode(std::string& msg);
int decode(const std::string& in, std::string& out, std::vector<unsigned int>* corrections=0);
int getPoly() // the representation as a number
{
return d_gfpoly;
}
~RSCodec();
private:
void* d_rs{0};
unsigned int d_gfpoly{0};
public:
const unsigned int d_N, d_K, d_nroots, d_bits;
};
class RSCodecInt
{
public:
RSCodecInt(const std::vector<unsigned int>& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad=0, unsigned int bits=8);
void encode(std::vector<unsigned int>& msg);
int decode(const std::vector<unsigned int>& in, std::vector<unsigned int>& out, std::vector<unsigned int>* corrections=0);
int getPoly() // the representation as a number
{
return d_gfpoly;
}
~RSCodecInt();
private:
void* d_rs{0};
unsigned int d_gfpoly{0};
public:
const unsigned int d_N, d_K, d_nroots, d_bits;
};

228
rtcm.cc
View file

@ -1,33 +1,18 @@
#include "rtcm.hh"
#include "bits.hh"
#include <iostream>
#include <string.h>
using namespace std;
void RTCMMessage::parse(const std::string& str)
{
d_gm={};
// memset(&d_gm, 0, sizeof(d_gm));
auto gbu=[&str](int offset, int bits) {
return getbitu((const unsigned char*)str.c_str(), offset, bits);
};
auto gbum=[&str](int& offset, int bits) {
unsigned int ret = getbitu((const unsigned char*)str.c_str(), offset, bits);
offset += bits;
return ret;
};
auto gbs=[&str](int offset, int bits) {
return getbits((const unsigned char*)str.c_str(), offset, bits);
};
auto gbsm=[&str](int& offset, int bits) {
int ret = getbits((const unsigned char*)str.c_str(), offset, bits);
offset += bits;
return ret;
};
type = gbu(0, 12);
// cout<<"Message number: "<<type << " of size "<<str.size()<<"\n";
if(type == 1057 || type == 1240) {
@ -44,7 +29,7 @@ void RTCMMessage::parse(const std::string& str)
}
int sats = gbu(62, 6);
sow = gbu(12, 20); // this is DF385
sow = gbu(12, 20);
udi = gbu(32, 4);
mmi = gbu(36, 1);
reference = gbu(37,1);
@ -60,16 +45,16 @@ void RTCMMessage::parse(const std::string& str)
EphemerisDelta ed;
int off = 68+stride*n;
ed.radial = gbs(off+ iodlen + 6, 22) * 0.1; // we store this in millimeters
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; // we store this in mm/s
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;
ed.udi = udi;
if(type == 1057) {
ed.id.gnss = 0;
ed.id.sigid = 0;
@ -98,11 +83,9 @@ void RTCMMessage::parse(const std::string& str)
// 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;
cd.sow = sow;
cd.udi = udi;
if(type == 1058) {
cd.id.gnss = 0;
cd.id.sigid = 0;
@ -114,20 +97,9 @@ void RTCMMessage::parse(const std::string& str)
int off = 67+76*n;
cd.id.sv = gbu(off +0, 6);
/*
C0 polynomial coefficient for correction of broadcast satellite clock.
The reference time t0 is Epoch Time (DF385, DF386) plus 12 SSR
Update Interval. The reference time t0 for SSR Update Interval 0 is
Epoch Time
DF 385: Full seconds since the beginning of the GPS week
*/
cd.dclock0 = gbs(off + 6, 22)*1e-4; // in 0.1 mm, this converts to meters
cd.dclock1 = gbs(off + 28, 21)*1e-6; // meter/s
cd.dclock2 = gbs(off + 49, 27)*2e-8; // meter/s^2
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 <<" ";
@ -135,184 +107,6 @@ DF 385: Full seconds since the beginning of the GPS week
// cout<< cd.dclock2 << endl;
}
}
else if(type == 1060 || type == 1243) { // combined
int sow = gbu(12, 20);
int udi = gbu(32, 4);
// int mmi = gbu(36, 1);
// int srd = gbu(37, 1);
ssrIOD = gbu(38, 4);
ssrProvider = gbu(42, 16);
ssrSolution=gbu(58, 4);
unsigned int numsats=gbu(62, 6);
int offset=68;
d_ephs.clear();
d_clocks.clear();
int iodlen = type == 1060 ? 8 : 10;
for(unsigned int n=0; n < numsats; ++n) {
ClockDelta cd;
EphemerisDelta ed;
int off = offset + n*(197 + iodlen);
cd.sow = ed.sow = sow;
cd.udi = ed.udi = udi;
cd.id.gnss = (type == 1060) ? 0 : 2; // GPS or Galileo
cd.id.sv = gbu(off + 0, 6);
cd.id.sigid = (type == 1060) ? 0 : 1;
ed.id = cd.id;
ed.iod = gbu(off + 6, iodlen);
int shift = iodlen - 8;
ed.radial = gbs(off + 14 + shift, 22 ) * 0.1; // we store this in millimeters
ed.along = gbs(off + 36 + shift, 20 ) * 0.4;
ed.cross = gbs(off + 56 + shift, 20 ) * 0.4;
ed.dradial= gbs(off + 76 + shift, 21) * 0.001; // we store this in mm/s
ed.dalong = gbs(off + 97 + shift, 19) * 0.004;
ed.dcross = gbs(off +116 + shift, 19) * 0.004;
d_ephs.push_back(ed);
cd.iod = ed.iod;
cd.dclock0 = gbs(off + 135 + shift, 22)*1e-4; // in 0.1 mm, this converts to meters
cd.dclock1 = gbs(off + 157 + shift, 21)*1e-6; // meter/s
cd.dclock2 = gbs(off + 178 + shift, 27)*2e-8; // meter/s^2
// 205 / 207
d_clocks.push_back(cd);
}
}
else if(type == 1045 || type == 1046) { // F/NAV or I/NAV respectively ephemeris
int off=12;
d_sv = gbum(off, 6);
d_gm.wn = gbum(off, 12);
d_gm.iodnav = gbum(off, 10);
d_gm.sisa = gbum(off, 8);
d_gm.idot = gbsm(off, 14);
d_gm.t0c = gbum(off, 14);
d_gm.af2 = gbsm(off, 6);
d_gm.af1 = gbsm(off, 21);
d_gm.af0 = gbsm(off, 31);
//
d_gm.crs = gbsm(off, 16);
d_gm.deltan = gbsm(off, 16);
d_gm.m0 = gbsm(off, 32);
d_gm.cuc = gbsm(off, 16);
d_gm.e = gbum(off, 32);
d_gm.cus = gbsm(off, 16);
d_gm.sqrtA = gbum(off, 32);
d_gm.t0e = gbum(off, 14);
//
d_gm.cic = gbsm(off, 16);
d_gm.omega0 = gbsm(off, 32);
d_gm.cis = gbsm(off, 16);
d_gm.i0 = gbsm(off, 32);
d_gm.crc = gbsm(off, 16);
d_gm.omega = gbsm(off, 32);
d_gm.omegadot = gbsm(off, 24);
// 16 + 16 + 32 + 16 + 32 + 16 + 32 + 14 +
// crs deln M0 cuc e cus sqrA toe cic OM0 cis i0 crc omeg omegdot
// off += 16+ 32 +16 + 32 + 16 + 32 +24;
d_gm.BGDE1E5a = gbsm(off, 10);
if(type == 1046) { // I/NAV
d_gm.BGDE1E5b = gbsm(off, 10);
}
else {
d_gm.BGDE1E5b = 9999999;
}
// thank you RTKLIB:
#if 0
setbitu(rtcm->buff,i,12,1045 ); i+=12;
setbitu(rtcm->buff,i, 6,prn ); i+= 6;
setbitu(rtcm->buff,i,12,week ); i+=12;
setbitu(rtcm->buff,i,10,eph->iode); i+=10;
setbitu(rtcm->buff,i, 8,eph->sva ); i+= 8;
setbits(rtcm->buff,i,14,idot ); i+=14;
setbitu(rtcm->buff,i,14,toc ); i+=14;
setbits(rtcm->buff,i, 6,af2 ); i+= 6;
setbits(rtcm->buff,i,21,af1 ); i+=21;
setbits(rtcm->buff,i,31,af0 ); i+=31;
setbits(rtcm->buff,i,16,crs ); i+=16;
setbits(rtcm->buff,i,16,deln ); i+=16;
setbits(rtcm->buff,i,32,M0 ); i+=32;
setbits(rtcm->buff,i,16,cuc ); i+=16;
setbitu(rtcm->buff,i,32,e ); i+=32;
setbits(rtcm->buff,i,16,cus ); i+=16;
setbitu(rtcm->buff,i,32,sqrtA ); i+=32;
setbitu(rtcm->buff,i,14,toe ); i+=14;
setbits(rtcm->buff,i,16,cic ); i+=16;
setbits(rtcm->buff,i,32,OMG0 ); i+=32;
setbits(rtcm->buff,i,16,cis ); i+=16;
setbits(rtcm->buff,i,32,i0 ); i+=32;
setbits(rtcm->buff,i,16,crc ); i+=16;
setbits(rtcm->buff,i,32,omg ); i+=32;
setbits(rtcm->buff,i,24,OMGd ); i+=24;
setbits(rtcm->buff,i,10,bgd1 ); i+=10;
1045: F/NAV
setbitu(rtcm->buff,i, 2,oshs ); i+= 2; /* E5a SVH */
setbitu(rtcm->buff,i, 1,osdvs ); i+= 1; /* E5a DVS */
setbitu(rtcm->buff,i, 7,0 ); i+= 7; /* reserved */
1046: I/NAV
setbits(rtcm->buff,i,10,bgd2 ); i+=10;
setbitu(rtcm->buff,i, 2,oshs1 ); i+= 2; /* E5b SVH */
setbitu(rtcm->buff,i, 1,osdvs1 ); i+= 1; /* E5b DVS */
setbitu(rtcm->buff,i, 2,oshs2 ); i+= 2; /* E1 SVH */
setbitu(rtcm->buff,i, 1,osdvs2 ); i+= 1; /* E1 DVS */
#endif
}
else if(type == 1059 || type == 1242) { // GPS/Galileo bias
int off = 0;
int msgnum = gbum(off, 12);
int gpstime = gbum(off, 20);
int uinterval = gbum(off, 4);
int mmi = gbum(off, 1);
int iodssr = gbum(off, 4);
int ssrprov = gbum(off, 16);
int ssrsol = gbum(off, 4);
int numsats = gbum(off, 6);
// cout <<"msgnum "<<msgnum<<" gpstime " << gpstime<<" numsats "<< numsats<<endl;
d_dcbs.clear();
for(int n=0; n < numsats; ++n) {
int gpsid = gbum(off, 6);
int numdcbs = gbum(off, 5);
// cout<<" "<< (type==1059 ? "G" : "E") <<gpsid<<" has "<<numdcbs <<" DCBs\n";
SatID id;
id.gnss = (type==1059 ? 0 : 2); // GPS or Galileo
id.sv = gpsid;
for(int m = 0 ; m < numdcbs; ++m) {
int sig = gbum(off, 5);
id.sigid = sig;
int dcb = gbsm(off, 14); // 0.01 meter
d_dcbs[id] = 0.01*dcb;
// cout<<" sig "<<sig <<" dcb " << dcb*0.01 << "\n";
/*
Indicator to specify the GPS signal and tracking mode:
0 - L1 C/A
1- L1 P
2- L1 Z-tracking and similar (AS on)
3 - Reserved
4 - Reserved
5 - L2 C/A
6 - L2 L1(C/A)+(P2-P1) (semi-codeless)
7 - L2 L2C (M)
8 - L2 L2C (L)
9 - L2 L2C (M+L)
10 - L2 P
11 - L2 Z-tracking and similar (AS on)
12 - Reserved
13 - Reserved
14 - L5 I
15 - L5 Q
>15 - Reserved.
*/
}
}
}
}

15
rtcm.hh
View file

@ -3,9 +3,6 @@
#include <stdio.h>
#include "navmon.hh"
#include <vector>
#include "galileo.hh"
#include <map>
struct RTCMFrame
{
std::string payload;
@ -34,11 +31,10 @@ struct RTCMMessage
{
SatID id;
// in millimeters
double radial, along, cross; // mm
double dradial, dalong, dcross; // mm/s
double radial, along, cross;
double dradial, dalong, dcross;
int iod;
int sow;
int udi;
};
struct ClockDelta
{
@ -46,14 +42,9 @@ struct RTCMMessage
double dclock0; // in meters
double dclock1;
double dclock2;
int sow;
int udi;
int iod{-1};
};
std::vector<EphemerisDelta> d_ephs;
std::vector<ClockDelta> d_clocks;
std::map<SatID, double> d_dcbs;
GalileoMessage d_gm;
int d_sv;
};

View file

@ -2,7 +2,6 @@
#include "bits.hh"
#include <vector>
#include <iostream>
#include <signal.h>
#include "nmmsender.hh"
#include "CLI/CLI.hpp"
#include "swrappers.hh"
@ -14,14 +13,10 @@ using namespace std;
bool RTCMReader::get(RTCMFrame& rf)
{
int c;
bool skipped=false;
while( ((c=fgetc(d_fp)) != -1) && c != 211) {
skipped=true;
cerr<<".";
cerr<<"Skipped.. "<<endl;
continue;
}
if(skipped)
cerr<<endl;
if(c != 211) {
cerr<<"EOF"<<endl;
@ -153,9 +148,8 @@ int main(int argc, char** argv)
RTCMReader rr(0);
RTCMFrame rf;
cerr<<"Station "<<g_srcid<<endl;
while(rr.get(rf)) {
// cerr<<"Got a "<<rf.payload.size()<<" byte frame"<<endl;
// cout<<"Got a "<<rf.payload.size()<<" byte frame"<<endl;
RTCMMessage rm;
NavMonMessage nmm;
struct timespec ts;

20
sbas.cc
View file

@ -4,14 +4,14 @@ using namespace std;
#include "bits.hh"
#include <math.h>
void SBASState::parse0(const vector<uint8_t>& sbas, time_t now)
void SBASState::parse0(const basic_string<uint8_t>& sbas, time_t now)
{
d_lastDNU = now;
d_lastSeen = now;
}
void SBASState::parse1(const vector<uint8_t>& sbas, time_t now)
void SBASState::parse1(const basic_string<uint8_t>& sbas, time_t now)
{
d_lastSeen = now;
int slot=1;
@ -25,7 +25,7 @@ void SBASState::parse1(const vector<uint8_t>& sbas, time_t now)
}
}
vector<SBASState::FastCorrection> SBASState::parse2_5(const vector<uint8_t>&sbas, time_t now)
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);
@ -47,7 +47,7 @@ vector<SBASState::FastCorrection> SBASState::parse2_5(const vector<uint8_t>&sbas
return ret;
}
vector<SBASState::FastCorrection> SBASState::parse6(const vector<uint8_t>&sbas, time_t now)
vector<SBASState::FastCorrection> SBASState::parse6(const basic_string<uint8_t>&sbas, time_t now)
{
d_lastSeen = now;
vector<SBASState::FastCorrection> ret;
@ -68,7 +68,7 @@ vector<SBASState::FastCorrection> SBASState::parse6(const vector<uint8_t>&sbas,
return ret;
}
void SBASState::parse7(const vector<uint8_t>&sbas, time_t now)
void SBASState::parse7(const basic_string<uint8_t>&sbas, time_t now)
{
d_lastSeen = now;
d_latency = getbitu(&sbas[0], 14+4, 4);
@ -101,7 +101,7 @@ SatID SBASState::getSBASSatID(int slot) const
return ret;
}
vector<SBASState::LongTermCorrection> SBASState::parse25(const vector<uint8_t>& sbas, time_t t)
vector<SBASState::LongTermCorrection> SBASState::parse25(const basic_string<uint8_t>& sbas, time_t t)
{
d_lastSeen = t;
vector<LongTermCorrection> ret;
@ -111,7 +111,7 @@ vector<SBASState::LongTermCorrection> SBASState::parse25(const vector<uint8_t>&
return ret;
}
pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> SBASState::parse24(const vector<uint8_t>& sbas, time_t t)
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;
@ -135,7 +135,7 @@ pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> S
return ret;
}
pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> SBASState::parse(const std::vector<uint8_t>& sbas, time_t now)
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);
@ -164,7 +164,7 @@ pair<vector<SBASState::FastCorrection>, vector<SBASState::LongTermCorrection>> S
return ret;
}
void SBASState::parse25H(const vector<uint8_t>& sbas, time_t t, int offset, vector<LongTermCorrection>& 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);
@ -217,7 +217,7 @@ void SBASState::parse25H(const vector<uint8_t>& sbas, time_t t, int offset, vect
// old version with ephemeris parsing
#if 0
void parseSBAS25H(int sv, const vector<uint8_t>& sbas, time_t t, ofstream& sbascsv, int offset, map<int, GPSState>* gpseph, const Point& src)
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);

18
sbas.hh
View file

@ -45,22 +45,22 @@ struct SBASState
time_t lastUpdate{-1};
};
std::pair<std::vector<SBASState::FastCorrection>, std::vector<SBASState::LongTermCorrection>> parse(const std::vector<uint8_t>& sbas, time_t now);
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::vector<uint8_t>& message, time_t now);
void parse0(const std::basic_string<uint8_t>& message, time_t now);
// updates slot2prn mapping
void parse1(const std::vector<uint8_t>& message, time_t now);
void parse1(const std::basic_string<uint8_t>& message, time_t now);
std::vector<FastCorrection> parse2_5(const std::vector<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::vector<uint8_t>& message, time_t now);
void parse7(const std::vector<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::vector<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::vector<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;
@ -72,7 +72,7 @@ struct SBASState
std::map<int,int> d_slot2prn;
int d_latency = -1;
time_t d_lastSeen{-1};
void parse25H(const std::vector<uint8_t>& sbas, time_t t, int offset, std::vector<LongTermCorrection>& ret);
void parse25H(const std::basic_string<uint8_t>& sbas, time_t t, int offset, std::vector<LongTermCorrection>& ret);
};

View file

@ -1,747 +0,0 @@
#include <string>
#include "navmon.hh"
#include <iostream>
#include <string.h>
#include <signal.h>
#include <time.h>
#include "bits.hh"
#include <sys/time.h>
#include <arpa/inet.h>
#include "galileo.hh"
#include "nmmsender.hh"
#include "CLI/CLI.hpp"
#include "swrappers.hh"
#include "sclasses.hh"
#include "version.hh"
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/printf.h"
#include "gps.hh"
using namespace std;
static int sepsig2ubx(int sep)
{
if(sep == 1) // GPS L1P
return 0;
else if(sep == 2) // GPS L2P
return 4;
else if(sep== 4) // GPS L5
return 7; // ??
else if(sep == 17)
return 1; // Galileo E1
else if(sep == 19)
return 8; // Galileo E6
else if(sep == 20)
return 6; // Galileo E5a
else if(sep==21)
return 5; // Galileo E5b
else if(sep==22)
return 6; // Galileo "AltBoc"
else if(sep==0)
return 0; // GPS L1
else if(sep==3)
return 4; // GPS L2c
else throw runtime_error("Asked to convert unknown Septentrio signal id "+to_string(sep));
return -1;
}
struct SEPMessage
{
SEPMessage(const std::vector<uint8_t>& str) : d_store(str) {}
uint16_t getID() // includes revision
{
return d_store[4] + 256*d_store[5];
}
uint16_t getIDBare() // includes revision
{
return getID() & 0xFFF;
}
std::vector<uint8_t> getPayload()
{
return makeVec(&d_store[0], 8);
}
std::vector<uint8_t> d_store;
};
/* format:
01 23 45 67
$@ CRC blk-id len [len-8 bytes]
|
multiple of four
bits 0-12 of blk-id are the type
*/
std::pair<SEPMessage, struct timeval> getSEPMessage(int fd, double* timeout)
{
uint8_t marker[2]={0};
bool hadskip=false;
for(;;) {
marker[0] = marker[1];
int res = readn2Timeout(fd, marker+1, 1, timeout);
if(res < 0) {
cerr<<"Readn2Timeout failed: "<<strerror(errno)<<endl;
throw EofException();
}
if(marker[0]=='$' && marker[1]=='@') { // bingo
if(hadskip) {
cerr<<"\n";
hadskip=false;
}
struct timeval tv;
gettimeofday(&tv, 0);
vector <uint8_t> msg= makeVec(marker, 2);
uint8_t b[6];
readn2Timeout(fd, b, 6, timeout);
for(int n=0; n < 6; ++n)
msg.push_back(b[n]);
// 0,1 = crc, 2-3 = marker, 4, 5
// uint16_t blkid = htons(b[2] + 256*b[3]);
uint16_t len = b[4] + 256*b[5];
// cerr<<"Got message of type "<<getbitu((uint8_t*)&blkid, 0, 12)<<", revision "<<
// getbitu((uint8_t*)&blkid, 12, 4)<<" ("<<ntohs(blkid)<<"), len= "<<len<<endl;
uint8_t buffer[len-8];
res=readn2Timeout(fd, buffer, len-8, timeout);
for(int n=0; n < len-8; ++n)
msg.push_back(buffer[n]);
return make_pair(SEPMessage(msg), tv);
}
else if(marker[1] != '$') {
hadskip=true;
cerr<<".";
}
}
}
static char program[]="septool";
uint16_t g_srcid{0};
int main(int argc, char** argv)
try
{
time_t starttime=time(0);
GOOGLE_PROTOBUF_VERIFY_VERSION;
vector<string> destinations;
g_dtLS = 18;
bool doVERSION{false}, doSTDOUT{false};
CLI::App app(program);
string sourceaddr;
bool quiet{false};
string serial, owner, remark;
app.add_option("--source", sourceaddr, "Connect to this IP address:port to source SBF (otherwise stdin)");
app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address");
app.add_option("--station", g_srcid, "Station id")->required();
app.add_option("--serial", serial, "Serial number of your Septentrio device");
app.add_option("--quiet", quiet, "Don't emit noise");
app.add_option("--owner,-o", owner, "Name/handle/nick of owner/operator");
app.add_option("--remark", remark, "Remark for this station");
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_flag("--stdout", doSTDOUT, "Emit output to stdout");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
return app.exit(e);
}
if(doVERSION) {
showVersion(program, g_gitHash);
exit(0);
}
signal(SIGPIPE, SIG_IGN);
NMMSender ns;
ns.d_debug = true;
for(const auto& s : destinations) {
auto res=resolveName(s, true, true);
if(res.empty()) {
cerr<<"Unable to resolve '"<<s<<"' as destination for data, exiting"<<endl;
exit(EXIT_FAILURE);
}
ns.addDestination(s); // ComboAddress(s, 29603));
}
if(doSTDOUT)
ns.addDestination(1);
int srcfd=0;
if(!sourceaddr.empty()) {
ComboAddress src(sourceaddr);
srcfd = socket(src.sin4.sin_family, SOCK_STREAM, 0);
if(srcfd < 0)
unixDie("making socket for SBF connection");
cerr<<"Connecting to "<< src.toStringWithPort()<<" to source data..";
SConnectWithTimeout(srcfd, src, 5);
cerr<<" done"<<endl;
}
ns.d_compress = true;
ns.launch();
cerr<<"Station "<<g_srcid<<endl;
for(;;) {
double to=1000;
auto res = getSEPMessage(srcfd, &to);
if(res.first.getID() == 4023) { // I/NAV
auto str = res.first.getPayload();
struct SEPInav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[32];
} __attribute__((packed));
SEPInav si;
memcpy(&si, &str[0], sizeof(si));
// cerr<<"tow "<<si.towMsec /1000<<" wn "<<si.wn <<" sv " << (int) si.sv - 70<<" ";
int sigid = si.src & 31;
int pbsigid=sepsig2ubx(sigid);
if(!si.crcPassed) {
cerr<<fmt::sprintf("E%02d (sigid %d) I/NAV CRC error, skipping\n", si.sv-70, pbsigid);
continue;
}
std::string inav((char*)si.navBits, 32);
// cerr<<makeHexDump(inav)<<endl;
// byte order adjustment
std::vector<uint8_t> payload;
for(unsigned int i = 0 ; i < 8; ++i) {
payload.push_back(si.navBits[4 * i + 3]);
payload.push_back(si.navBits[4 * i + 2]);
payload.push_back(si.navBits[4 * i + 1]);
payload.push_back(si.navBits[4 * i + 0]);
}
/*
NAVBits contains the 234 bits of an I/NAV navigation page (in nominal
or alert mode). Note that the I/NAV page is transmitted as two sub-pages
(the so-called even and odd pages) of duration 1 second each (120 bits
each). In this block, the even and odd pages are concatenated, even page
rst and odd page last. The 6 tails bits at the end of the even page are
removed (hence a total of 234 bits). If the even and odd pages have been
received from two different carriers (E5b and L1), bit 5 of the Source
eld is set.
Encoding: NAVBits contains all the bits of the frame, with the ex-
ception of the synchronization eld. The rst received bit is stored as the
MSB of NAVBits[0]. The unused bits in NAVBits[7] must be ignored
by the decoding software.
*/
/* so we find EVEN_PAGE ODD_PAGE */
vector<uint8_t> inav2;
// copy in the payload bits of the even page
for(int n = 0 ; n < 14; ++n)
inav2.push_back(getbitu(&payload[0], 2 + n*8, 8));
// odd page payload bits
for(int n = 0 ; n < 2; ++n)
inav2.push_back(getbitu(&payload[0], 116 + n*8, 8));
// cerr<<makeHexDump(inav2) << endl;
vector<uint8_t> reserved1;
for(int n=0; n < 5 ; ++n)
reserved1.push_back(getbitu(&payload[0], 116 + 16 + n*8, 8));
vector<uint8_t> crc;
for(int n=0; n < 3 ; ++n)
crc.push_back(getbitu(&payload[0], 116 + 16 + 40 +22 + 2 + n*8, 8));
uint8_t ssp = getbitu(&payload[0], 116 + 16 + 40 + 22 + 2 + 24, 8);
// xxx add reserved2
// xxx add sar
// xxx add spare
NavMonMessage nmm;
double t = utcFromGST(si.wn - 1024, si.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoInavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds((t - floor(t))*1000000000);
nmm.mutable_gi()->set_gnsswn(si.wn - 1024);
// -2 since Septentrio counts from the *end* of the message
nmm.mutable_gi()->set_gnsstow(si.towMsec/1000.0 - 2);
nmm.mutable_gi()->set_gnssid(2);
nmm.mutable_gi()->set_gnsssv(si.sv - 70);
nmm.mutable_gi()->set_contents((const char*)&inav2[0], inav2.size());
nmm.mutable_gi()->set_sigid(pbsigid);
nmm.mutable_gi()->set_reserved1((const char*)&reserved1[0], reserved1.size());
if(pbsigid==1) // on E5b there is no SSP
nmm.mutable_gi()->set_ssp(ssp);
nmm.mutable_gi()->set_crc((const char*)&crc[0], crc.size());
ns.emitNMM( nmm);
}
else if(res.first.getID() == 5914) { // current time
auto str = res.first.getPayload();
struct TimeMsg {
uint32_t tow;
uint16_t wn;
int8_t utcyear;
int8_t utcmonth; // 1 = jan
int8_t utcday;
int8_t utchour;
int8_t utcmin;
int8_t utcsec;
int8_t deltals;
uint8_t synclevel;
} __attribute__((packed));
TimeMsg tmsg;
memcpy(&tmsg, &str[0], sizeof(tmsg));
if(!quiet)
cerr<< fmt::sprintf("UTC Time: %04d%02d%02d %02d:%02d:%02d\n",
2000+tmsg.utcyear,
tmsg.utcmonth,
tmsg.utcday,
tmsg.utchour,
tmsg.utcmin,
tmsg.utcsec);
}
else if(res.first.getID() == 4026) {
// GLONASS
}
else if(res.first.getID() == 4017) { // GPS-CA
auto str = res.first.getPayload();
struct GPSCA
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[40];
} __attribute__((packed));
GPSCA ga;
memcpy(&ga, &str[0], sizeof(ga));
int sigid = ga.src & 31;
// cerr<<"tow "<<sf.towMsec /1000<<" wn "<<sf.wn <<" sv " << (int) sf.sv - 70<<" sigid " << sigid <<" ";
if(!ga.crcPassed) {
cerr<<fmt::sprintf("G%02d CA CRC error, skipping\n", ga.sv);
continue;
}
// we get 10 words of 32 bits.
// bits 0-5 are 6 parity bits
// bits 6-29 are contents
// bits 30-31 are padding
// we need to output to protobuf the parity AND padding bits as well
// downstream they get stripped
unsigned char tmp[40]={};
for(int i = 0; i < 10; ++i) {
uint32_t rev= htonl(*(uint32_t*)&ga.navBits[4*i]);
setbitu(tmp, i*32, 30, getbitu((uint8_t*)&rev, 0, 30));
}
auto payload = makeVec(tmp, 40);
auto cond = getCondensedGPSMessage(payload);
// cerr<<makeHexDump(cond)<<" ";
GPSState gs;
gs.parseGPSMessage(cond);
NavMonMessage nmm;
double t = utcFromGST(ga.wn - 1024, ga.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GPSInavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds((t - floor(t))*1000000000);
nmm.mutable_gpsi()->set_gnsswn(ga.wn);
int pbsigid=sepsig2ubx(sigid);
nmm.mutable_gpsi()->set_sigid(pbsigid);
nmm.mutable_gpsi()->set_gnsstow(ga.towMsec/1000 - 6); // needs to be adjusted to beginning of message
nmm.mutable_gpsi()->set_gnssid(0);
nmm.mutable_gpsi()->set_gnsssv(ga.sv);
nmm.mutable_gpsi()->set_contents(string((char*)&payload[0], payload.size()));
ns.emitNMM( nmm);
}
else if(res.first.getID() == 4018) { // GPS-L2C
}
else if(res.first.getID() == 4019) { // GPS raw L5
}
else if(res.first.getID() == 4093) { // navic raw
}
else if(res.first.getID() == 4022) { // F/NAV
auto str = res.first.getPayload();
struct SEPFnav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[32];
} __attribute__((packed));
SEPFnav sf;
memcpy(&sf, &str[0], sizeof(sf));
int sigid = sf.src & 31;
// cerr<<"tow "<<sf.towMsec /1000<<" wn "<<sf.wn <<" sv " << (int) sf.sv - 70<<" sigid " << sigid <<" ";
if(!sf.crcPassed) {
cerr<<fmt::sprintf("E%02d F/NAV CRC error, skipping\n", sf.sv-70);
continue;
}
std::string fnav((char*)sf.navBits, 32);
// byte order adjustment
std::vector<uint8_t> payload;
for(unsigned int i = 0 ; i < 8; ++i) {
payload.push_back(sf.navBits[4 * i + 3]);
payload.push_back(sf.navBits[4 * i + 2]);
payload.push_back(sf.navBits[4 * i + 1]);
payload.push_back(sf.navBits[4 * i + 0]);
}
NavMonMessage nmm;
double t = utcFromGST(sf.wn - 1024, sf.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoFnavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds(0); // yeah XXX
nmm.mutable_gf()->set_gnsswn(sf.wn - 1024);
nmm.mutable_gf()->set_gnsstow(sf.towMsec/1000.0);
nmm.mutable_gf()->set_gnssid(2);
nmm.mutable_gf()->set_gnsssv(sf.sv - 70);
nmm.mutable_gf()->set_contents((const char*)&payload[0], payload.size());
nmm.mutable_gf()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM( nmm);
}
else if(res.first.getIDBare() == 4047) {
// BDSRaw
}
else if(res.first.getIDBare() == 4006) {
auto str = res.first.getPayload();
struct PVTCartesian
{
uint32_t towMsec;
uint16_t wn;
uint8_t mode, error;
double x, y, z;
float undulation;
float vx, vy, vz;
float cog;
double rxclkbias;
float rxclkdrift;
uint8_t timesystem;
uint8_t datum;
uint8_t nrsv;
uint8_t wacorrinfo;
uint16_t referenceid;
uint16_t meancorrage;
uint32_t signalinfo;
uint8_t alertflag;
uint8_t nrbases;
uint16_t pppinfo;
uint16_t latency;
uint16_t haccuracy;
uint16_t vaccuracy;
uint8_t misc;
} __attribute__((packed));
PVTCartesian pc;
memcpy(&pc, &str[0], sizeof(pc));
NavMonMessage nmm;
nmm.set_type(NavMonMessage::ObserverPositionType);
nmm.set_localutcseconds(utcFromGST(pc.wn - 1024, pc.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_sourceid(g_srcid);
nmm.mutable_op()->set_x(pc.x);
nmm.mutable_op()->set_y(pc.y);
nmm.mutable_op()->set_z(pc.z);
// cerr << "acc: "<<pc.haccuracy*0.01 <<" "<<pc.vaccuracy*0.01<<" nrsv "<<(int)pc.nrsv<<endl;
nmm.mutable_op()->set_acc( sqrt(pc.haccuracy*0.005*pc.haccuracy*0.005 + pc.vaccuracy*0.005*pc.vaccuracy*0.005)); // septentrio reports twice the error
ns.emitNMM( nmm);
{
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(pc.wn - 1024, pc.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ObserverDetailsType);
nmm.mutable_od()->set_vendor("Septentrio");
nmm.mutable_od()->set_hwversion("Mosaic");
nmm.mutable_od()->set_swversion("");
nmm.mutable_od()->set_serialno(serial);
nmm.mutable_od()->set_modules("");
nmm.mutable_od()->set_clockoffsetns(0);
nmm.mutable_od()->set_clockoffsetdriftns(0);
nmm.mutable_od()->set_clockaccuracyns(0);
nmm.mutable_od()->set_freqaccuracyps(0);
nmm.mutable_od()->set_owner(owner);
nmm.mutable_od()->set_remark(remark);
nmm.mutable_od()->set_recvgithash(g_gitHash);
nmm.mutable_od()->set_uptime(time(0) - starttime);
ns.emitNMM( nmm);
}
}
else if(res.first.getIDBare() == 4024) { // GALRawCNAV
struct SEPCnav
{
uint32_t towMsec;
uint16_t wn;
uint8_t sv;
uint8_t crcPassed;
uint8_t viterbiCount;
uint8_t src;
uint8_t ign1;
uint8_t rxChannel;
uint8_t navBits[64];
} __attribute__((packed));
SEPCnav sc;
auto str = res.first.getPayload();
memcpy(&sc, &str[0], sizeof(sc));
int sigid = sc.src & 31;
// cerr<<"C/NAV tow "<<sc.towMsec /1000<<" wn "<<sc.wn <<" sv " << (int) sc.sv - 70<<" sigid " << sigid <<" ";
if(!sc.crcPassed) {
cerr<<fmt::sprintf("E%02d C/NAV CRC error, skipping\n", sc.sv-70);
continue;
}
std::string cnav((char*)sc.navBits, 64);
// byte order adjustment
std::vector<uint8_t> payload;
for(unsigned int i = 0 ; i < 16; ++i) {
payload.push_back( sc.navBits[4 * i + 3]);
payload.push_back( sc.navBits[4 * i + 2]);
payload.push_back( sc.navBits[4 * i + 1]);
payload.push_back( sc.navBits[4 * i + 0]);
}
unsigned char crc_buff[58]={0};
unsigned int i;
for (i=0; i< 462;i++)
setbitu(crc_buff, 2+i, 1,getbitu(&payload[0],i, 1));
int calccrc=rtk_crc24q(crc_buff,58);
int realcrc= getbitu(&payload[0], 14+448, 24);
if (calccrc != realcrc) {
cerr << "CRC mismatch, " << calccrc << " != " << realcrc <<endl;
}
// else
// cerr<<"Checksum Correct: "<<calccrc << " = " << realcrc<<endl;
NavMonMessage nmm;
double t = utcFromGST(sc.wn - 1024, sc.towMsec / 1000.0);
// cerr<<t<< " " <<si.wn - 1024 <<" " <<si.towMsec /1000.0 <<" " << g_dtLS<<endl;
nmm.set_sourceid(g_srcid);
nmm.set_type(NavMonMessage::GalileoCnavType);
nmm.set_localutcseconds(t);
nmm.set_localutcnanoseconds(0); // yeah XXX
nmm.mutable_gc()->set_gnsswn(sc.wn - 1024);
nmm.mutable_gc()->set_gnsstow(sc.towMsec/1000.0);
nmm.mutable_gc()->set_gnssid(2);
nmm.mutable_gc()->set_gnsssv(sc.sv - 70);
nmm.mutable_gc()->set_contents((const char*)&payload[0], payload.size());
nmm.mutable_gc()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM( nmm);
}
else if(res.first.getIDBare() == 4027) {
auto str = res.first.getPayload();
struct MeasEpoch
{
uint32_t towMsec;
uint16_t wn;
uint8_t n1;
uint8_t sb1len;
uint8_t sb2len;
uint8_t commonFlags;
uint8_t clkJumps;
uint8_t res1;
} __attribute__((packed));
MeasEpoch me;
memcpy(&me, &str[0], sizeof(me));
// cerr<<"Got "<<(int)me.n1<<" signal statuses, block1 "<<(int)me.sb1len<<", block2 "<<(int)me.sb2len<<endl;
struct Block1
{
uint8_t rxchannel, type, sv, misc; // misc contains 4 bits of codeMSB
uint32_t codeLSB;
int32_t doppler;
uint16_t carrierLSB;
int8_t carrierMSB;
uint8_t cn0;
uint16_t lockTime;
uint8_t obsinfo;
uint8_t n2;
} __attribute__((packed));
struct Block2
{
uint8_t type, locktime, cn0, offsetMSB;
int8_t carrierMSB;
uint8_t obsinfo;
uint16_t codeoffsetLSB;
uint16_t carrierLSB;
uint16_t dopplerOffsetLSB;
} __attribute__((packed));
int pos = sizeof(me);
for(int n = 0 ; n < me.n1; ++n) {
Block1 b1;
memcpy(&b1, &str[0] + pos, sizeof(b1));
uint8_t sigid = b1.type & 31;
// cerr<<"sv "<<(int)b1.sv<<" sigid "<< (int)sigid <<" cn0 ";
double db;
if(sigid==1 || sigid ==2)
db = b1.cn0 *0.25;
else db = b1.cn0 * 0.25 + 10;
// cerr<<" "<<db;
// cerr<<" n2 "<< (int)b1.n2;
// cerr<<endl;
pos += me.sb1len;
if(b1.sv <= 36 || (b1.sv > 70 && b1.sv <= 106)) {
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(me.wn - 1024, me.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ReceptionDataType);
nmm.mutable_rd()->set_gnssid(b1.sv > 70 ? 2 : 0 );
nmm.mutable_rd()->set_gnsssv(b1.sv <= 37 ? b1.sv : b1.sv - 70);
nmm.mutable_rd()->set_db(db);
nmm.mutable_rd()->set_el(0);
nmm.mutable_rd()->set_azi(0);
nmm.mutable_rd()->set_prres(-1);
nmm.mutable_rd()->set_qi(7);
try {
nmm.mutable_rd()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM(nmm);
}
catch(...){}
/*
LSB of the pseudorange. The pseudorange expressed in meters
is computed as follows:
PR type1 [m] = ( CodeMSB *4294967296+ CodeLSB )*0.001
codeMSB hides in bits 0-3 of misc.
*/
}
for(int m = 0 ; m < b1.n2; ++m) {
Block2 b2;
memcpy(&b2, &str[0] + pos, sizeof(b2));
pos += me.sb2len;
sigid = b2.type & 31;
// cerr<<"\t sigid "<<(int)sigid<<" cn0 ";
if(sigid==1 || sigid ==2)
db= b2.cn0 *0.25;
else db = b2.cn0 * 0.25 + 10;
// cerr<<db;
// cerr<<endl;
if(b1.sv <= 36 || (b1.sv > 70 && b1.sv <= 106)) {
NavMonMessage nmm;
nmm.set_sourceid(g_srcid);
nmm.set_localutcseconds(utcFromGST(me.wn - 1024, me.towMsec / 1000.0));
nmm.set_localutcnanoseconds(0);
nmm.set_type(NavMonMessage::ReceptionDataType);
nmm.mutable_rd()->set_gnssid(b1.sv > 70 ? 2 : 0 );
nmm.mutable_rd()->set_gnsssv(b1.sv <= 37 ? b1.sv : b1.sv - 70);
nmm.mutable_rd()->set_db(db);
nmm.mutable_rd()->set_el(0);
nmm.mutable_rd()->set_azi(0);
nmm.mutable_rd()->set_prres(0);
nmm.mutable_rd()->set_qi(7);
try {
nmm.mutable_rd()->set_sigid(sepsig2ubx(sigid));
ns.emitNMM(nmm);
}catch(...){} // might be unknown signal type
}
}
}
}
else {
if(!quiet)
cerr<<"Unknown message "<<res.first.getID() << " / " <<res.first.getIDBare()<<" ("<<res.first.d_store.size()<<" bytes)"<<endl;
}
}
}
catch(std::exception& e)
{
cerr<<"Fatal: "<<e.what()<<endl;
}
catch(EofException& e)
{
}
/*
NAVBits contains the 234 bits of an I/NAV navigation page (in nominal
or alert mode). Note that the I/NAV page is transmitted as two sub-pages
(the so-called even and odd pages) of duration 1 second each (120 bits
each).
In this block, the even and odd pages are concatenated, even page
first and odd page last. The 6 tails bits at the end of the even page are
removed (hence a total of 234 bits). If the even and odd pages have been
received from two different carriers (E5b and L1), bit 5 of the Source
field is set.
*/

4
sp3.hh
View file

@ -8,8 +8,8 @@ struct SP3Entry
int gnss;
int sv;
time_t t;
double x, y, z; // meters
double clockBias; // nanoseconds
double x, y, z;
double clockBias;
};
class SP3Reader

View file

@ -18,12 +18,11 @@ int main(int argc, char **argv)
{
string influxDBName("galileo2");
bool doVERSION=false;
int sigid=1;
CLI::App app(program);
vector<string> fnames;
string sp3src("default");
app.add_flag("--version", doVERSION, "show program version and copyright");
app.add_option("--sp3src,-s", sp3src, "Identifier of SP3 source");
app.add_option("--sigid,-s", sigid, "Signal identifier. 1 or 5 for Galileo.");
app.add_option("--influxdb", influxDBName, "Name of influxdb database");
app.add_option("files", fnames, "filenames to parse");
try {
@ -41,10 +40,14 @@ int main(int argc, char **argv)
for(const auto& fn : fnames) {
SP3Reader sp3(fn);
SP3Entry e;
SatID sid;
cout<<fn<<endl;
while(sp3.get(e)) {
sid.gnss = e.gnss;
sid.sigid = sigid;
sid.sv = e.sv;
// XXX LEAP SECOND ADJUSTMENT FIXED AT 18 SECONDS
idb.addValue({{"gnssid", e.gnss}, {"sv", e.sv}, {"sp3src", sp3src}}, "sp3", {{"x", e.x}, {"y", e.y}, {"z", e.z}, {"clock-bias", e.clockBias}}, e.t + 18);
idb.addValue(sid, "sp3", {{"x", e.x}, {"y", e.y}, {"z", e.z}, {"clock-bias", e.clockBias}}, e.t + 18);
}
}
}

Some files were not shown because too many files have changed in this diff Show more