mirror of
https://github.com/berthubert/galmon.git
synced 2026-05-15 13:46:55 -04:00
Compare commits
No commits in common. "master" and "sarsend" have entirely different histories.
109 changed files with 14622 additions and 15175 deletions
12
.github/workflows/ccpp.yml
vendored
12
.github/workflows/ccpp.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
32
.github/workflows/docker.yml
vendored
32
.github/workflows/docker.yml
vendored
|
|
@ -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 \
|
||||
.
|
||||
47
Dockerfile
47
Dockerfile
|
|
@ -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
18
Dockerfile-pi
Normal 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}
|
||||
87
Makefile
87
Makefile
|
|
@ -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
182
README.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
23
beidou.hh
23
beidou.hh
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 §ion, 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 ¤tSection) {
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
115
ext/CLI11/CLI/Ini.hpp
Normal 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
|
||||
|
|
@ -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
|
|
@ -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 ¤t, 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 ¤t, 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 ¤t, 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 ¤t, 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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]
|
||||
2931
ext/doctest.h
2931
ext/doctest.h
File diff suppressed because it is too large
Load diff
27
ext/fmt-6.1.2/LICENSE.rst
Normal file
27
ext/fmt-6.1.2/LICENSE.rst
Normal 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
501
ext/fmt-6.1.2/README.rst
Normal 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.
|
||||
1106
ext/fmt-6.1.2/include/fmt/chrono.h
Normal file
1106
ext/fmt-6.1.2/include/fmt/chrono.h
Normal file
File diff suppressed because it is too large
Load diff
570
ext/fmt-6.1.2/include/fmt/color.h
Normal file
570
ext/fmt-6.1.2/include/fmt/color.h
Normal 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_
|
||||
585
ext/fmt-6.1.2/include/fmt/compile.h
Normal file
585
ext/fmt-6.1.2/include/fmt/compile.h
Normal 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_
|
||||
1519
ext/fmt-6.1.2/include/fmt/core.h
Normal file
1519
ext/fmt-6.1.2/include/fmt/core.h
Normal file
File diff suppressed because it is too large
Load diff
1396
ext/fmt-6.1.2/include/fmt/format-inl.h
Normal file
1396
ext/fmt-6.1.2/include/fmt/format-inl.h
Normal file
File diff suppressed because it is too large
Load diff
3541
ext/fmt-6.1.2/include/fmt/format.h
Normal file
3541
ext/fmt-6.1.2/include/fmt/format.h
Normal file
File diff suppressed because it is too large
Load diff
77
ext/fmt-6.1.2/include/fmt/locale.h
Normal file
77
ext/fmt-6.1.2/include/fmt/locale.h
Normal 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_
|
||||
141
ext/fmt-6.1.2/include/fmt/ostream.h
Normal file
141
ext/fmt-6.1.2/include/fmt/ostream.h
Normal 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_
|
||||
321
ext/fmt-6.1.2/include/fmt/posix.h
Normal file
321
ext/fmt-6.1.2/include/fmt/posix.h
Normal 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_
|
||||
711
ext/fmt-6.1.2/include/fmt/printf.h
Normal file
711
ext/fmt-6.1.2/include/fmt/printf.h
Normal 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_
|
||||
365
ext/fmt-6.1.2/include/fmt/ranges.h
Normal file
365
ext/fmt-6.1.2/include/fmt/ranges.h
Normal 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
176
ext/fmt-6.1.2/src/format.cc
Normal 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
237
ext/fmt-6.1.2/src/posix.cc
Normal 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
|
||||
218
fixhunter.cc
218
fixhunter.cc
|
|
@ -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;
|
||||
}
|
||||
15
fixhunter.hh
15
fixhunter.hh
|
|
@ -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;
|
||||
};
|
||||
138
galileo.cc
138
galileo.cc
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
170
galileo.hh
170
galileo.hh
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
51
galmonmon.cc
51
galmonmon.cc
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
13
glonass.cc
13
glonass.cc
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
21
glonass.hh
21
glonass.hh
|
|
@ -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);
|
||||
|
|
|
|||
81
gndate.cc
81
gndate.cc
|
|
@ -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
14
gps.cc
|
|
@ -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
7
gps.hh
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 += " <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)
|
||||
|
|
|
|||
BIN
html/favicon.ico
BIN
html/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 198 B |
|
|
@ -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>
|
||||
<input type="checkbox" id="GalE5a" onclick="updateSats();"> <label for="GalE5a">Galileo E5a</label>
|
||||
<input type="checkbox" id="GalE5b" onclick="updateSats();"> <label for="GalE5b">Galileo E5b</label>
|
||||
<input type="checkbox" id="BeiDouB1I" onclick="updateSats();"> <label for="BeiDouB1I">BeiDou B1I</label>
|
||||
<input type="checkbox" id="BeiDouB2I" onclick="updateSats();"> <label for="BeiDouB2I">BeiDou B2I</label>
|
||||
|
|
@ -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 & 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>last‑seen‑s</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&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>
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
276
html/sbas.js
276
html/sbas.js
|
|
@ -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 + " ";
|
||||
if(img != "")
|
||||
ret.value += '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
|
||||
else
|
||||
ret.value += "";
|
||||
// ret.value="";
|
||||
|
||||
ret.value += " <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();
|
||||
|
||||
|
||||
|
|
@ -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>.
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
|
|||
32
influxdb.md
32
influxdb.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
76
minicurl.cc
76
minicurl.cc
|
|
@ -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;
|
||||
|
|
|
|||
13
minicurl.hh
13
minicurl.hh
|
|
@ -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);
|
||||
|
|
|
|||
13
minivec.hh
13
minivec.hh
|
|
@ -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
173
navcat.cc
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) );
|
||||
|
|
|
|||
379
navdump.cc
379
navdump.cc
|
|
@ -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;
|
||||
|
|
|
|||
286
navmerge.cc
286
navmerge.cc
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
navmon.cc
97
navmon.cc
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
16
navmon.hh
16
navmon.hh
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
57
navmon.proto
57
navmon.proto
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
20
navnexus.cc
20
navnexus.cc
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
969
navparse.cc
969
navparse.cc
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
|
|
|
|||
127
navrecv.cc
127
navrecv.cc
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
317
nmmsender.cc
317
nmmsender.cc
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
32
nmmsender.hh
32
nmmsender.hh
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
543
reporter.cc
543
reporter.cc
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
75
rinex.cc
75
rinex.cc
|
|
@ -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)
|
||||
|
|
|
|||
23
rinex.hh
23
rinex.hh
|
|
@ -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
|
||||
|
||||
|
|
|
|||
52
rinjoin.cc
52
rinjoin.cc
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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
153
rs.cc
|
|
@ -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
44
rs.hh
|
|
@ -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
228
rtcm.cc
|
|
@ -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 1⁄2 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
15
rtcm.hh
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
|||
10
rtcmtool.cc
10
rtcmtool.cc
|
|
@ -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
20
sbas.cc
|
|
@ -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
18
sbas.hh
|
|
@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
747
septool.cc
747
septool.cc
|
|
@ -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
|
||||
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.
|
||||
Encoding: NAVBits contains all the bits of the frame, with the ex-
|
||||
ception of the synchronization field. The first 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
4
sp3.hh
|
|
@ -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
|
||||
|
|
|
|||
11
sp3feed.cc
11
sp3feed.cc
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue