├── debian ├── compat ├── watch ├── source │ ├── format │ └── local-options ├── patches │ └── series ├── galmon.docs ├── changelog ├── galmon.galmon-upgrade.timer ├── galmon.substvars ├── README.Debian ├── galmon.galmon-upgrade.service ├── galmon.navstar.default ├── galmon.ubxtool@.service ├── galmon.navrecv.service ├── galmon.navnexus.service ├── profile-debuild.sh ├── rules ├── control ├── galmon.postinst ├── galmon.default ├── copyright └── 86E7F51C04FBAAB0.asc ├── githash.cc ├── html ├── ext │ ├── bei.png │ ├── gal.png │ ├── glo.png │ └── gps.png ├── favicon.ico ├── almanac.html ├── sv.html ├── overview.html ├── observers.html ├── sbas.html ├── sbstatus.html ├── observer.html ├── style.css ├── geo │ ├── coverage.html │ └── index.html ├── overview.js ├── almanac.js ├── observers.js └── sv.js ├── rinex └── PTGG00PHL_R_20193500000_01D_MN.rnx.gz ├── update-tles ├── .gitmodules ├── update-git-hash-if-necessary ├── bits.hh ├── fixhunter.hh ├── ubxtool.service ├── ext └── CLI11 │ └── CLI │ ├── Version.hpp │ ├── CLI.hpp │ ├── Macros.hpp │ ├── ConfigFwd.hpp │ ├── Timer.hpp │ └── Split.hpp ├── sp3.hh ├── version.hh ├── storage.hh ├── .github └── workflows │ ├── ccpp.yml │ └── docker.yml ├── .gitignore ├── gpscnav.cc ├── Building.md ├── tle.hh ├── minivec.hh ├── rtcm.hh ├── Dockerfile ├── rs.hh ├── influxpush.hh ├── sp3feed.cc ├── ubx.hh ├── ubxtool.sh ├── rinjoin.cc ├── tlecatch.cc ├── testrunner.cc ├── zstdwrap.hh ├── osen.cc ├── ephemeris.cc ├── gndate.cc ├── sbas.hh ├── navmon.hh ├── nmmsender.hh ├── minicurl.hh ├── Operator.md ├── sp3.cc ├── navparse.hh ├── minread.cc ├── architecture.md ├── rinex.hh ├── tle.cc ├── beidou.cc ├── bits.cc ├── galileo.cc ├── glonass.cc ├── trkmeas.cc ├── storage.cc ├── zstdwrap.cc ├── rinreport.cc ├── PACKAGE-DEBIAN.md ├── coverage.cc ├── rs.cc ├── influxpush.cc ├── rtcmtool.cc ├── influxdb.md ├── gps.cc └── navdisplay.cc /debian/compat: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /githash.cc: -------------------------------------------------------------------------------- 1 | #include "githash.h" 2 | 3 | const char* g_gitHash=GIT_HASH; 4 | -------------------------------------------------------------------------------- /debian/source/local-options: -------------------------------------------------------------------------------- 1 | #abort-on-upstream-changes 2 | #unapply-patches 3 | -------------------------------------------------------------------------------- /html/ext/bei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/html/ext/bei.png -------------------------------------------------------------------------------- /html/ext/gal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/html/ext/gal.png -------------------------------------------------------------------------------- /html/ext/glo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/html/ext/glo.png -------------------------------------------------------------------------------- /html/ext/gps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/html/ext/gps.png -------------------------------------------------------------------------------- /html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/html/favicon.ico -------------------------------------------------------------------------------- /debian/patches/series: -------------------------------------------------------------------------------- 1 | # You must remove unused comment lines for the released package. 2 | -------------------------------------------------------------------------------- /debian/galmon.docs: -------------------------------------------------------------------------------- 1 | Building.md 2 | influxdb.md 3 | Operator.md 4 | PACKAGE-DEBIAN.md 5 | README.md 6 | 7 | -------------------------------------------------------------------------------- /rinex/PTGG00PHL_R_20193500000_01D_MN.rnx.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berthubert/galmon/HEAD/rinex/PTGG00PHL_R_20193500000_01D_MN.rnx.gz -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | galmon (0.20191231-1) stable; urgency=low 2 | 3 | * Initial release. 4 | 5 | -- Patrick Tudor Tue, 31 Dec 2019 00:00:00 +0000 6 | -------------------------------------------------------------------------------- /update-tles: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | for a in galileo gps-ops beidou glo-ops active 3 | do 4 | wget -N --progress=dot:binary --backups=1 https://www.celestrak.com/NORAD/elements/$a.txt 5 | done 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/powerblog"] 2 | path = ext/powerblog 3 | url = https://github.com/ahupowerdns/powerblog.git 4 | [submodule "ext/sgp4"] 5 | path = ext/sgp4 6 | url = https://github.com/dnwrnr/sgp4.git 7 | ignore = untracked 8 | -------------------------------------------------------------------------------- /debian/galmon.galmon-upgrade.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Update galmon 3 | Documentation=https://github.com/ahupowerdns/galmon 4 | 5 | [Timer] 6 | OnBootSec=3min 7 | OnUnitActiveSec=3d 8 | 9 | [Install] 10 | WantedBy=timers.target 11 | -------------------------------------------------------------------------------- /debian/galmon.substvars: -------------------------------------------------------------------------------- 1 | shlibs:Depends=libc6 (>= 2.28), libcurl4 (>= 7.16.2), libgcc1 (>= 1:3.5), libh2o-evloop0.13, libncurses6 (>= 6), libprotobuf17, libssl1.1 (>= 1.1.0), libstdc++6 (>= 6), libtinfo6 (>= 6), zlib1g (>= 1:1.1.4) 2 | misc:Depends= 3 | misc:Pre-Depends= 4 | -------------------------------------------------------------------------------- /debian/README.Debian: -------------------------------------------------------------------------------- 1 | galmon for Debian 2 | 3 | This directory contains files created by debmake and 4 | required by debuild to create Debian packages. 5 | 6 | For further instruction, review the Guide for Debian Maintainers 7 | by Osamu Aoki: https:/www.debian.org/doc/manuals/debmake-doc/ 8 | 9 | -- Patrick Tudor Sun, 19 Jan 2020 22:52:16 +0000 10 | -------------------------------------------------------------------------------- /update-git-hash-if-necessary: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | HASH=$(git describe --always --dirty=+ | tr -d '\n') 3 | 4 | echo \#define GIT_HASH \"$HASH\" > githash.h.tmp 5 | echo $HASH > githash 6 | 7 | cmp -s githash.h githash.h.tmp > /dev/null 8 | 9 | if [ "$?" -ne "0" ] 10 | then 11 | mv githash.h.tmp githash.h 12 | echo updated githash.h 13 | else 14 | rm githash.h.tmp 15 | fi 16 | 17 | 18 | -------------------------------------------------------------------------------- /bits.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* lovingly lifted from RTK */ 3 | 4 | unsigned int getbitu(const unsigned char *buff, int pos, int len); 5 | int getbits(const unsigned char *buff, int pos, int len); 6 | int getbitsglonass(const unsigned char *buff, int pos, int len); 7 | void setbitu(unsigned char *buff, int pos, int len, unsigned int data); 8 | unsigned int rtk_crc24q(const unsigned char *buff, int len); 9 | -------------------------------------------------------------------------------- /fixhunter.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "galileo.hh" 4 | 5 | class FixHunter 6 | { 7 | public: 8 | void reportInav(const std::basic_string& inav, int32_t gst); 9 | private: 10 | void tryFix(int32_t gst); 11 | struct GalileoMessage fillGMFromRS(const std::string& out); 12 | std::basic_string inav1, inav2, inav3, inav4, inav16, inav17, inav18, inav19, inav20; 13 | int d_latestiod; 14 | uint32_t d_inav16t0r; 15 | }; 16 | -------------------------------------------------------------------------------- /debian/galmon.galmon-upgrade.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Upgrade Galmon Software 3 | After=network.target nss-lookup.target 4 | StartLimitIntervalSec=0 5 | # require that the configuration exists 6 | ConditionPathExists=/etc/default/galmon 7 | 8 | [Service] 9 | Environment=DEBIAN_FRONTEND=noninteractive 10 | Type=simple 11 | Restart=no 12 | ExecStartPre=+apt-get update 13 | ExecStart=+apt-get install -y galmon 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /ubxtool.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=galmon ubxtool 3 | After=network.target 4 | StartLimitIntervalSec=0 5 | 6 | [Service] 7 | Type=simple 8 | Restart=always 9 | RestartSec=1 10 | # uncomment User and Group to not run as root 11 | #User=ubxtool 12 | #Group=ubxtool 13 | RuntimeDirectory=ubxtool 14 | RuntimeDirectoryPreserve=yes 15 | WorkingDirectory=/run/ubxtool 16 | ExecStart=/usr/local/ubxtool/ubxtool.sh 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | 21 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/Version.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | // [CLI11:verbatim] 10 | 11 | #define CLI11_VERSION_MAJOR 1 12 | #define CLI11_VERSION_MINOR 9 13 | #define CLI11_VERSION_PATCH 1 14 | #define CLI11_VERSION "1.9.1" 15 | 16 | // [CLI11:verbatim] 17 | -------------------------------------------------------------------------------- /sp3.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | struct SP3Entry 7 | { 8 | int gnss; 9 | int sv; 10 | time_t t; 11 | double x, y, z; // meters 12 | double clockBias; // nanoseconds 13 | }; 14 | 15 | class SP3Reader 16 | { 17 | public: 18 | SP3Reader(std::string_view fname); 19 | bool get(SP3Entry& sp3); 20 | ~SP3Reader(); 21 | private: 22 | std::string fname; 23 | FILE* d_fp{0}; 24 | time_t d_time{0}; 25 | }; 26 | -------------------------------------------------------------------------------- /version.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void showVersion(const char *pname, const char *hash) { 4 | std::cout <<"galmon tools (" < 3 | #include 4 | #include 5 | #include "navmon.pb.h" 6 | 7 | std::vector getPathComponents(std::string_view root, time_t s, uint64_t sourceid); 8 | std::string getPath(std::string_view root, time_t s, uint64_t sourceid, bool create=false); 9 | /* 10 | bool getNMM(int fd, NavMonMessage& nmm, uint32_t& offset); 11 | bool getNMM(FILE* fp, NavMonMessage& nmm, uint32_t& offset); 12 | */ 13 | bool getRawNMM(int fd, timespec& t, std::string& raw, uint32_t& offset); 14 | bool getRawNMM(FILE* fp, timespec& t, std::string& raw, uint32_t& offset); 15 | -------------------------------------------------------------------------------- /debian/galmon.navrecv.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=galmon navrecv (receive data on port 29603) 3 | After=network.target nss-lookup.target 4 | StartLimitIntervalSec=0 5 | # require that the pre-installed configuration file exists 6 | ConditionPathExists=/etc/default/navstar 7 | 8 | [Service] 9 | # Customize $DAEMON_OPTS_NAVRECV in /etc/default/navstar 10 | EnvironmentFile=-/etc/default/navstar 11 | Type=simple 12 | Restart=always 13 | RestartSec=1 14 | User=ubxtool 15 | Group=ubxtool 16 | RuntimeDirectory=navrecv 17 | ExecStart=/usr/bin/navrecv $DAEMON_OPTS_NAVRECV 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /debian/galmon.navnexus.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=galmon navnexus (serve recorded data on port 29601) 3 | After=network.target nss-lookup.target 4 | StartLimitIntervalSec=0 5 | # require that the pre-installed configuration file exists 6 | ConditionPathExists=/etc/default/navstar 7 | 8 | [Service] 9 | # Customize $DAEMON_OPTS_NAVNEXUS in /etc/default/navstar 10 | EnvironmentFile=-/etc/default/navstar 11 | Type=simple 12 | Restart=always 13 | RestartSec=4 14 | User=ubxtool 15 | Group=ubxtool 16 | RuntimeDirectory=navnexus 17 | ExecStart=/usr/bin/navnexus $DAEMON_OPTS_NAVNEXUS 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | submodules: "recursive" 12 | - name: deps 13 | run: | 14 | sudo apt-get update 15 | 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 16 | - name: config 17 | run: echo WSLAY=-lwslay > Makefile.local 18 | - name: make 19 | run: make 20 | - name: make check 21 | run: make check 22 | -------------------------------------------------------------------------------- /debian/profile-debuild.sh: -------------------------------------------------------------------------------- 1 | # This file of environment variables is sourced via /etc/profile.d/debuild.sh 2 | # 3 | # It sets the email and name of the maintainer first. 4 | # Next we disable parallel builds for ram conservation, normally enabled. 5 | # Finally we add hardening to the build. 6 | 7 | DEBEMAIL="debian@ptudor.net" 8 | DEBFULLNAME="Patrick Tudor" 9 | 10 | # "nocheck" here because testrunner runs out of ram on most current arm hardware -pht 11 | # manual: dh_auto_test: If the DEB_BUILD_OPTIONS environment variable contains nocheck, no tests will be performed. 12 | DEB_BUILD_OPTIONS='parallel=1 nocheck' 13 | 14 | # https://wiki.debian.org/Hardening 15 | DEB_BUILD_MAINT_OPTIONS='hardening=+all' 16 | 17 | export DEBEMAIL DEBFULLNAME DEB_BUILD_OPTIONS DEB_BUILD_MAINT_OPTIONS 18 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/CLI.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | // CLI Library includes 10 | // Order is important for combiner script 11 | 12 | #include "Version.hpp" 13 | 14 | #include "Macros.hpp" 15 | 16 | #include "StringTools.hpp" 17 | 18 | #include "Error.hpp" 19 | 20 | #include "TypeTools.hpp" 21 | 22 | #include "Split.hpp" 23 | 24 | #include "ConfigFwd.hpp" 25 | 26 | #include "Validators.hpp" 27 | 28 | #include "FormatterFwd.hpp" 29 | 30 | #include "Option.hpp" 31 | 32 | #include "App.hpp" 33 | 34 | #include "Config.hpp" 35 | 36 | #include "Formatter.hpp" 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # - created by protoc 2 | navmon.pb.cc 3 | navmon.pb.h 4 | # - created by Makefile 5 | navcat 6 | navdisplay 7 | navdump 8 | navnexus 9 | navrecv 10 | testrunner 11 | tlecatch 12 | ubxtool 13 | # - created by update-tles 14 | active.txt 15 | beidou.txt 16 | galileo.txt 17 | glo-ops.txt 18 | gps-ops.txt 19 | # - git version internals 20 | githash.h 21 | # 22 | *.csv 23 | # Prerequisites 24 | *.d 25 | *~ 26 | 27 | # Compiled Object files 28 | *.slo 29 | *.lo 30 | *.o 31 | *.obj 32 | 33 | # Precompiled Headers 34 | *.gch 35 | *.pch 36 | 37 | # Compiled Dynamic libraries 38 | *.so 39 | *.dylib 40 | *.dll 41 | 42 | # Fortran module files 43 | *.mod 44 | *.smod 45 | 46 | # Compiled Static libraries 47 | *.lai 48 | *.la 49 | *.a 50 | *.lib 51 | 52 | # Executables 53 | *.exe 54 | *.out 55 | *.app 56 | -------------------------------------------------------------------------------- /gpscnav.cc: -------------------------------------------------------------------------------- 1 | /* 2 | cerr<<"Preamble "< 3 | #include 4 | #include 5 | #include 6 | class SGP4; 7 | class Tle; 8 | 9 | class TLERepo 10 | { 11 | public: 12 | TLERepo(); 13 | ~TLERepo(); 14 | void parseFile(std::string_view fname); 15 | struct Match 16 | { 17 | std::string name; 18 | int norad; 19 | std::string internat; 20 | double inclination{360}; // radians 21 | double ran; // radians 22 | double e{-1}; 23 | double ecefX{0}; // m 24 | double ecefY{0}; // m 25 | double ecefZ{0}; // m 26 | 27 | double eciX{0}, eciY{0}, eciZ{0}; // m 28 | double distance{-1}; // m 29 | 30 | double latitude, longitude, altitude; 31 | }; 32 | 33 | Match getBestMatch(time_t, double x, double y, double z, Match* secondbest=0); 34 | 35 | 36 | private: 37 | std::map> 39 | > d_sgp4s; 40 | }; 41 | -------------------------------------------------------------------------------- /html/almanac.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu almanac 5 | 6 | 7 | 8 | 9 | Last update:
10 | Note: all distances are in kilometers! 11 |
12 |
13 |

14 | 15 |

16 |

17 | Information on the status of BeiDou can be found on this page from the Chinese Test and Assessment Research Center of China Satellite Navigation Office. 18 |

19 |

20 | Feedback is very welcome on bert@hubertnet.nl or @PowerDNS_Bert. 21 |

22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish Docker image 2 | 3 | on: 4 | push: 5 | branches: master 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Checkout submodules 14 | run: git submodule update --init --recursive 15 | - name: Set up docker buildx 16 | id: buildx 17 | uses: crazy-max/ghaction-docker-buildx@v3 18 | with: 19 | buildx-version: latest 20 | qemu-version: latest 21 | - name: Login to docker registry 22 | run: | 23 | docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_TOKEN }} 24 | - name: Run buildx 25 | run: | 26 | docker buildx build \ 27 | --tag berthubert/galmon \ 28 | --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 \ 29 | --output "type=registry" \ 30 | --build-arg MAKE_FLAGS=-j1 \ 31 | --file Dockerfile \ 32 | . 33 | -------------------------------------------------------------------------------- /minivec.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct Point 6 | { 7 | Point() : x{0}, y{0}, z{0} 8 | {} 9 | Point(double x_, double y_, double z_) : x(x_), y(y_), z(z_) 10 | {} 11 | double x, y, z; 12 | }; 13 | 14 | inline std::ostream& operator<<(std::ostream& os, const Point& p) 15 | { 16 | os << '(' << p.x << ", " << p.y << ", " << p.z <<')'; 17 | return os; 18 | } 19 | 20 | 21 | struct Vector 22 | { 23 | Vector() : x{0}, y{0}, z{0} {} 24 | Vector(double x_, double y_, double z_) : x(x_), y(y_), z(z_) 25 | {} 26 | Vector(const Point& a, const Point& b) : Vector(b.x - a.x, b.y - a.y, b.z - a.z) 27 | { 28 | } 29 | double x, y, z; 30 | 31 | double length() const 32 | { 33 | return sqrt(x*x + y*y + z*z); 34 | } 35 | 36 | void norm() 37 | { 38 | double l = length(); 39 | x/=l; 40 | y/=l; 41 | z/=l; 42 | } 43 | 44 | double inner(const Vector& b) const 45 | { 46 | return x*b.x + y*b.y + z*b.z; 47 | } 48 | }; 49 | 50 | inline std::ostream& operator<<(std::ostream& os, const Vector& p) 51 | { 52 | os << '(' << p.x << ", " << p.y << ", " << p.z <<')'; 53 | return os; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: galmon 2 | Section: net 3 | Priority: optional 4 | Maintainer: Patrick Tudor 5 | Build-Depends: debhelper (>=11~), help2man, libzstd-dev, adduser 6 | Standards-Version: 4.3.0 7 | Homepage: https://github.com/ahupowerdns/galmon/ 8 | 9 | Package: galmon 10 | Architecture: any 11 | Multi-Arch: foreign 12 | Conflicts: gpsd-clients 13 | Depends: ${misc:Depends}, ${shlibs:Depends} 14 | Description: galmon GNSS Monitoring Project software 15 | 88 16 | 88 17 | 88 18 | ,adPPYb,d8 ,adPPYYba, 88 88,dPYba,,adPYba, ,adPPYba, 8b,dPPYba, 19 | a8" `Y88 "" `Y8 88 88P' "88" "8a a8" "8a 88P' `"8a 20 | 8b 88 ,adPPPPP88 88 88 88 88 8b d8 88 88 21 | "8a, ,d88 88, ,88 88 88 88 88 "8a, ,a8" 88 88 22 | `"YbbdP"Y8 `"8bbdP"Y8 88 88 88 88 `"YbbdP"' 88 88 23 | aa, ,88 24 | "Y8bbdP" 25 | -------------------------------------------------------------------------------- /rtcm.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "navmon.hh" 5 | #include 6 | #include "galileo.hh" 7 | #include 8 | 9 | struct RTCMFrame 10 | { 11 | std::string payload; 12 | }; 13 | 14 | class RTCMReader 15 | { 16 | public: 17 | explicit RTCMReader(int fd) : d_fp(fdopen(fd, "r")) {} 18 | bool get(RTCMFrame& rf); 19 | private: 20 | FILE* d_fp; 21 | }; 22 | 23 | 24 | struct RTCMMessage 25 | { 26 | void parse(const std::string& str); 27 | int type; 28 | int sow; 29 | int udi; 30 | bool mmi; 31 | bool reference; 32 | int ssrIOD, ssrProvider, ssrSolution; 33 | struct EphemerisDelta 34 | { 35 | SatID id; 36 | // in millimeters 37 | double radial, along, cross; // mm 38 | double dradial, dalong, dcross; // mm/s 39 | int iod; 40 | int sow; 41 | int udi; 42 | }; 43 | struct ClockDelta 44 | { 45 | SatID id; 46 | double dclock0; // in meters 47 | double dclock1; 48 | double dclock2; 49 | int sow; 50 | int udi; 51 | int iod{-1}; 52 | }; 53 | 54 | std::vector d_ephs; 55 | std::vector d_clocks; 56 | std::map d_dcbs; 57 | GalileoMessage d_gm; 58 | int d_sv; 59 | }; 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # First stage - builder 3 | # 4 | FROM debian:10-slim AS builder 5 | 6 | ENV DEBIAN_FRONTEND noninteractive 7 | ENV LC_ALL C.UTF-8 8 | 9 | # This allows you to use a local Debian mirror 10 | ARG APT_URL=http://deb.debian.org/debian/ 11 | ARG MAKE_FLAGS=-j2 12 | 13 | RUN sed -i "s%http://deb.debian.org/debian/%${APT_URL}%" /etc/apt/sources.list \ 14 | && apt-get update && apt-get -y upgrade \ 15 | && apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \ 16 | libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev \ 17 | libeigen3-dev libzstd-dev libfmt-dev libncurses-dev \ 18 | make gcc g++ git build-essential curl autoconf automake help2man 19 | 20 | # Build 21 | ADD . /galmon-src/ 22 | RUN cd /galmon-src/ \ 23 | && make $MAKE_FLAGS \ 24 | && prefix=/galmon make install 25 | 26 | # 27 | # Second stage - contains just the binaries 28 | # 29 | FROM debian:10-slim 30 | RUN apt-get update && apt-get -y upgrade \ 31 | && apt-get install -y libcurl4 libssl1.1 libprotobuf17 libh2o-evloop0.13 \ 32 | libncurses6 \ 33 | && apt-get -y clean \ 34 | && rm -rf /var/lib/apt/lists/* 35 | COPY --from=builder /galmon/ /galmon/ 36 | ENV PATH=/galmon/bin:${PATH} 37 | ENV LC_ALL C.UTF-8 38 | WORKDIR /galmon/bin 39 | -------------------------------------------------------------------------------- /rs.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class RSCodec 6 | { 7 | public: 8 | RSCodec(const std::vector& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad=0, unsigned int bits=8); 9 | void encode(std::string& msg); 10 | 11 | int decode(const std::string& in, std::string& out, std::vector* corrections=0); 12 | int getPoly() // the representation as a number 13 | { 14 | return d_gfpoly; 15 | } 16 | ~RSCodec(); 17 | private: 18 | void* d_rs{0}; 19 | unsigned int d_gfpoly{0}; 20 | public: 21 | const unsigned int d_N, d_K, d_nroots, d_bits; 22 | 23 | }; 24 | 25 | 26 | class RSCodecInt 27 | { 28 | public: 29 | RSCodecInt(const std::vector& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad=0, unsigned int bits=8); 30 | void encode(std::vector& msg); 31 | 32 | int decode(const std::vector& in, std::vector& out, std::vector* corrections=0); 33 | int getPoly() // the representation as a number 34 | { 35 | return d_gfpoly; 36 | } 37 | ~RSCodecInt(); 38 | private: 39 | void* d_rs{0}; 40 | unsigned int d_gfpoly{0}; 41 | public: 42 | const unsigned int d_N, d_K, d_nroots, d_bits; 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /influxpush.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "navparse.hh" 7 | #include 8 | 9 | struct InfluxPusher 10 | { 11 | typedef std::variant var_t; 12 | explicit InfluxPusher(std::string_view dbname); 13 | void addValueObserver(int src, std::string_view name, const std::initializer_list>& values, double t, std::optional satid=std::optional()); 14 | void addValue(const SatID& id, std::string_view name, const std::initializer_list>& values, double t, std::optional src = std::optional(), std::optional tag = std::optional("src")); 15 | 16 | 17 | void addValue(const vector>& tags, string_view name, const initializer_list>& values, double t); 18 | void checkSend(); 19 | void doSend(const std::set& buffer); 20 | ~InfluxPusher(); 21 | std::set d_buffer; 22 | void queueValue(const std::string& line); 23 | 24 | time_t d_lastsent{0}; 25 | string d_dbname; 26 | bool d_mute{false}; 27 | int64_t d_nummsmts{0}; 28 | int64_t d_numvalues{0}; 29 | int64_t d_numdedupmsmts{0}; 30 | map d_msmtmap; 31 | }; 32 | -------------------------------------------------------------------------------- /html/sv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu sv 5 | 6 | 7 | 8 | 9 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live map here!. Contact me if you want access to the Grafana dashboard.
10 |
11 |

12 | This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States. 13 | It is very much a work in progress, and will not be available at all times. Extremely rough code is on 14 | GitHub. 15 | 16 | Some technical detail behind this setup can be found in this post. 17 | 18 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 19 | web gateway. 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/Macros.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | // [CLI11:verbatim] 10 | 11 | // The following version macro is very similar to the one in PyBind11 12 | #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) 13 | #if __cplusplus >= 201402L 14 | #define CLI11_CPP14 15 | #if __cplusplus >= 201703L 16 | #define CLI11_CPP17 17 | #if __cplusplus > 201703L 18 | #define CLI11_CPP20 19 | #endif 20 | #endif 21 | #endif 22 | #elif defined(_MSC_VER) && __cplusplus == 199711L 23 | // MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) 24 | // Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer 25 | #if _MSVC_LANG >= 201402L 26 | #define CLI11_CPP14 27 | #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 28 | #define CLI11_CPP17 29 | #if __MSVC_LANG > 201703L && _MSC_VER >= 1910 30 | #define CLI11_CPP20 31 | #endif 32 | #endif 33 | #endif 34 | #endif 35 | 36 | #if defined(CLI11_CPP14) 37 | #define CLI11_DEPRECATED(reason) [[deprecated(reason)]] 38 | #elif defined(_MSC_VER) 39 | #define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) 40 | #else 41 | #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) 42 | #endif 43 | 44 | // [CLI11:verbatim] 45 | -------------------------------------------------------------------------------- /html/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu overview 5 | 6 | 7 | 8 | 9 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live map here!. Contact me if you want access to the Grafana dashboard.
10 |
11 |

12 | This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States. 13 | It is very much a work in progress, and will not be available at all times. Extremely rough code is on 14 | GitHub. 15 | 16 | Some technical detail behind this setup can be found in this post. 17 | 18 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 19 | web gateway. 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /html/observers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu observers 5 | 6 | 7 | 8 | 9 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live map here!. Contact me if you want access to the Grafana dashboard.
10 |
11 |

12 | This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States. 13 | It is very much a work in progress, and will not be available at all times. Extremely rough code is on 14 | GitHub. 15 | 16 | Some technical detail behind this setup can be found in this post. 17 | 18 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 19 | web gateway. 20 |

21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sp3feed.cc: -------------------------------------------------------------------------------- 1 | #include "sp3.hh" 2 | #include "influxpush.hh" 3 | #include 4 | #include "navmon.hh" 5 | #include "fmt/format.h" 6 | #include "fmt/printf.h" 7 | 8 | #include "CLI/CLI.hpp" 9 | #include "version.hh" 10 | 11 | static char program[]="sp3feed"; 12 | 13 | using namespace std; 14 | 15 | extern const char* g_gitHash; 16 | 17 | int main(int argc, char **argv) 18 | { 19 | string influxDBName("galileo2"); 20 | bool doVERSION=false; 21 | 22 | CLI::App app(program); 23 | vector fnames; 24 | string sp3src("default"); 25 | app.add_flag("--version", doVERSION, "show program version and copyright"); 26 | app.add_option("--sp3src,-s", sp3src, "Identifier of SP3 source"); 27 | app.add_option("--influxdb", influxDBName, "Name of influxdb database"); 28 | app.add_option("files", fnames, "filenames to parse"); 29 | try { 30 | app.parse(argc, argv); 31 | } catch(const CLI::Error &e) { 32 | return app.exit(e); 33 | } 34 | 35 | if(doVERSION) { 36 | showVersion(program, g_gitHash); 37 | exit(0); 38 | } 39 | 40 | InfluxPusher idb(influxDBName); 41 | for(const auto& fn : fnames) { 42 | SP3Reader sp3(fn); 43 | SP3Entry e; 44 | cout< 3 | #include 4 | #include 5 | 6 | uint16_t calcUbxChecksum(uint8_t ubxClass, uint8_t ubxType, std::basic_string_view str); 7 | std::basic_string buildUbxMessage(uint8_t ubxClass, uint8_t ubxType, std::basic_string_view str); 8 | 9 | std::basic_string buildUbxMessage(uint8_t ubxClass, uint8_t ubxType, const std::initializer_list& str); 10 | 11 | std::basic_string getInavFromSFRBXMsg(std::basic_string_view msg, 12 | std::basic_string& reserved1, 13 | std::basic_string& reserved2, 14 | std::basic_string& sar, 15 | std::basic_string& spare, 16 | std::basic_string& crc, uint8_t* ssp=0); 17 | 18 | std::basic_string getFnavFromSFRBXMsg(std::basic_string_view msg, 19 | std::basic_string& crc); 20 | 21 | std::basic_string getGPSFromSFRBXMsg(std::basic_string_view msg); 22 | std::basic_string getGlonassFromSFRBXMsg(std::basic_string_view msg); 23 | std::basic_string getBeidouFromSFRBXMsg(std::basic_string_view msg); 24 | std::basic_string getSBASFromSFRBXMsg(std::basic_string_view msg); 25 | struct CRCMismatch{}; 26 | 27 | struct TrkSatStat 28 | { 29 | int gnss; 30 | int sv; 31 | double dopplerHz; 32 | uint64_t tr; 33 | }; 34 | 35 | std::vector parseTrkMeas(std::basic_string_view payload); 36 | -------------------------------------------------------------------------------- /html/sbas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu sbas 5 | 6 | 7 | 8 | 9 |

10 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live observer map here, status (coverage, DOP) map here. Experimental Grafana dashboard on public.galmon.eu (user: guest, password: guest). SBAS status, per-satellite.
11 |

12 |

13 | Even more so than the other galmon.eu pages, data presented here is NOT meant to be used in any way for flight safety! 14 |

15 |
16 |
17 | 18 |
19 |

20 | 21 | Some technical detail behind this setup can be found in this post. 22 | 23 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 24 | web gateway. 25 |

26 |

27 | Even more so than the other galmon.eu pages, data presented here is NOT meant to be used in any way for flight safety! 28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /html/sbstatus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu sbas 5 | 6 | 7 | 8 | 9 |

10 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live observer map here, status (coverage, DOP) map here. Experimental Grafana dashboard on public.galmon.eu (user: guest, password: guest). SBAS status, per-satellite.
11 |

12 |

13 | Even more so than the other galmon.eu pages, data presented here is NOT meant to be used in any way for flight safety! 14 |

15 |
16 |
17 | 18 |
19 |

20 | 21 | Some technical detail behind this setup can be found in this post. 22 | 23 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 24 | web gateway. 25 |

26 |

27 | Even more so than the other galmon.eu pages, data presented here is NOT meant to be used in any way for flight safety! 28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ubxtool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | runDir="/run/ubxtool" 3 | 4 | CONSTELLATIONS="--galileo --gps --glonass" 5 | # CONSTELLATIONS="--galileo --gps --beidou" 6 | # CONSTELLATIONS="--galileo --gps --glonass --beidou" # only on the F9P 7 | 8 | if [ -e /usr/local/ubxtool/constellations ] 9 | then 10 | CONSTELLATIONS=$(cat /usr/local/ubxtool/constellations) 11 | fi 12 | 13 | # DEVICE="/dev/ttyACM0" # comment out or leave blank to auto-search 14 | 15 | ######################################################################### 16 | rotate() { 17 | logt=$1 18 | cd ${runDir} 19 | if [ -r ${logt}.log ]; # only rotate if there's a current logfile 20 | then 21 | if [ -r ${logt}.log.4 ]; then mv ${logt}.log.4 ${logt}.log.5 ; fi; 22 | if [ -r ${logt}.log.3 ]; then mv ${logt}.log.3 ${logt}.log.4 ; fi; 23 | if [ -r ${logt}.log.2 ]; then mv ${logt}.log.2 ${logt}.log.3 ; fi; 24 | if [ -r ${logt}.log.1 ]; then mv ${logt}.log.1 ${logt}.log.2 ; fi; 25 | if [ -r ${logt}.log ]; then mv ${logt}.log ${logt}.log.1 ; fi; 26 | fi 27 | } 28 | 29 | if [ -z "${DEVICE}" ]; 30 | then 31 | # programmatically find the interface 32 | SYSD=$(grep -il u-blox /sys/bus/usb/devices/*/manufacturer) 33 | SYSD=${SYSD//manufacturer/} 34 | DEVD=$(find ${SYSD} -type d -iname 'ttyACM*') 35 | DEVICE="/dev/${DEVD##*/}" 36 | fi 37 | 38 | DESTINATION=$(cat /usr/local/ubxtool/destination) 39 | STATION=$(cat /usr/local/ubxtool/station) 40 | 41 | 42 | # systemctl script will do this, but if you don't use systemctl, we need to take care of it 43 | [[ -d ${runDir} ]] || mkdir -p ${runDir} 44 | 45 | for logFile in stdout stderr logfile 46 | do 47 | rotate ${logFile} 48 | done 49 | 50 | 51 | exec /usr/local/ubxtool/ubxtool ${CONSTELLATIONS} --port ${DEVICE} --station ${STATION} --destination ${DESTINATION} >> ${runDir}/stdout.log 2>> ${runDir}/stderr.log < /dev/null 52 | -------------------------------------------------------------------------------- /debian/galmon.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ptudor 20200120 3 | set -e 4 | 5 | #. /usr/share/debconf/confmodule 6 | 7 | setup_user() { 8 | 9 | if getent group ubxtool > /dev/null ; then 10 | echo "galmon: ubxtool group exists, skipping" 11 | else 12 | echo "galmon: creating ubxtool system group" 13 | addgroup --system ubxtool 14 | fi 15 | 16 | if getent passwd ubxtool > /dev/null ; then 17 | echo "galmon: ubxtool user exists, skipping" 18 | else 19 | echo "galmon: creating ubxtool system user" 20 | adduser --system ubxtool --no-create-home --home /run/ubxtool 21 | echo "galmon: adding ubxtool user to ubxtool group" 22 | adduser ubxtool ubxtool 23 | echo "galmon: adding ubxtool user to dialout group" 24 | adduser ubxtool dialout 25 | fi 26 | } 27 | 28 | restart_ubxtool_daemon() { 29 | # I feel like this belongs in rules with dh_installsystemd but do not understand how to add the wildcard. 30 | if systemctl is-active 'ubxtool@*' > /dev/null ; then 31 | echo "galmon: restarting ubxtool." 32 | systemctl daemon-reload && systemctl restart 'ubxtool@*' 33 | else 34 | echo "galmon: ubxtool services are not currently enabled, not restarting." 35 | fi 36 | } 37 | 38 | print_help_text() { 39 | echo "Galmon installation finished. If this is your first time, please:" 40 | echo " 1) Create a ubxtool configuration 2) Enable the service" 41 | echo " 3) Enable the timer for automatic upgrades if you want" 42 | echo "Replace ttyACM0 below with your device listed in /dev" 43 | echo "Example: cp /etc/default/galmon /etc/default/ubxtool-ttyACM0" 44 | echo "Example: vi /etc/default/ubxtool-ttyACM0" 45 | echo "Example: systemctl enable --now ubxtool@ttyACM0" 46 | echo "Example: (beta testing) systemctl enable --now galmon-upgrade.timer" 47 | } 48 | 49 | setup_user 50 | print_help_text 51 | restart_ubxtool_daemon 52 | 53 | #DEBHELPER# 54 | 55 | -------------------------------------------------------------------------------- /html/observer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | galmon.eu observer 5 | 6 | 7 | 8 | 9 | Last update: . More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found here. Live map here!. Contact me if you want access to the Grafana dashboard.
10 | 11 | 12 | 15 | 22 | 23 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
24 | 25 |

26 | This table shows live output from a Galileo/GPS/BeiDou/GLONASS receiver hosted by a volunteer somewhere in the world, and connected to the Galmon network. 27 | It is very much a work in progress, and will not be available at all times. Extremely rough code is on 28 | GitHub. 29 | 30 | Some technical detail behind this setup can be found in this post. 31 | 32 | For updates, follow @GalileoSats on Twitter, or join us on our IRC channel (chat) via the 33 | web gateway. 34 |

35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /rinjoin.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "rinex.hh" 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | struct Value 8 | { 9 | optional af0Inav; 10 | optional af0Fnav; 11 | int af1; 12 | int iod; 13 | optional BGDE1E5a; 14 | optional BGDE1E5b; 15 | }; 16 | 17 | map, Value> satmap; 18 | 19 | int main(int argc, char** argv) 20 | { 21 | for(int n = 1; n < argc; ++n) { 22 | RINEXReader rr(argv[n]); 23 | RINEXEntry e; 24 | while(rr.get(e)) { 25 | if(e.gnss != 2) 26 | continue; 27 | // 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))) <<" "< 3 | #include 4 | 5 | #include "CLI/CLI.hpp" 6 | #include "version.hh" 7 | 8 | static char program[]="tlecatch"; 9 | 10 | using namespace std; 11 | 12 | extern const char* g_gitHash; 13 | 14 | int main(int argc, char **argv) 15 | { 16 | string line; 17 | // DateTime d(2019, 8, 31, 1, 52, 30); 18 | time_t now = time(0); 19 | struct tm tm; 20 | gmtime_r(&now, &tm); 21 | bool doVERSION{false}; 22 | 23 | CLI::App app(program); 24 | 25 | app.add_flag("--version", doVERSION, "show program version and copyright"); 26 | 27 | try { 28 | app.parse(argc, argv); 29 | } catch(const CLI::Error &e) { 30 | return app.exit(e); 31 | } 32 | 33 | if(doVERSION) { 34 | showVersion(program, g_gitHash); 35 | exit(0); 36 | } 37 | 38 | for(;;) { 39 | DateTime d(1900 + tm.tm_year, tm.tm_mon+1, tm.tm_mday, 04, 43, 51); 40 | string name, line1, line2; 41 | if(!getline(cin, name) || !getline(cin, line1) || !getline(cin, line2)) 42 | break; 43 | name.resize(name.size()-1); 44 | line1.resize(line1.size()-1); 45 | line2.resize(line2.size()-1); 46 | Tle tle(line1, line2); 47 | /* 48 | cout<<"line1: "< 3 | #include // apt-get install libzstd-dev if you miss this. 4 | // can't easily be moved to zstdwrap.cc, trust me 5 | #include 6 | #include 7 | #include 8 | 9 | // users submit (give()) data to this class 10 | // the class has an internal buffer to which compressed data gets written 11 | // If that buffer is full, we call emit() to empty it 12 | // the emit() function must make sure that everything in the buffer gets sent! 13 | 14 | class ZStdCompressor 15 | { 16 | public: 17 | explicit ZStdCompressor(const std::function& emit, int compressionLevel); 18 | ZStdCompressor(const ZStdCompressor& rhs) = delete; 19 | ~ZStdCompressor(); 20 | void give(const char* data, uint32_t bytes); 21 | 22 | static int maxCompressionLevel(); 23 | uint64_t d_inputBytes{0}, d_outputBytes{0}; 24 | uint32_t outputBufferBytes(); // Number of bytes in output buffer 25 | uint32_t outputBufferCapacity(); // output buffer capacity 26 | void flush(); 27 | 28 | private: 29 | void flushToEmit(); 30 | ZSTD_CCtx *d_z{nullptr}; 31 | ZSTD_outBuffer d_out; 32 | uint32_t d_outcapacity; 33 | std::function d_emit; 34 | 35 | }; 36 | 37 | /* this class is tremendously devious 38 | you pass it a filedescriptor from which it reads zstd compressed data 39 | You can then read the uncompressed data on the filedescriptor you 40 | get from getFD() 41 | */ 42 | 43 | class ZStdReader 44 | { 45 | public: 46 | ZStdReader(int fd); // we don't close this for you 47 | ZStdReader(const ZStdReader& rhs) = delete; 48 | ~ZStdReader(); 49 | int getFD() 50 | { 51 | return d_readpipe; 52 | } 53 | private: 54 | std::thread d_thread; 55 | int d_sourcefd; // this is where we read compressed data 56 | int d_writepipe; // which we then stuff into this pipe 57 | int d_readpipe; // and it comes out here for the client 58 | 59 | void worker(); 60 | }; 61 | -------------------------------------------------------------------------------- /debian/galmon.default: -------------------------------------------------------------------------------- 1 | # Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/ 2 | # https://galmon.eu - https://github.com/ahupowerdns/galmon 3 | # This package is free software: /usr/share/common-licenses/GPL-3 4 | # 5 | # INSTRUCTIONS: 6 | # (Pre-condition: "dmesg | tail" reports your GPS device appears on /dev/ttyACM0.) 7 | # Copy this file from /etc/default/galmon to /etc/default/ubxtool-ttyACM0 8 | # Please choose your constellations, update the owner and remark, and set your station and destination (ipv6 supported) 9 | # After customization, start the daemon: 10 | # systemctl enable --now ubxtool@ttyACM0 && journalctl -fu ubxtool 11 | # If you want, enable automatic upgrades at boot and every three days following: 12 | # systemctl enable --now galmon-upgrade.timer 13 | # If you have several devices, enable multiple services with their unique device names. 14 | # 15 | # FOR HELP: 16 | # Review the Operator.md file: https://github.com/ahupowerdns/galmon/blob/master/Operator.md 17 | # Download an IRC client like Textual (Mac) or HexChat (Windows). Join #galileo on the OFTC servers. 18 | # Thank you for contributing, it is nice to see data from your location on the map. 19 | # 20 | # 21 | # DO NOT SET THE "PORT" OPTION HERE. 22 | # The device is set by an argument to service, see above. 23 | # 24 | # uBlox M8-series and Aliexpress Specials support ( (gps AND galileo) AND ( beidou OR glonass) ) with optional SBAS. 25 | # 26 | DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --sbas --station 65000 --destination ::1" 27 | #DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --sbas --station 65000 --destination ::1" 28 | #DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --glonass --sbas --station 65000 --destination ::1" 29 | # 30 | # uBlox ZED F9T and F9P modules support all four major constellations simultaneously but not SBAS. 31 | # 32 | #DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --glonass --station 65000 --destination ::1" 33 | -------------------------------------------------------------------------------- /osen.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ephemeris.hh" 4 | 5 | /* gratefully adopted from http://danceswithcode.net/engineeringnotes/geodetic_to_ecef/geodetic_to_ecef.html 6 | which in turn is based on the excellent work from 7 | https://hal.archives-ouvertes.fr/hal-01704943/document 8 | */ 9 | 10 | // lat, lon, height (rad, rad, meters) 11 | std::tuple ecefToWGS84(double x, double y, double z) 12 | { 13 | constexpr double a = 6378137.0; //WGS-84 semi-major axis 14 | constexpr double e2 = 6.6943799901377997e-3; //WGS-84 first eccentricity squared 15 | constexpr double a1 = 4.2697672707157535e+4; //a1 = a*e2 16 | constexpr double a2 = 1.8230912546075455e+9; //a2 = a1*a1 17 | constexpr double a3 = 1.4291722289812413e+2; //a3 = a1*e2/2 18 | constexpr double a4 = 4.5577281365188637e+9; //a4 = 2.5*a2 19 | constexpr double a5 = 4.2840589930055659e+4; //a5 = a1+a3 20 | constexpr double a6 = 9.9330562000986220e-1; //a6 = 1-e2 21 | double zp,w2,w,r2,r,s2,c2,s,c,ss; 22 | double g,rg,rf,u,v,m,f,p; 23 | 24 | std::tuple geo; //Results go here (Lat, Lon, Altitude) 25 | zp = abs( z ); 26 | w2 = x*x + y*y; 27 | w = sqrt( w2 ); 28 | r2 = w2 + z*z; 29 | r = sqrt( r2 ); 30 | std::get<1>(geo) = atan2( y, x ); //Lon (final) 31 | s2 = z*z/r2; 32 | c2 = w2/r2; 33 | u = a2/r; 34 | v = a3 - a4/r; 35 | if( c2 > 0.3 ){ 36 | s = ( zp/r )*( 1.0 + c2*( a1 + u + s2*v )/r ); 37 | std::get<0>(geo) = asin( s ); //Lat 38 | ss = s*s; 39 | c = sqrt( 1.0 - ss ); 40 | } 41 | else{ 42 | c = ( w/r )*( 1.0 - s2*( a5 - u - c2*v )/r ); 43 | std::get<0>(geo) = acos( c ); //Lat 44 | ss = 1.0 - c*c; 45 | s = sqrt( ss ); 46 | } 47 | g = 1.0 - e2*ss; 48 | rg = a/sqrt( g ); 49 | rf = a6*rg; 50 | u = w - rg*c; 51 | v = zp - rf*s; 52 | f = c*u + s*v; 53 | m = c*v - s*u; 54 | p = m/( rf/g + f ); 55 | std::get<0>(geo) = std::get<0>(geo) + p; //Lat 56 | std::get<2>(geo) = f + m*p/2.0; //Altitude 57 | if( z < 0.0 ){ 58 | std::get<0>(geo) *= -1.0; //Lat 59 | } 60 | return( geo ); //Return Lat, Lon, Altitude in that order 61 | } 62 | -------------------------------------------------------------------------------- /html/style.css: -------------------------------------------------------------------------------- 1 | text { 2 | font: 12px sans-serif; 3 | } 4 | 5 | th, td { 6 | padding-left: 8px; 7 | padding-right: 8px; 8 | padding-top: 1px; 9 | padding-bottom: 1px; 10 | font-family: monospace; 11 | } 12 | 13 | tr:nth-child(even) { 14 | background: #BFBFBF 15 | } 16 | 17 | tr:nth-child(odd) { 18 | background: #E6E6E6 19 | } 20 | 21 | label { 22 | cursor: pointer; 23 | } 24 | 25 | input[type="checkbox"] { 26 | cursor: pointer; 27 | } 28 | 29 | .CellWithComment { 30 | position:relative; 31 | } 32 | 33 | .CellComment { 34 | display:none; 35 | position:absolute; 36 | z-index:100; 37 | border:1px; 38 | background-color:white; 39 | border-style:solid; 40 | border-width:1px; 41 | border-color:red; 42 | padding:3px; 43 | color:red; 44 | top:20px; 45 | left:20px; 46 | } 47 | 48 | .CellWithComment:hover span.CellComment { 49 | display:block; 50 | } 51 | 52 | .centered { 53 | margin: auto 0px; 54 | text-align: center; 55 | } 56 | 57 | #fields tr { 58 | vertical-align:top; 59 | } 60 | 61 | .Orbits { 62 | margin-top:3em; 63 | margin-bottom:1em; 64 | } 65 | 66 | .Orbits .Variables { 67 | display:inline-block; 68 | vertical-align:top; 69 | width:28em; 70 | height:44em; 71 | overflow:auto; 72 | position:relative; 73 | } 74 | 75 | .Orbits .Variables table { 76 | font-family:'Roboto Mono',monospace; 77 | color:#000; 78 | border-spacing:.5em; 79 | display:block; 80 | position:absolute; 81 | left:50%; 82 | transform:translateX(-50%); 83 | } 84 | 85 | .Orbits .Variables table tr { 86 | text-align:center; 87 | } 88 | 89 | .Orbits .Variables table td { 90 | font-size:.85em; 91 | } 92 | 93 | .Orbits .Drawing { 94 | display:inline-block; 95 | vertical-align:top; 96 | margin-top:.5em; 97 | width:44em; 98 | height:44em; 99 | } 100 | 101 | .Orbits .Drawing .sats-label { 102 | font-size:12px; 103 | fill:#454545; 104 | font-weight:bold; 105 | stroke:transparent; 106 | } 107 | 108 | .Orbits .Drawing .satellite circle { 109 | stroke-width:2px; 110 | } 111 | 112 | .Orbits .Drawing svg { 113 | font-family:sans-serif; 114 | font-size:10px; 115 | text-anchor:middle; 116 | fill:none; 117 | stroke:#000; 118 | width:100%; 119 | height:100%; 120 | } 121 | -------------------------------------------------------------------------------- /ephemeris.cc: -------------------------------------------------------------------------------- 1 | #include "ephemeris.hh" 2 | #include "minivec.hh" 3 | #include 4 | /* | t0e tow | - > tow - t0e, <3.5 days, so ok 5 | 6 | | t0e tow | -> tow - t0e > 3.5 days, so 7 | 7*86400 - tow + t0e 8 | 9 | | tow t0e | -> 7*86400 - tow + t0e > 3.5 days, so 10 | tow - t0e (negative age) 11 | 12 | | tow t0e | -> 7*86400 - tow + t0e < 3.5 days, ok 13 | */ 14 | 15 | // positive age = t0e in the past 16 | double ephAge(double tow, int t0e) 17 | { 18 | double diff = tow - t0e; 19 | if(diff > 3.5*86400) 20 | diff -= 604800; 21 | if(diff < -3.5*86400) 22 | diff += 604800; 23 | return diff; 24 | } 25 | 26 | 27 | 28 | // all axes start at earth center of gravity 29 | // x-axis is on equator, 0 longitude 30 | // y-axis is on equator, 90 longitude 31 | // z-axis is straight up to the north pole 32 | // https://en.wikipedia.org/wiki/ECEF#/media/File:Ecef.png 33 | std::pair getLongLat(double x, double y, double z) 34 | { 35 | auto ret = ecefToWGS84(x, y, z); 36 | return {std::get<1>(ret), std::get<0>(ret)}; 37 | } 38 | 39 | 40 | double getElevationDeg(const Point& sat, const Point& our) 41 | { 42 | 43 | Point core{0,0,0}; 44 | 45 | Vector core2us(core, our); 46 | Vector dx(our, sat); // = x-ourx, dy = y-oury, dz = z-ourz; 47 | 48 | // https://ds9a.nl/articles/ 49 | 50 | double elev = acos ( core2us.inner(dx) / (core2us.length() * dx.length())); 51 | double deg = 180.0* (elev/M_PI); 52 | return 90.0 - deg; 53 | } 54 | 55 | // https://gis.stackexchange.com/questions/58923/calculating-view-angle 56 | 57 | double getAzimuthDeg(const Point& sat, const Point& our) 58 | { 59 | Point core{0,0,0}; 60 | 61 | Vector north{ 62 | -our.z*our.x, 63 | -our.z*our.y, 64 | our.x*our.x + our.y * our.y}; 65 | 66 | Vector east{-our.y, our.x, 0}; 67 | 68 | Vector dx(our, sat); // = x-ourx, dy = y-oury, dz = z-ourz; 69 | 70 | // https://ds9a.nl/articles/ 71 | 72 | double azicos = ( north.inner(dx) / (north.length() * dx.length())); 73 | double azisin = ( east.inner(dx) / (east.length() * dx.length())); 74 | 75 | double azi = atan2(azisin, azicos); 76 | 77 | double deg = 180.0* (azi/M_PI); 78 | if(deg < 0) 79 | deg += 360; 80 | return deg; 81 | } 82 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: galmon 3 | Source: https://github.com/ahupowerdns/galmon 4 | 5 | Files: * 6 | Copyright: AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/ 7 | License: GPL-3 8 | This package is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 3 of the License, or 11 | (at your option) any later version. 12 | . 13 | This package is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | . 18 | You should have received a copy of the GNU General Public License 19 | along with this package; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | . 22 | On Debian systems, the complete text of the GNU General 23 | Public License can be found in `/usr/share/common-licenses/GPL-3'. 24 | 25 | Files: minicurl.cc 26 | minicurl.hh 27 | Copyright: 2018-2019 powerdns.com bv 28 | License: Expat 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | . 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | . 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | -------------------------------------------------------------------------------- /gndate.cc: -------------------------------------------------------------------------------- 1 | #include "navmon.hh" 2 | #include 3 | #include "CLI/CLI.hpp" 4 | #include "version.hh" 5 | 6 | extern const char* g_gitHash; 7 | using namespace std; 8 | 9 | int main(int argc, char** argv) 10 | try 11 | { 12 | string program("gndate"); 13 | CLI::App app(program); 14 | string date; 15 | int galwn{-1}; 16 | bool doProgOutput{false}; 17 | bool doGPSWN{false}, doGALWN{false}, doBEIDOUWN{false}, doVERSION{false}, doUTC{false}; 18 | app.add_flag("--version", doVERSION, "show program version and copyright"); 19 | app.add_option("--date,-d", date, "yyyy-mm-dd hh:mm[:ss] hh:mm yyyymmdd hhmm"); 20 | app.add_option("--date-gal-wn", galwn, "Give data for this Galileo week number"); 21 | app.add_flag("--utc,-u", doUTC, "Interpret --date,-d as UTC"); 22 | app.add_flag("--gps-wn", doGPSWN, "Print GPS week number"); 23 | app.add_flag("--gal-wn", doGALWN, "Print GPS week number"); 24 | app.add_flag("--prog-output", doProgOutput, "Modulate some date formats for use as parameters to programs"); 25 | try { 26 | app.parse(argc, argv); 27 | } catch(const CLI::Error &e) { 28 | return app.exit(e); 29 | } 30 | 31 | if(doVERSION) { 32 | showVersion(program.c_str(), g_gitHash); 33 | exit(0); 34 | } 35 | 36 | 37 | time_t now; 38 | if(date.empty()) 39 | now = time(0); 40 | else { 41 | if(doUTC) 42 | setenv("TZ", "UTC", 1); 43 | now = parseTime(date); 44 | } 45 | 46 | int wn, tow; 47 | 48 | if(galwn >= 0) { 49 | time_t week=utcFromGST(galwn, 0); 50 | if(doProgOutput) 51 | cout< 2 | 3 | 4 | galmon.eu geo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | This is a live map from the galmon.eu project. This map can plot if a fix is possible ('Coverage') or if the positioning, horizontal or vertical precision will be bad (PDOP, HDOP, VDOP). Red blocks indicate a problem even in perfect viewing conditions. Orange means a problem if there are obstructions below 10 degrees above of the horizon, yellow for less than 20 degrees ('urban canyon'). 16 | These calculations are very provisional and might be wrong! 17 |
18 |

19 | 20 | 21 | 22 | 23 |

24 |
25 |

26 |    27 |    28 |    29 |    30 |

31 |
32 |
33 | 34 |
35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /sbas.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "navmon.hh" 6 | #include 7 | #include "minivec.hh" 8 | 9 | // 0 do not use 10 | // 1 PRN mask 11 | // 2-5 fast corrections 12 | // 7 Fast correction degradation factor 13 | // 10 Degradation parameters 14 | // 18 Ionospheric grid point masks 15 | // 24 Mixed fastllong-term satellite error corrections 16 | // 25 half-message 17 | // 26 Ionospheric delay corrections 18 | // 27 SBAS service message 19 | 20 | // GSA HQ Prague 21 | const Point c_egnosCenter{3970085, 1021937, 4869792}; 22 | 23 | // Somewhere in Minnesota, Dakota, Canada border 24 | const Point c_waasCenter{-510062, -4166466, 4786089}; 25 | 26 | struct SBASState 27 | { 28 | struct FastCorrection 29 | { 30 | SatID id; 31 | double correction; 32 | int udrei; 33 | time_t lastUpdate{-1}; 34 | }; 35 | 36 | struct LongTermCorrection 37 | { 38 | SatID id; 39 | int iod8; 40 | int toa; 41 | int iodp; 42 | double dx, dy, dz, dai; 43 | double ddx{0}, ddy{0}, ddz{0}, ddai{0}; 44 | bool velocity{false}; 45 | time_t lastUpdate{-1}; 46 | }; 47 | 48 | std::pair, std::vector> parse(const std::basic_string& sbas, time_t now); 49 | 50 | void parse0(const std::basic_string& message, time_t now); 51 | 52 | // updates slot2prn mapping 53 | void parse1(const std::basic_string& message, time_t now); 54 | 55 | 56 | std::vector parse2_5(const std::basic_string& message, time_t now); 57 | 58 | std::vector parse6(const std::basic_string& message, time_t now); 59 | void parse7(const std::basic_string& message, time_t now); 60 | 61 | std::pair, std::vector> parse24(const std::basic_string& message, time_t now); 62 | 63 | std::vector parse25(const std::basic_string& message, time_t now); 64 | 65 | int getSBASNumber(int slot) const; 66 | SatID getSBASSatID(int slot) const; 67 | 68 | std::map d_fast; 69 | std::map d_longterm; 70 | 71 | time_t d_lastDNU{-1}; 72 | std::map d_slot2prn; 73 | int d_latency = -1; 74 | time_t d_lastSeen{-1}; 75 | void parse25H(const std::basic_string& sbas, time_t t, int offset, std::vector& ret); 76 | 77 | }; 78 | 79 | struct SBASCombo 80 | { 81 | SBASState::FastCorrection fast; 82 | SBASState::LongTermCorrection longterm; 83 | }; 84 | -------------------------------------------------------------------------------- /navmon.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | extern const char* g_gitHash; 10 | 11 | 12 | struct EofException{}; 13 | struct TimeoutError{}; 14 | 15 | size_t readn2(int fd, void* buffer, size_t len); 16 | size_t readn2Timeout(int fd, void* buffer, size_t len, double* timeout); 17 | std::string humanTimeNow(); 18 | std::string humanTime(time_t t); 19 | std::string humanTimeShort(time_t t); 20 | std::string humanTime(time_t t, uint32_t nanoseconds); 21 | // influx ascii time format, in UTC 22 | std::string influxTime(time_t t); 23 | struct SatID 24 | { 25 | uint32_t gnss{255}; // these could all be 'int16_t' but leads to howling numbers of warnings with protobuf 26 | uint32_t sv{0}; 27 | uint32_t sigid{0}; 28 | bool operator<(const SatID& rhs) const 29 | { 30 | return std::tie(gnss, sv, sigid) < std::tie(rhs.gnss, rhs.sv, rhs.sigid); 31 | } 32 | }; 33 | 34 | template 35 | class GetterSetter 36 | { 37 | public: 38 | void set(const T& t) 39 | { 40 | std::lock_guard mut(d_lock); 41 | d_t = t; 42 | } 43 | 44 | T get() 45 | { 46 | T ret; 47 | { 48 | std::lock_guard mut(d_lock); 49 | ret = d_t; 50 | } 51 | return ret; 52 | } 53 | private: 54 | T d_t; 55 | std::mutex d_lock; 56 | }; 57 | 58 | 59 | double truncPrec(double in, unsigned int digits); 60 | std::string humanFt(uint8_t ft); 61 | std::string humanSisa(uint8_t sisa); 62 | std::string humanUra(uint8_t ura); 63 | 64 | double numFt(uint8_t ft); 65 | double numSisa(uint8_t sisa); 66 | double numUra(uint8_t ura); 67 | 68 | 69 | 70 | char getGNSSChar(int id); 71 | std::string makeSatIDName(const SatID& satid); 72 | std::string makeSatPartialName(const SatID& satid); 73 | 74 | std::string sbasName(int prn); 75 | 76 | extern int g_dtLS, g_dtLSBeidou; 77 | uint64_t utcFromGST(int wn, int tow); 78 | double utcFromGST(int wn, double tow); 79 | double utcFromGPS(int wn, double tow); 80 | 81 | void getGPSDateFromUTC(time_t t, int& wn, int& tow); 82 | void getGalDateFromUTC(time_t t, int& wn, int& tow); 83 | void getBeiDouDateFromUTC(time_t t, int&wn, int& sow); 84 | 85 | std::string makeHexDump(const std::string& str); 86 | std::string makeHexDump(const std::basic_string& str); 87 | size_t writen2(int fd, const void *buf, size_t count); 88 | void unixDie(const std::string& reason); 89 | time_t parseTime(std::string_view in); 90 | std::string string_replace(const std::string& str, const std::string& match, 91 | const std::string& replacement, unsigned int max_replacements = UINT_MAX); 92 | -------------------------------------------------------------------------------- /nmmsender.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "navmon.pb.h" 7 | #include 8 | #include 9 | #include "zstdwrap.hh" 10 | #include "comboaddress.hh" 11 | #include "swrappers.hh" 12 | #include "sclasses.hh" 13 | 14 | class NMMSender 15 | { 16 | struct Destination 17 | { 18 | int fd{-1}; 19 | std::string dst; 20 | std::string fname; 21 | bool listener{false}; 22 | std::deque queue; 23 | std::mutex mut; 24 | void emitNMM(const std::string& out, bool compress); 25 | std::vector clients; 26 | }; 27 | 28 | public: 29 | void addDestination(int fd) 30 | { 31 | auto d = std::make_unique(); 32 | d->fd = fd; 33 | std::lock_guard l(d_destslock); 34 | d_dests.push_back(std::move(d)); 35 | } 36 | void addDestination(const std::string& dest) 37 | { 38 | auto d = std::make_unique(); 39 | d->dst = dest; 40 | std::lock_guard l(d_destslock); 41 | d_dests.push_back(std::move(d)); 42 | } 43 | void addListener(const std::string& dest) 44 | { 45 | auto d = std::make_unique(); 46 | d->dst = dest; 47 | d->listener = true; 48 | std::lock_guard l(d_destslock); 49 | d_dests.push_back(std::move(d)); 50 | } 51 | 52 | void launch() 53 | { 54 | for(auto& d : d_dests) { 55 | if(d->listener) { 56 | d_thread.emplace_back(std::move(std::make_unique(&NMMSender::acceptorThread, this, d.get()))); 57 | } 58 | else if(!d->dst.empty()) { 59 | d_thread.emplace_back(std::move(std::make_unique(&NMMSender::sendTCPThread, this, d.get()))); 60 | } 61 | } 62 | } 63 | 64 | void sendTCPThread(Destination* d); 65 | void acceptorThread(Destination* d); 66 | void forwarderThread(Destination* d, NMMSender* there); 67 | void sendTCPListenerThread(Destination* d, int fd, ComboAddress remote); 68 | void sendLoop(Destination* d, SocketCommunicator& sc, std::unique_ptr& zsc, Socket& s, std::map& unacked, time_t connStartTime); 69 | void emitNMM(const NavMonMessage& nmm); 70 | void emitNMM(const std::string& out); 71 | bool d_debug{false}; 72 | bool d_compress{false}; // set BEFORE launch 73 | bool d_pleaseQuit{false}; 74 | ~NMMSender() 75 | { 76 | if(!d_thread.empty()) { 77 | d_pleaseQuit = true; 78 | for(auto& t : d_thread) 79 | t->join(); 80 | } 81 | } 82 | 83 | private: 84 | std::mutex d_destslock; 85 | std::vector> d_dests; 86 | std::vector> d_thread; 87 | }; 88 | -------------------------------------------------------------------------------- /minicurl.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018-2019 powerdns.com bv 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | #include 27 | #include 28 | #include "comboaddress.hh" 29 | #include 30 | #include 31 | #include 32 | using std::string; 33 | // turns out 'CURL' is currently typedef for void which means we can't easily forward declare it 34 | 35 | class MiniCurl 36 | { 37 | public: 38 | using MiniCurlHeaders = std::map; 39 | 40 | static void init(); 41 | 42 | MiniCurl(const string& useragent="MiniCurl/0.0"); 43 | ~MiniCurl(); 44 | MiniCurl& operator=(const MiniCurl&) = delete; 45 | typedef std::map> certinfo_t; 46 | std::string getURL(const std::string& str, const bool nobody=0, certinfo_t* ciptr=0, const ComboAddress* rem=0, const ComboAddress* src=0); 47 | std::string postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers); 48 | 49 | std::string urlEncode(std::string_view str); 50 | CURL *d_curl; 51 | time_t d_filetime=-1; 52 | long d_http_code=-1; 53 | private: 54 | std::string d_data; 55 | static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata); 56 | 57 | struct curl_slist* d_header_list = nullptr; 58 | struct curl_slist *d_host_list = nullptr; 59 | void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0); 60 | void setHeaders(const MiniCurlHeaders& headers); 61 | void clearHeaders(); 62 | }; 63 | 64 | std::string extractHostFromURL(const std::string& url); 65 | -------------------------------------------------------------------------------- /Operator.md: -------------------------------------------------------------------------------- 1 | Hi! 2 | 3 | Many thanks for joining the galmon.eu project as an observer and station operator! 4 | 5 | Here are some ground rules to follow once you have locally tested your galmon station: 6 | 7 | * Please ask bert@hubertnet.nl or [ahu on 8 | IRC](https://webchat.oftc.net/?channels=galileo) or 9 | [@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) for a station ID and receiver 10 | hostname. 11 | * Please do not randomly pick a station ID!! Project data quality and integrity 12 | depends on your cooperation. 13 | * The rule is: one station ID per receiver. Do not under any circumstances 14 | use the same ID on multiple receivers, even if you think it should be 15 | fine. We have 2^64 IDs available, just ask for more IDs. 16 | * Even if you think it should be fine to reuse the same ID, for example 17 | because your observer page on galmon.eu will look nicer, DO NOT DO IT! 18 | It messes up our algorithms which track the clock of your receiver. 19 | * Please set the --owner and --remark fields - they help with administration. 20 | These fields will show up on https://galmon.eu/observers.html 21 | * Ublox8 based receivers support (GPS AND Galileo) + (BeiDou OR GLONASS). They can't 22 | process all four systems at the same time. GPS and Galileo share a slot. BeiDou or GLONASS 23 | share a second slot. See 24 | [here](https://www.geospatialworld.net/blogs/gnss-frequency-bands-for-constellations/) 25 | for some more information. 26 | * If you want to know if you should add BeiDou or GLONASS, you can either use your own 27 | preference or ask us what we are missing in your region. 28 | 29 | Downtime 30 | -------- 31 | The project is grateful for every bit you can provide. Don't feel bad about 32 | downtime either because your family needs the power plug for Christmas 33 | decorations (this happened) or for whatever reason. 34 | 35 | A good way to increase uptime is to use the Systemd file to ensure automatic 36 | startup on reboots. Some stations with bad power, bad hardware or bad cables 37 | have had great success enabling the Raspberry Pi watchdog. 38 | 39 | (need instruction here how to do that for various Raspberries, it differs) 40 | 41 | Upgrades 42 | -------- 43 | From time to time we upgrade the ubxtool receiver software. Old data also 44 | works but typically if you upgrade, the value to the network increases. 45 | 46 | Improving reception 47 | ------------------- 48 | A good view of the sky can help tremendously, although as noted, we are 49 | happy with every bit we receive. It helps to put your receiver on a "ground 50 | plate" which is a fancy word for a piece of metal (or mesh even). 51 | 52 | In addition, if you are in a northerly region, your view to the south is 53 | most important. Conversely, if you are in the south, more action will be to 54 | the north. 55 | -------------------------------------------------------------------------------- /sp3.cc: -------------------------------------------------------------------------------- 1 | #include "sp3.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | SP3Reader::SP3Reader(std::string_view fname) 11 | { 12 | d_fp = fopen(&fname[0], "r"); 13 | if(!d_fp) 14 | throw runtime_error("Unable to open "+(string)fname+": "+strerror(errno)); 15 | 16 | } 17 | 18 | SP3Reader::~SP3Reader() 19 | { 20 | if(d_fp) 21 | fclose(d_fp); 22 | } 23 | 24 | bool SP3Reader::get(SP3Entry& entry) 25 | { 26 | char line[80]; 27 | struct tm tm; 28 | 29 | while(fgets(line, sizeof(line), d_fp)) { 30 | if(line[0]=='*') { 31 | //0 1 2 3 4 5 6 32 | //* 2019 9 17 1 0 0.00000000 33 | 34 | std::stringstream ss(line); 35 | std::string token; 36 | int num = 0; 37 | memset(&tm, 0, sizeof(tm)); 38 | while (std::getline(ss, token, ' ')) { 39 | if(token.empty()) 40 | continue; 41 | int val = atoi(token.c_str()); 42 | if(num == 1) 43 | tm.tm_year = val - 1900; 44 | else if(num==2) 45 | tm.tm_mon = val - 1; 46 | else if(num == 3) 47 | tm.tm_mday = val; 48 | else if(num == 4) 49 | tm.tm_hour = val; 50 | else if(num == 5) 51 | tm.tm_min = val; 52 | else if(num==6) 53 | tm.tm_sec = val; 54 | 55 | num++; 56 | 57 | // cout<<"Token: "< 8 | #include "tle.hh" 9 | #include "sbas.hh" 10 | #include "ephemeris.hh" 11 | #include "rtcm.hh" 12 | 13 | using namespace std; // XXX 14 | 15 | struct SVPerRecv 16 | { 17 | int el{-1}, azi{-1}, db{-1}; 18 | time_t deltaHzTime{-1}; 19 | double deltaHz{-1}; 20 | double prres{-1}; 21 | int used{-1}; // -1 = unknown 22 | int qi{-1}; // quality indicator, -1 = unknown 23 | time_t t; // last seen 24 | }; 25 | 26 | class InfluxPusher; 27 | struct SVStat 28 | { 29 | int gnss; 30 | 31 | GPSState gpsmsg; // continuously being updated 32 | GPSState gpsmsg2, gpsmsg3; // new ephemeris being assembled here 33 | GPSState ephgpsmsg, oldephgpsmsg; // always has a consistent ephemeris 34 | GPSAlmanac gpsalma; 35 | 36 | int wn() const; // gets from the 'live' message 37 | int tow() const; // same 38 | 39 | TLERepo::Match tleMatch; 40 | double lastTLELookupX{0}; 41 | 42 | // live, ephemeris 43 | BeidouMessage beidoumsg, ephBeidoumsg, oldephBeidoumsg; 44 | // internal 45 | BeidouMessage lastBeidouMessage1, lastBeidouMessage2; 46 | 47 | // new galileo 48 | // consistent, live 49 | GalileoMessage ephgalmsg, galmsg, oldephgalmsg; 50 | // internal 51 | map galmsgTyped; 52 | bool osnma{false}; 53 | time_t osnmaTime{0}; 54 | 55 | bool impinav{false}; 56 | time_t impinavTime{0}; 57 | 58 | 59 | // Glonass 60 | GlonassMessage ephglomsg, glonassMessage, oldephglomsg; 61 | pair glonassAlmaEven; 62 | 63 | map perrecv; 64 | 65 | double latestDisco{-1}, latestDiscoAge{-1}, timeDisco{-1000}; 66 | 67 | map sbas; 68 | 69 | RTCMMessage::EphemerisDelta rtcmEphDelta; 70 | RTCMMessage::ClockDelta rtcmClockDelta; 71 | 72 | const GPSLikeEphemeris& liveIOD() const; 73 | const GPSLikeEphemeris& prevIOD() const; 74 | 75 | bool completeIOD() const; 76 | double getCoordinates(double tow, Point* p, bool quiet=true) const; 77 | double getOldEphCoordinates(double tow, Point* p, bool quiet=true) const; 78 | void getSpeed(double tow, Vector* v) const; 79 | DopplerData doDoppler(double tow, const Point& us, double freq) const; 80 | void reportNewEphemeris(const SatID& id, InfluxPusher& idb); 81 | }; 82 | 83 | 84 | typedef std::map svstats_t; 85 | 86 | // a vector of pairs of latidude,vector 87 | typedef vector > > > covmap_t; 88 | covmap_t emitCoverage(const vector& sats); 89 | struct xDOP 90 | { 91 | double gdop{-1}; 92 | double pdop{-1}; 93 | double tdop{-1}; 94 | double hdop{-1}; 95 | double vdop{-1}; 96 | }; 97 | 98 | xDOP getDOP(Point& us, vector sats); 99 | -------------------------------------------------------------------------------- /html/overview.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var repeat; 3 | 4 | moment.relativeTimeThreshold('m', 120); 5 | 6 | function makeTable(str, arr, svs) 7 | { 8 | var table=d3.select(str); 9 | table.html(""); 10 | var thead=table.append("thead"); 11 | var tbody=table.append("tbody"); 12 | 13 | var header=["",""]; 14 | var rows=tbody.selectAll("tr"). 15 | data(header). 16 | enter(). 17 | append("tr"); 18 | 19 | var columns = arr; 20 | columns.unshift("@"); 21 | console.log(columns); 22 | 23 | // append the header row 24 | thead.append("tr") 25 | .selectAll("th") 26 | .data(columns) 27 | .enter() 28 | .append("th") 29 | .text(function(d) { 30 | return d; 31 | }); 32 | 33 | var sigid = 1; 34 | var cells = rows.selectAll("td"). 35 | data(function(row) { 36 | var oldsigid = sigid; 37 | sigid = 5; 38 | return columns.map(function(column) { 39 | var ret={}; 40 | ret.sigid = oldsigid; 41 | console.log("column: "+column); 42 | console.log("sigid: "+oldsigid); 43 | if(column=="@") 44 | ret.value = oldsigid; 45 | else if(sigid == 1) 46 | ret.value = svs[column+"@"+sigid].e1bhs; 47 | else if(sigid == 5) 48 | ret.value = svs[column+"@"+sigid].e5bhs; 49 | 50 | if(ret.value == 0 || column=='@') 51 | ret.color = null; 52 | else if(ret.value == 3) 53 | ret.color="orange"; 54 | else ret.color = "red"; 55 | 56 | return ret; 57 | })}). 58 | enter().append("td").html(function(d) { 59 | return d.value; 60 | 61 | }).attr("align", "right").style("background-color", d=> d.color); 62 | 63 | } 64 | 65 | var sats={}; 66 | var lastseen=null; 67 | function update() 68 | { 69 | var seconds = 2; 70 | clearTimeout(repeat); 71 | repeat=setTimeout(update, 5000.0*seconds); 72 | 73 | if(lastseen != null) 74 | d3.select("#freshness").html(lastseen.fromNow()); 75 | d3.json("global", function(d) { 76 | lastseen = moment(1000*d["last-seen"]); 77 | d3.select("#freshness").html(lastseen.fromNow()); 78 | }); 79 | 80 | 81 | d3.queue(1).defer(d3.json, "../svs").defer(d3.json, "../almanac").defer(d3.json, "../observers").awaitAll(ready); 82 | 83 | function ready(error, results) { 84 | var arr=[]; 85 | Object.keys(results[0]).forEach(function(e) { 86 | // console.log(results[0][e]); 87 | if(results[0][e].gnssid == 2 && results[0][e].sigid==1) 88 | arr.push(e.slice(0,3)); 89 | }); 90 | makeTable("#galileo", arr, results[0]); 91 | }; 92 | 93 | } 94 | 95 | repeat=update(); 96 | 97 | 98 | -------------------------------------------------------------------------------- /html/almanac.js: -------------------------------------------------------------------------------- 1 | var repeat; 2 | 3 | moment.relativeTimeThreshold('m', 120); 4 | 5 | function maketable(str, arr) 6 | { 7 | var table=d3.select(str); 8 | table.html(""); 9 | var thead=table.append("thead"); 10 | var tbody=table.append("tbody"); 11 | 12 | var rows=tbody.selectAll("tr"). 13 | data(arr). 14 | enter(). 15 | append("tr"); 16 | 17 | var columns = ["sv", "best-tle", "best-tle-dist", "best-tle-norad", "best-tle-int-desig", "eph-ecefX", "eph-ecefY", "eph-ecefZ", "tle-ecefX", "tle-ecefY", "tle-ecefZ", "eph-latitude", "eph-longitude", "tle-latitude", "tle-longitude", "tle-eciX", "tle-eciY", "tle-eciZ", "t0e", "t", "E", "M0"]; 18 | 19 | // append the header row 20 | thead.append("tr") 21 | .selectAll("th") 22 | .data(columns) 23 | .enter() 24 | .append("th") 25 | .text(function(column) { 26 | if(column == "delta_hz_corr") 27 | return "ΔHz"; 28 | else 29 | return column; 30 | }); 31 | 32 | var cells = rows.selectAll("td"). 33 | data(function(row) { 34 | return columns.map(function(column) { 35 | var ret={}; 36 | ret.column = column; 37 | ret.color=null; 38 | if(row[column] != null && column != "sv" && column != "best-tle" && column != "best-tle-norad" && column != "best-tle-int-desig") 39 | ret.value = row[column].toFixed(1); 40 | else 41 | ret.value = row[column]; 42 | return ret; 43 | }) 44 | }) 45 | .enter().append("td").html(function(d) { return d.value; }).attr("align", "right").style("background-color", function(d) { 46 | return d.color; 47 | }); 48 | 49 | } 50 | 51 | var sats={}; 52 | var lastseen=null; 53 | function update() 54 | { 55 | var seconds = 2; 56 | clearTimeout(repeat); 57 | repeat=setTimeout(update, 1000.0*seconds); 58 | 59 | if(lastseen != null) 60 | d3.select("#freshness").html(lastseen.fromNow()); 61 | d3.json("global.json", function(d) { 62 | lastseen = moment(1000*d["last-seen"]); 63 | d3.select("#freshness").html(lastseen.fromNow()); 64 | }); 65 | 66 | 67 | d3.json("almanac.json", function(d) { 68 | // put data in an array 69 | sats=d; 70 | var arr=[]; 71 | Object.keys(d).forEach(function(e) { 72 | var o = d[e]; 73 | o.sv=e; 74 | arr.push(o); 75 | }); 76 | 77 | // sort it on SV 78 | arr.sort(function(a, b) { 79 | if(Number(a.sv) < Number(b.sv)) 80 | return -1; 81 | if(Number(b.sv) < Number(a.sv)) 82 | return 1; 83 | return 0; 84 | }); 85 | 86 | var livearr=[]; 87 | for(n = 0 ; n < arr.length; n++) 88 | { 89 | livearr.push(arr[n]); 90 | } 91 | 92 | maketable("#svs", livearr); 93 | }); 94 | } 95 | 96 | repeat=update(); 97 | 98 | 99 | -------------------------------------------------------------------------------- /minread.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BAUDRATE B921600 11 | #define MODEMDEVICE "/dev/ttyACM0" 12 | #define _POSIX_SOURCE 1 /* POSIX compliant source */ 13 | #define FALSE 0 14 | #define TRUE 1 15 | 16 | volatile int STOP=FALSE; 17 | 18 | int main() 19 | { 20 | int fd,c, res; 21 | struct termios oldtio,newtio; 22 | char buf[255]; 23 | 24 | fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 25 | if (fd <0) {perror(MODEMDEVICE); exit(-1); } 26 | 27 | tcgetattr(fd,&oldtio); /* save current port settings */ 28 | 29 | bzero(&newtio, sizeof(newtio)); 30 | newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; 31 | newtio.c_iflag = IGNPAR; 32 | newtio.c_oflag = 0; 33 | 34 | /* set input mode (non-canonical, no echo,...) */ 35 | newtio.c_lflag = 0; 36 | 37 | newtio.c_cc[VTIME] = 0; /* inter-character timer unused */ 38 | newtio.c_cc[VMIN] = 5; /* blocking read until 5 chars received */ 39 | 40 | tcflush(fd, TCIFLUSH); 41 | tcsetattr(fd,TCSANOW,&newtio); 42 | 43 | for(;;) { 44 | res = read(fd, buf, 25); 45 | if(res < 0) 46 | break; 47 | for(int n=0; n < res; ++n) 48 | printf("%c", buf[n]); 49 | fflush(stdout); 50 | } 51 | tcsetattr(fd,TCSANOW,&oldtio); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /architecture.md: -------------------------------------------------------------------------------- 1 | Architecture 2 | ------------ 3 | 4 | The galmon project consists of several components. Core of what we do are 5 | protobuf messages that are (mostly) device independent and contain satellite 6 | data and metadata. 7 | 8 | All protobuf messages are 'NavMonMessages', of which there are 15 types. 9 | These messages are: 10 | 11 | * Navigation messages from satellites, various types 12 | * Device / software metadata 13 | * Reception strength details 14 | * Doppler, carrier phase data 15 | * RTCM messages 16 | 17 | For every supported receiver type, there is a tool to convert the device 18 | specific protocol to these protobuf messages. Currently we only have 19 | 'ubxtool' that does this for many u-blox devices. In addition, 'rtcmtool' 20 | converts a RTCM to protobuf. 21 | 22 | Components 23 | ---------- 24 | 25 | Core components: 26 | * ubxtool/rtcmtool: generate protobuf messages, transmit them over network 27 | * navrecv: receive protobuf messages over the network and store them on 28 | disk 29 | * navnexus: serve protobuf messages from disk over the network 30 | * navparse: consume protobuf and turn into a webserver with data, plus 31 | optionally fill an influxdb time-series database for graphing and analysis 32 | purposes. 33 | 34 | This offers a complete suite for: 35 | 36 | * generating protobuf messages and transmitting them 37 | * reception & long-term storage 38 | * serving protobuf messages 39 | * analysing them for display & storing statistics for analysis 40 | 41 | 42 | Non-core tools: 43 | * navdump: convert protobuf format data into a textual display of messages 44 | * navcat: serve protobuf messages from disk directly to stdout 45 | * reporter: make "the galmon.eu weekly galileo report", based on the 46 | time-series database filled by navparse 47 | * rinreport: rinex analysis tooling (not generically useful yet) 48 | * galmonmon: monitor a navparse instance for changes, tweet them out 49 | * navdisplay: some eye-candy that converts protobuf into a live display 50 | (not very good) 51 | 52 | Transmission details 53 | -------------------- 54 | Messages are sent as protobuf messages with an intervening magic value and a 55 | length field. 56 | 57 | Both rtcmtool and ubxtool can send data over TCP/IP, but also to standard 58 | output. This standard output mode makes it possible to pipe the output of 59 | these tools straight into navdump or navparse. 60 | 61 | There are two transport protocols, one uncompressed, which consists of the 4 62 | byte magic value, a 2 byte length field, and then the message. This protocol 63 | has been deprecated for TCP, but it is still there for --stdout output. 64 | 65 | The second protocol is zstd compressed, and features acknowledgements. The 66 | protocol is an initial 4 byte magic value "RNIE", followed by 8 reserved 67 | bytes which are currently ignored. Then zstd compression starts, each 68 | message consists of a 4 byte message number, followed by a two byte length 69 | field, followed by the message. There are no further magic values beyond the 70 | first one. 71 | 72 | The receiver is expected to return the 4 byte message number for each 73 | message received. Messages which have not been acknowledged like this will 74 | be retransmitted on reconnect. 75 | 76 | Storage details 77 | --------------- 78 | Storage is very simplistic but robust. For every receiver, for every hour, 79 | there is a file with protobuf messages. That's it. The goal is for the 80 | receiver to never ever go down. 81 | 82 | navnexus and navcat can consume data from this simplistic storage and serve 83 | it over TCP/IP or to standard output. 84 | 85 | -------------------------------------------------------------------------------- /html/observers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var repeat; 3 | 4 | moment.relativeTimeThreshold('m', 120); 5 | 6 | function escapeHTML(html) { 7 | return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML; 8 | } 9 | 10 | 11 | function makeTable(str, arr) 12 | { 13 | var table=d3.select(str); 14 | table.html(""); 15 | var thead=table.append("thead"); 16 | var tbody=table.append("tbody"); 17 | 18 | var rows=tbody.selectAll("tr"). 19 | data(arr). 20 | enter(). 21 | append("tr"); 22 | 23 | var columns= ["id", "last-seen", "latitude", "longitude", "owner", "remark", "serialno", "hwversion", "swversion", "impinav", "mods", "githash", "uptime", "clockdriftns", "clockacc", "freqacc", "h", "acc", "satellites"]; 24 | 25 | // append the header row 26 | thead.append("tr") 27 | .selectAll("th") 28 | .data(columns) 29 | .enter() 30 | .append("th") 31 | .text(function(d) { 32 | return d; 33 | }); 34 | 35 | var cells = rows.selectAll("td"). 36 | data(function(row) { 37 | return columns.map(function(column) { 38 | var ret={}; 39 | ret.align = "right"; 40 | ret.color = null; 41 | if(column == "id") { 42 | ret.value=''+row[column]+""; 43 | } 44 | else if(column == "impinav") { 45 | if(row["impinav"]==true) 46 | ret.value="✅"; 47 | else 48 | ret.value=""; 49 | } 50 | else if(column == "last-seen") { 51 | ret.value = moment(1000*row["last-seen"]).fromNow(); 52 | let lastSeen = moment(1000*row["last-seen"]); 53 | let age = (moment() - lastSeen); 54 | // console.log(age.valueOf()/1000); 55 | if(age.valueOf() / 60000 > 5) 56 | ret.color = "red"; 57 | 58 | } 59 | else if(column == "satellites") { 60 | ret.value = 0; 61 | Object.keys(row["svs"]).forEach(function(d) {ret.value = ret.value +1 }); 62 | } 63 | else { 64 | ret.value = escapeHTML(row[column]); 65 | } 66 | return ret; 67 | })}). 68 | enter().append("td").html(function(d) { 69 | return d.value; 70 | 71 | }).attr("align", d=> d.align).style("background-color", d=> d.color); 72 | 73 | } 74 | 75 | var sats={}; 76 | var lastseen=null; 77 | function update() 78 | { 79 | var seconds = 2; 80 | clearTimeout(repeat); 81 | repeat=setTimeout(update, 5000.0*seconds); 82 | 83 | if(lastseen != null) 84 | d3.select("#freshness").html(lastseen.fromNow()); 85 | d3.json("./global.json", function(d) { 86 | lastseen = moment(1000*d["last-seen"]); 87 | d3.select("#freshness").html(lastseen.fromNow()); 88 | }); 89 | 90 | 91 | d3.queue(1).defer(d3.json, "./svs.json").defer(d3.json, "./observers.json").awaitAll(ready); 92 | 93 | function ready(error, results) { 94 | var arr=[]; 95 | Object.keys(results[1]).forEach(function(e) { 96 | arr.push(results[1][e]); 97 | }); 98 | makeTable("#galileo", arr, results[0]); 99 | }; 100 | 101 | } 102 | 103 | repeat=update(); 104 | 105 | 106 | -------------------------------------------------------------------------------- /html/geo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | galmon.eu geo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

galmon.eu geo

17 |
18 | 19 | This is a live map from the galmon.eu project 20 | 21 | 22 | 23 |  |  24 | 25 |  |  26 | 27 | 28 | 29 | 33 | 34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /rinex.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "navmon.hh" 8 | #include 9 | #include "fmt/format.h" 10 | #include "fmt/printf.h" 11 | 12 | struct RINEXEntry 13 | { 14 | time_t t; 15 | int gnss; 16 | int sv; 17 | double sisa; 18 | int health; 19 | int toe; 20 | int tow; 21 | int iodnav; 22 | double af0, af1, af2; 23 | double clkflags; 24 | double BGDE1E5a, BGDE1E5b; 25 | }; 26 | 27 | class RINEXReader 28 | { 29 | public: 30 | RINEXReader(std::string_view fname); 31 | bool get(RINEXEntry& rinex); 32 | ~RINEXReader(); 33 | private: 34 | void skipFileHeader(); 35 | std::string d_fname; 36 | gzFile d_fp{0}; 37 | time_t d_time{0}; 38 | }; 39 | 40 | class RINEXNavWriter 41 | { 42 | public: 43 | explicit RINEXNavWriter(std::string_view fname); 44 | template 45 | void emitEphemeris(const SatID& sid, const T& t); 46 | private: 47 | std::ofstream d_ofs; 48 | void emit(double n) 49 | { 50 | if(n >= 0) 51 | d_ofs << fmt::format(" {:18.12E}", n); 52 | else 53 | d_ofs << fmt::format("{:019.12E}", n); 54 | } 55 | }; 56 | 57 | template 58 | void RINEXNavWriter::emitEphemeris(const SatID& sid, const T& e) 59 | { 60 | 61 | /* 62 | E01 2019 09 21 23 30 00-6.949011585675E-04-7.943867785798E-12 0.000000000000E+00 63 | 1.090000000000E+02 9.746875000000E+01 2.820474626946E-09 2.393449606726E+00 64 | 4.611909389496E-06 2.816439373419E-04 7.767230272293E-06 5.440614154816E+03 65 | 6.030000000000E+05 1.862645149231E-09-1.121206798215E+00-4.656612873077E-08 66 | 9.859399710315E-01 1.802500000000E+02-2.137974852171E+00-5.485228481783E-09 67 | -1.789360248322E-10 5.170000000000E+02 2.071000000000E+03 68 | 3.120000000000E+00 0.000000000000E+00-1.862645149231E-09-2.095475792885E-09 69 | 6.036660000000E+05 70 | */ 71 | 72 | using std::endl; 73 | time_t then = utcFromGST(e.wn, (int)e.tow); 74 | struct tm tm; 75 | gmtime_r(&then, &tm); 76 | 77 | // 0 78 | 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); 79 | 80 | emit(ldexp(e.af0, -34)); 81 | emit(ldexp(e.af1, -46)); 82 | emit(ldexp(e.af2, -59)); 83 | d_ofs<<"\n "; 84 | 85 | // 1 86 | emit(e.iodnav); 87 | emit(e.getCrs()); 88 | emit(e.getDeltan()); 89 | emit(e.getM0()); 90 | d_ofs<<"\n "; 91 | 92 | // 2 93 | emit(e.getCuc()); 94 | emit(e.getE()); 95 | emit(e.getCus()); 96 | emit(e.getSqrtA()); 97 | d_ofs<<"\n "; 98 | 99 | // 3 100 | emit(e.getT0e()); 101 | emit(e.getCic()); 102 | emit(e.getOmega0()); 103 | emit(e.getCis()); 104 | 105 | d_ofs<<"\n "; 106 | 107 | // 4 108 | emit(e.getI0()); 109 | emit(e.getCrc()); 110 | emit(e.getOmega()); 111 | emit(e.getOmegadot()); 112 | 113 | d_ofs<<"\n "; 114 | 115 | // 5 116 | emit(e.getIdot()); 117 | 118 | emit(257); // bit 0 = I/NAV E1, bit 1 = F/NAV E5a, bit 2 = I/NAV E5b 119 | // bit 8 = E1,E5a clock aka F/NAV, bit 9 = E1,E5b aka I/NAV 120 | emit(e.wn + 1024); // so it aligns with GPS 121 | 122 | 123 | // 6 124 | d_ofs<<"\n "; 125 | emit(numSisa(e.sisa)); 126 | int health=0; 127 | health |= e.e1bdvs; 128 | health |= (e.e1bhs << 2); 129 | // don't have e5advs 130 | // don't have e5ahs 131 | health |= (e.e5bdvs << 6); 132 | health |= (e.e5bhs << 8); 133 | emit(health); // HEALTH 134 | emit(ldexp(e.BGDE1E5a, -32)); 135 | emit(ldexp(e.BGDE1E5b, -32)); 136 | 137 | // 7 138 | d_ofs<<"\n "; 139 | emit(e.tow); // XXX 140 | 141 | d_ofs< d.align).style("background-color", d=> d.color); 47 | 48 | } 49 | 50 | var sats={}; 51 | var lastseen=null; 52 | var sv=2; 53 | var gnssid=3; 54 | var sigid=0; 55 | 56 | function update() 57 | { 58 | var seconds = 2; 59 | clearTimeout(repeat); 60 | repeat=setTimeout(update, 1000.0*seconds); 61 | 62 | if(lastseen != null) 63 | d3.select("#freshness").html(lastseen.fromNow()); 64 | d3.json("./global.json", function(d) { 65 | lastseen = moment(1000*d["last-seen"]); 66 | d3.select("#freshness").html(lastseen.fromNow()); 67 | }); 68 | 69 | 70 | d3.queue(1).defer(d3.json, "./sv.json?gnssid="+gnssid+"&sv="+sv+"&sigid="+sigid).defer(d3.json, "./almanac.json").awaitAll(ready); 71 | 72 | function ready(error, results) { 73 | var arr=[]; 74 | let recvs; 75 | Object.keys(results[0]).forEach(function(e) { 76 | if(e=="recvs") { 77 | recvs=results[0][e]; 78 | return; 79 | } 80 | arr.push({id: e, value: results[0][e]}); 81 | }); 82 | 83 | var newarr=[]; 84 | for(var n=0 ; n < arr.length; n+=2) { 85 | 86 | if(n + 1 < arr.length) 87 | newarr.push({id1: arr[n].id, value1: arr[n].value, id2: arr[n+1].id, value2: arr[n+1].value}); 88 | else 89 | newarr.push({id1: arr[n].id, value1: arr[n].value, id2: "", value2: ""}); 90 | } 91 | 92 | Object.keys(recvs).forEach(function(e) { 93 | if(recvs[e]["last-seen-s"] < 60) 94 | newarr.push({id1:"", value1:"", id2:"", value2:"", 95 | src: e, 96 | db: recvs[e].db, 97 | qi: recvs[e].qi, 98 | prres: recvs[e].prres.toFixed(1), 99 | elev: recvs[e].elev, 100 | used: recvs[e].used 101 | }); 102 | }); 103 | makeTable("#galileo", newarr, results[0]); 104 | }; 105 | 106 | } 107 | 108 | console.log(window.location.href); 109 | var url = new URL(window.location.href); 110 | sv = url.searchParams.get("sv"); 111 | gnssid = url.searchParams.get("gnssid"); 112 | sigid = url.searchParams.get("sigid"); 113 | 114 | 115 | 116 | 117 | repeat=update(); 118 | 119 | 120 | -------------------------------------------------------------------------------- /tle.cc: -------------------------------------------------------------------------------- 1 | #include "tle.hh" 2 | #include "SGP4.h" 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | static void trim(std::string& str) 9 | { 10 | auto pos = str.find_first_of("\r\n"); 11 | if(pos != string::npos) 12 | str.resize(pos); 13 | pos = str.find_last_not_of(" \t"); 14 | if(pos != string::npos) 15 | str.resize(pos+1); 16 | } 17 | 18 | void TLERepo::parseFile(std::string_view fname) 19 | { 20 | ifstream ifs(&fname[0]); 21 | string name, line1, line2; 22 | for(;;) { 23 | if(!getline(ifs, name) || !getline(ifs, line1) || !getline(ifs, line2)) 24 | break; 25 | trim(name); 26 | trim(line1); 27 | trim(line2); 28 | Tle tle(line1, line2); 29 | /* 30 | cout<<"name: "<>(SGP4(tle), tle); 36 | d_sgp4s[name] = std::move(sgp4); 37 | } 38 | } 39 | 40 | TLERepo::TLERepo() 41 | {} 42 | 43 | TLERepo::~TLERepo() 44 | {} 45 | 46 | 47 | static TLERepo::Match makeMatch(const DateTime& d, const SGP4& sgp4, const Tle& tle, string name, double distance) 48 | { 49 | TLERepo::Match m; 50 | m.name=name; 51 | m.norad = tle.NoradNumber(); 52 | m.internat = tle.IntDesignator(); 53 | m.inclination = tle.Inclination(false); 54 | m.ran = tle.RightAscendingNode(false); 55 | m.e = tle.Eccentricity(); 56 | m.distance = distance; 57 | 58 | auto eci = sgp4.FindPosition(d); 59 | 60 | double theta = -eci.GetDateTime().ToGreenwichSiderealTime(); 61 | Vector rot = eci.Position(); 62 | m.eciX = 1000.0*rot.x; 63 | m.eciY = 1000.0*rot.y; 64 | m.eciZ = 1000.0*rot.z; 65 | 66 | rot.x = eci.Position().x * cos(theta) - eci.Position().y * sin(theta); 67 | rot.y = eci.Position().x * sin(theta) + eci.Position().y * cos(theta); 68 | 69 | m.ecefX = 1000.0 * rot.x; 70 | m.ecefY = 1000.0 * rot.y; 71 | m.ecefZ = 1000.0 * rot.z; 72 | 73 | auto geod = eci.ToGeodetic(); 74 | m.latitude = geod.latitude; 75 | m.longitude = geod.longitude; 76 | m.altitude = geod.altitude; 77 | return m; 78 | } 79 | 80 | TLERepo::Match TLERepo::getBestMatch(time_t now, double x, double y, double z, TLERepo::Match* secondbest) 81 | { 82 | struct tm tm; 83 | gmtime_r(&now, &tm); 84 | DateTime d(1900 + tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); 85 | 86 | Vector sat(x/1000.0, y/1000.0, z/1000.0); 87 | 88 | multimap distances; 89 | for(const auto& sgp4 : d_sgp4s) { 90 | try { 91 | auto eci = get<0>(*sgp4.second).FindPosition(d); 92 | 93 | double theta = -eci.GetDateTime().ToGreenwichSiderealTime(); 94 | Vector rot = eci.Position(); 95 | rot.x = eci.Position().x * cos(theta) - eci.Position().y * sin(theta); 96 | rot.y = eci.Position().x * sin(theta) + eci.Position().y * cos(theta); 97 | 98 | distances.insert({1000.0*(rot - sat).Magnitude(),sgp4.first}); 99 | } 100 | catch(SatelliteException& se) { 101 | // cerr<<"TLE error: "<(*d_sgp4s[iter->second]), get<1>(*d_sgp4s[iter->second]), iter->second, iter->first); 118 | } 119 | } 120 | 121 | return makeMatch(d, get<0>(*d_sgp4s[distances.begin()->second]), get<1>(*d_sgp4s[distances.begin()->second]), distances.begin()->second, distances.begin()->first); 122 | 123 | } 124 | -------------------------------------------------------------------------------- /beidou.cc: -------------------------------------------------------------------------------- 1 | #include "beidou.hh" 2 | #include "bits.hh" 3 | #include 4 | 5 | using namespace std; 6 | 7 | // with immense gratitude to https://stackoverflow.com/questions/24612436/how-to-check-a-bch15-11-1-code-checksum-for-bds-beidou-satellite-system 8 | 9 | static int checkbds(int bits) 10 | { 11 | static int const at[15] = {1, 2, 4, 8, 3, 6, 12, 11, 5, 10, 7, 14, 15, 13, 9}; 12 | int s, i, j; 13 | 14 | for(i = 1; i <= 2; i++) 15 | { 16 | s = 0; 17 | for(j = 0; j < 15; j++) 18 | { 19 | if(bits & (1< getCondensedBeidouMessage(std::basic_string_view payload) 33 | { 34 | 35 | // payload consists of 32 bit words where we have to ignore the first 2 bits of every word 36 | 37 | int chunk = getbitu(&payload[0], 17, 15); 38 | // cout <<"w0 checkbds(chunk0): "<(buffer, 28); 60 | } 61 | 62 | 63 | // the BeiDou ICD lists bits from bit 1 64 | // In addition, they count the parity bits which we strip out 65 | // this function converts their bit numbers to ours 66 | // the first word has 4 parity bits, the rest has 8 67 | int beidouBitconv(int their) 68 | { 69 | int wordcount = their/30; 70 | 71 | if(!wordcount) 72 | return their - 1; 73 | 74 | return their - (1 + 4 + (wordcount -1)*8); 75 | } 76 | 77 | 78 | bool processBeidouAlmanac(const BeidouMessage& bm, struct BeidouAlmanacEntry& bae) 79 | { 80 | bae.alma = bm.alma; 81 | int pageno = bm.alma.pageno; 82 | 83 | if(bm.fraid == 5 && pageno >= 11 && pageno <= 23) { 84 | /* 85 | cout<<" AmEpID "<< bm.alma.AmEpID; 86 | cout << " AmID "<>(7-i%8))&1u); 8 | return bits; 9 | } 10 | 11 | int getbits(const unsigned char *buff, int pos, int len) 12 | { 13 | unsigned int bits=getbitu(buff,pos,len); 14 | if (len<=0||32<=len||!(bits&(1u<<(len-1)))) return (int)bits; 15 | return (int)(bits|(~0u<>=1) { 32 | if (data&mask) buff[i/8]|=1u<<(7-i%8); else buff[i/8]&=~(1u<<(7-i%8)); 33 | } 34 | } 35 | static const unsigned int tbl_CRC24Q[]={ 36 | 0x000000,0x864CFB,0x8AD50D,0x0C99F6,0x93E6E1,0x15AA1A,0x1933EC,0x9F7F17, 37 | 0xA18139,0x27CDC2,0x2B5434,0xAD18CF,0x3267D8,0xB42B23,0xB8B2D5,0x3EFE2E, 38 | 0xC54E89,0x430272,0x4F9B84,0xC9D77F,0x56A868,0xD0E493,0xDC7D65,0x5A319E, 39 | 0x64CFB0,0xE2834B,0xEE1ABD,0x685646,0xF72951,0x7165AA,0x7DFC5C,0xFBB0A7, 40 | 0x0CD1E9,0x8A9D12,0x8604E4,0x00481F,0x9F3708,0x197BF3,0x15E205,0x93AEFE, 41 | 0xAD50D0,0x2B1C2B,0x2785DD,0xA1C926,0x3EB631,0xB8FACA,0xB4633C,0x322FC7, 42 | 0xC99F60,0x4FD39B,0x434A6D,0xC50696,0x5A7981,0xDC357A,0xD0AC8C,0x56E077, 43 | 0x681E59,0xEE52A2,0xE2CB54,0x6487AF,0xFBF8B8,0x7DB443,0x712DB5,0xF7614E, 44 | 0x19A3D2,0x9FEF29,0x9376DF,0x153A24,0x8A4533,0x0C09C8,0x00903E,0x86DCC5, 45 | 0xB822EB,0x3E6E10,0x32F7E6,0xB4BB1D,0x2BC40A,0xAD88F1,0xA11107,0x275DFC, 46 | 0xDCED5B,0x5AA1A0,0x563856,0xD074AD,0x4F0BBA,0xC94741,0xC5DEB7,0x43924C, 47 | 0x7D6C62,0xFB2099,0xF7B96F,0x71F594,0xEE8A83,0x68C678,0x645F8E,0xE21375, 48 | 0x15723B,0x933EC0,0x9FA736,0x19EBCD,0x8694DA,0x00D821,0x0C41D7,0x8A0D2C, 49 | 0xB4F302,0x32BFF9,0x3E260F,0xB86AF4,0x2715E3,0xA15918,0xADC0EE,0x2B8C15, 50 | 0xD03CB2,0x567049,0x5AE9BF,0xDCA544,0x43DA53,0xC596A8,0xC90F5E,0x4F43A5, 51 | 0x71BD8B,0xF7F170,0xFB6886,0x7D247D,0xE25B6A,0x641791,0x688E67,0xEEC29C, 52 | 0x3347A4,0xB50B5F,0xB992A9,0x3FDE52,0xA0A145,0x26EDBE,0x2A7448,0xAC38B3, 53 | 0x92C69D,0x148A66,0x181390,0x9E5F6B,0x01207C,0x876C87,0x8BF571,0x0DB98A, 54 | 0xF6092D,0x7045D6,0x7CDC20,0xFA90DB,0x65EFCC,0xE3A337,0xEF3AC1,0x69763A, 55 | 0x578814,0xD1C4EF,0xDD5D19,0x5B11E2,0xC46EF5,0x42220E,0x4EBBF8,0xC8F703, 56 | 0x3F964D,0xB9DAB6,0xB54340,0x330FBB,0xAC70AC,0x2A3C57,0x26A5A1,0xA0E95A, 57 | 0x9E1774,0x185B8F,0x14C279,0x928E82,0x0DF195,0x8BBD6E,0x872498,0x016863, 58 | 0xFAD8C4,0x7C943F,0x700DC9,0xF64132,0x693E25,0xEF72DE,0xE3EB28,0x65A7D3, 59 | 0x5B59FD,0xDD1506,0xD18CF0,0x57C00B,0xC8BF1C,0x4EF3E7,0x426A11,0xC426EA, 60 | 0x2AE476,0xACA88D,0xA0317B,0x267D80,0xB90297,0x3F4E6C,0x33D79A,0xB59B61, 61 | 0x8B654F,0x0D29B4,0x01B042,0x87FCB9,0x1883AE,0x9ECF55,0x9256A3,0x141A58, 62 | 0xEFAAFF,0x69E604,0x657FF2,0xE33309,0x7C4C1E,0xFA00E5,0xF69913,0x70D5E8, 63 | 0x4E2BC6,0xC8673D,0xC4FECB,0x42B230,0xDDCD27,0x5B81DC,0x57182A,0xD154D1, 64 | 0x26359F,0xA07964,0xACE092,0x2AAC69,0xB5D37E,0x339F85,0x3F0673,0xB94A88, 65 | 0x87B4A6,0x01F85D,0x0D61AB,0x8B2D50,0x145247,0x921EBC,0x9E874A,0x18CBB1, 66 | 0xE37B16,0x6537ED,0x69AE1B,0xEFE2E0,0x709DF7,0xF6D10C,0xFA48FA,0x7C0401, 67 | 0x42FA2F,0xC4B6D4,0xC82F22,0x4E63D9,0xD11CCE,0x575035,0x5BC9C3,0xDD8538 68 | }; 69 | 70 | 71 | /* crc-24q parity -------------------------------------------------------------- 72 | * compute crc-24q parity for sbas, rtcm3 73 | * args : unsigned char *buff I data 74 | * int len I data length (bytes) 75 | * return : crc-24Q parity 76 | * notes : see reference [2] A.4.3.3 Parity 77 | *-----------------------------------------------------------------------------*/ 78 | unsigned int rtk_crc24q(const unsigned char *buff, int len) 79 | { 80 | unsigned int crc=0; 81 | int i; 82 | 83 | for (i=0;i>16)^buff[i]]; 84 | return crc; 85 | } 86 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/ConfigFwd.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Error.hpp" 16 | #include "StringTools.hpp" 17 | 18 | namespace CLI { 19 | 20 | class App; 21 | 22 | /// Holds values to load into Options 23 | struct ConfigItem { 24 | /// This is the list of parents 25 | std::vector parents{}; 26 | 27 | /// This is the name 28 | std::string name{}; 29 | 30 | /// Listing of inputs 31 | std::vector inputs{}; 32 | 33 | /// The list of parents and name joined by "." 34 | std::string fullname() const { 35 | std::vector tmp = parents; 36 | tmp.emplace_back(name); 37 | return detail::join(tmp, "."); 38 | } 39 | }; 40 | 41 | /// This class provides a converter for configuration files. 42 | class Config { 43 | protected: 44 | std::vector items{}; 45 | 46 | public: 47 | /// Convert an app into a configuration 48 | virtual std::string to_config(const App *, bool, bool, std::string) const = 0; 49 | 50 | /// Convert a configuration into an app 51 | virtual std::vector from_config(std::istream &) const = 0; 52 | 53 | /// Get a flag value 54 | virtual std::string to_flag(const ConfigItem &item) const { 55 | if(item.inputs.size() == 1) { 56 | return item.inputs.at(0); 57 | } 58 | throw ConversionError::TooManyInputsFlag(item.fullname()); 59 | } 60 | 61 | /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure 62 | std::vector from_file(const std::string &name) { 63 | std::ifstream input{name}; 64 | if(!input.good()) 65 | throw FileError::Missing(name); 66 | 67 | return from_config(input); 68 | } 69 | 70 | /// Virtual destructor 71 | virtual ~Config() = default; 72 | }; 73 | 74 | /// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML 75 | class ConfigBase : public Config { 76 | protected: 77 | /// the character used for comments 78 | char commentChar = ';'; 79 | /// the character used to start an array '\0' is a default to not use 80 | char arrayStart = '\0'; 81 | /// the character used to end an array '\0' is a default to not use 82 | char arrayEnd = '\0'; 83 | /// the character used to separate elements in an array 84 | char arraySeparator = ' '; 85 | /// the character used separate the name from the value 86 | char valueDelimiter = '='; 87 | 88 | public: 89 | std::string 90 | to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; 91 | 92 | std::vector from_config(std::istream &input) const override; 93 | /// Specify the configuration for comment characters 94 | ConfigBase *comment(char cchar) { 95 | commentChar = cchar; 96 | return this; 97 | } 98 | /// Specify the start and end characters for an array 99 | ConfigBase *arrayBounds(char aStart, char aEnd) { 100 | arrayStart = aStart; 101 | arrayEnd = aEnd; 102 | return this; 103 | } 104 | /// Specify the delimiter character for an array 105 | ConfigBase *arrayDelimiter(char aSep) { 106 | arraySeparator = aSep; 107 | return this; 108 | } 109 | /// Specify the delimiter between a name and value 110 | ConfigBase *valueSeparator(char vSep) { 111 | valueDelimiter = vSep; 112 | return this; 113 | } 114 | }; 115 | 116 | /// the default Config is the INI file format 117 | using ConfigINI = ConfigBase; 118 | 119 | /// ConfigTOML generates a TOML compliant output 120 | class ConfigTOML : public ConfigINI { 121 | 122 | public: 123 | ConfigTOML() { 124 | commentChar = '#'; 125 | arrayStart = '['; 126 | arrayEnd = ']'; 127 | arraySeparator = ','; 128 | valueDelimiter = '='; 129 | } 130 | }; 131 | } // namespace CLI 132 | -------------------------------------------------------------------------------- /galileo.cc: -------------------------------------------------------------------------------- 1 | #include "bits.hh" 2 | #include "galileo.hh" 3 | 4 | bool getTOWFromInav(std::basic_string_view inav, uint32_t *satTOW, uint16_t *wn) 5 | { 6 | unsigned int wtype = getbitu(&inav[0], 0, 6); 7 | if(wtype==0) { 8 | if(getbitu(&inav[0], 6,2) == 2) { 9 | *wn = getbitu(&inav[0], 96, 12); 10 | *satTOW = getbitu(&inav[0], 108, 20); 11 | return true; 12 | } 13 | } 14 | else if(wtype==5) { 15 | *wn = getbitu(&inav[0], 73, 12); 16 | *satTOW = getbitu(&inav[0], 85, 20); 17 | return true; 18 | } 19 | else if(wtype==6) { 20 | // !! NO WN!! 21 | *satTOW=getbitu(&inav[0], 105, 20); 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | int GalileoMessage::parseFnav(std::basic_string_view page) 29 | { 30 | const uint8_t* ptr = &page[0]; 31 | int offset=0; 32 | auto gbum=[&ptr, &offset](int bits) { 33 | unsigned int ret = getbitu(ptr, offset, bits); 34 | offset += bits; 35 | return ret; 36 | }; 37 | 38 | auto gbsm=[&ptr, &offset](int bits) { 39 | int ret = getbits(ptr, offset, bits); 40 | offset += bits; 41 | return ret; 42 | }; 43 | 44 | wtype = gbum(6); 45 | if(wtype == 1) { 46 | /*int sv = */ (void)gbum(6); 47 | iodnav = gbum(10); 48 | t0c = gbum(14); 49 | af0 = gbsm(31); 50 | af1 = gbsm(21); 51 | af2 = gbsm(6); 52 | sisa = gbum(8); 53 | ai0 = gbum(11); 54 | ai1 = gbsm(11); 55 | ai2 = gbsm(14); 56 | sf1 = gbum(1); 57 | sf2 = gbum(1); 58 | sf3 = gbum(1); 59 | sf4 = gbum(1); 60 | sf5 = gbum(1); 61 | BGDE1E5a = gbsm(10); 62 | e5ahs = gbum(2); 63 | wn = gbum(12); 64 | tow = gbum(20); 65 | e5advs=gbum(1); 66 | } 67 | else if(wtype==2) { 68 | iodnav = gbum(10); 69 | m0 = gbsm(32); 70 | omegadot = gbsm(24); 71 | e = gbum(32); 72 | sqrtA = gbum(32); 73 | omega0 = gbsm(32); 74 | idot = gbsm(14); 75 | wn = gbum(12); 76 | tow = gbum(20); 77 | } 78 | else if(wtype == 3) { 79 | iodnav = gbum(10); 80 | i0 = gbsm(32); 81 | omega = gbsm(32); 82 | deltan = gbsm(16); 83 | cuc = gbsm(16); 84 | cus = gbsm(16); 85 | crc =gbsm(16); 86 | crs = gbsm(16); 87 | t0e = gbum(14); 88 | wn = gbum(12); 89 | tow = gbum(20); 90 | } 91 | else if(wtype == 4) { 92 | iodnav = gbum(10); 93 | cic = gbsm(16); 94 | cis = gbsm(16); 95 | 96 | a0 = gbsm(32); 97 | a1 = gbsm(24); 98 | 99 | dtLS= gbsm(8); 100 | t0t = gbum(8); 101 | wn0t = gbum(8); 102 | wnLSF = gbum(8); 103 | 104 | dn = gbum(3); 105 | dtLSF = gbsm(8); 106 | 107 | t0g = gbum(8); 108 | a0g = gbsm(16); 109 | a1g = gbsm(12); 110 | wn0g = gbum(6); 111 | tow = gbum(20); 112 | } 113 | else if(wtype == 5) { // almanac1, containing 1.5 satellites 114 | ioda = gbum(4); 115 | alma1.wnalmanac = gbum(2); 116 | alma1.t0almanac = gbum(10); 117 | alma1.svid = gbum(6); 118 | alma1.deltaSqrtA = gbsm(13); 119 | alma1.e = gbum(11); 120 | alma1.omega = gbum(16); 121 | alma1.deltai = gbum(11); 122 | alma1.Omega0 = gbum(16); 123 | alma1.Omegadot = gbum(11); 124 | alma1.M0 = gbum(16); 125 | alma1.af0 = gbsm(16); 126 | alma1.af1 = gbsm(13); 127 | alma1.e5ahs = gbum(2); 128 | 129 | alma2.svid = gbum(6); 130 | alma2.deltaSqrtA = gbsm(13); 131 | alma2.e = gbum(11); 132 | alma2.omega = gbum(16); 133 | alma2.deltai = gbum(11); 134 | alma2.Omega0 = gbum(4); 135 | // omega02 .. is partial 136 | } 137 | else if(wtype == 6) { // almanac2, containing 1.5 satellites 138 | ioda = gbum(4); 139 | alma2.Omega0 = gbum(12); // PARTIAL, does not really work like this 140 | alma2.Omegadot = gbum(11); 141 | alma2.M0 = gbum(16); 142 | alma2.af0 = gbsm(16); 143 | alma2.af1 = gbsm(13); 144 | alma2.e5ahs = gbum(2); 145 | 146 | alma3.svid = gbum(6); 147 | alma3.deltaSqrtA = gbsm(13); 148 | alma3.e = gbum(11); 149 | alma3.omega = gbum(16); 150 | alma3.deltai = gbum(11); 151 | alma3.Omega0 = gbum(4); 152 | alma3.M0 = gbum(16); 153 | alma3.af0 = gbsm(16); 154 | alma3.af1 = gbsm(13); 155 | alma3.e5ahs = gbum(2); 156 | } 157 | 158 | 159 | 160 | return wtype; 161 | } 162 | 163 | -------------------------------------------------------------------------------- /glonass.cc: -------------------------------------------------------------------------------- 1 | #include "glonass.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | using std::cout; 7 | using std::endl; 8 | 9 | static const double ae = 6378.136; // km // IERS: 6378136.6 10 | static const double mu = 398.6004418E3; // km3/s2 // IERS: 3.986004418 11 | static const double J2 = 1082625.75E-9; // IERS: 1.0826359 12 | static const double oe = 7.2921151467E-5; // rad/s // IERS: 7.292115 13 | 14 | // this strips out spare bits + parity, and leaves 10 clean 24 bit words 15 | std::basic_string getGlonassMessage(std::basic_string_view payload) 16 | { 17 | uint8_t buffer[4*4]; 18 | 19 | for(int w = 0 ; w < 4; ++w) { 20 | setbitu(buffer, 32*w, 32, getbitu(&payload[0], w*32, 32)); 21 | } 22 | 23 | return std::basic_string(buffer, 16); 24 | 25 | } 26 | 27 | // this does NOT turn it into unix time!! 28 | uint32_t GlonassMessage::getGloTime() const 29 | { 30 | struct tm tm; 31 | memset(&tm, 0, sizeof(tm)); 32 | tm.tm_year = 96+4*(n4 -1); 33 | tm.tm_mon = 0; 34 | tm.tm_mday = 1; 35 | tm.tm_hour = -3; 36 | tm.tm_min = 0; 37 | tm.tm_sec = 0; 38 | time_t t = timegm(&tm); 39 | // cout<<" n4 "<<(int)gm.n4<<" start-of-4y "<< humanTime(t) <<" NT "<<(int)gm.NT; 40 | 41 | t += 86400 * (NT-1); 42 | 43 | t += 3600 * (hour) + 60 * minute + seconds; 44 | return t - 820368000; // this starts GLONASS time at 31st of december 1995, 00:00 UTC 45 | } 46 | 47 | // the 'referencetime' must reflect the time when the frame with Tb was received 48 | uint32_t getGlonassT0e(time_t referencetime, int Tb) 49 | { 50 | time_t now = referencetime + 3*3600; // this is so we get the Moscow day 51 | struct tm tm; 52 | memset(&tm, 0, sizeof(tm)); 53 | gmtime_r(&now, &tm); 54 | tm.tm_hour = (Tb/4.0); 55 | tm.tm_min = (Tb % 4)*15; 56 | tm.tm_sec = 0; 57 | return timegm(&tm)-3*3600; // and back to UTC 58 | } 59 | 60 | // y[0] .. y[2] --> X, Y, Z 61 | // y[3] .. y[5] --> Vx, Vy, Vz 62 | static void ff (const double A[3], const double y[6], double f[6]) 63 | { 64 | double X = y[0]; 65 | double Y = y[1]; 66 | double Z = y[2]; 67 | double Vx = y[3]; 68 | double Vy = y[4]; 69 | double Vz = y[5]; 70 | double R = sqrt (X*X + Y*Y + Z*Z); 71 | double C0 = pow(R, -3); 72 | double C1 = 1.5*J2*ae*ae*pow(R, -5); 73 | double C2 = 5*pow(Z/R, 2); 74 | double CXY = C0 + C1 * (1 - C2); 75 | double CZ = C0 + C1 * (3 - C2); 76 | f[0] = Vx; 77 | f[1] = Vy; 78 | f[2] = Vz; 79 | f[3] = (- mu*CXY + oe*oe)*X + 2*oe*Vy + A[0]; 80 | f[4] = (- mu*CXY + oe*oe)*Y - 2*oe*Vx + A[1]; 81 | f[5] = - mu*CZ*Z + A[2]; 82 | } 83 | 84 | static void rk4step (const double A[3], double y[6], double h) 85 | { 86 | double k1[6], k2[6], k3[6], k4[6], z[6]; 87 | 88 | ff (A, y, k1); 89 | for (int j = 0; j < 6; j ++) 90 | z[j] = y[j] + 0.5*h*k1[j]; 91 | 92 | ff (A, z, k2); 93 | for (int j = 0; j < 6; j ++) 94 | z[j] = y[j] + 0.5*h*k2[j]; 95 | 96 | ff (A, z, k3); 97 | for (int j = 0; j < 6; j ++) 98 | z[j] = y[j] + h*k3[j]; 99 | 100 | ff (A, z, k4); 101 | for (int j = 0; j < 6; j ++) 102 | y[j] = y[j] + h * (k1[j] + 2*(k2[j] + k3[j]) + k4[j]) / 6; 103 | } 104 | 105 | 106 | using Clock = std::chrono::steady_clock; 107 | 108 | static double passedMsec(const Clock::time_point& then, const Clock::time_point& now) 109 | { 110 | return std::chrono::duration_cast(now - then).count()/1000.0; 111 | } 112 | 113 | #if 0 114 | static double passedMsec(const Clock::time_point& then) 115 | { 116 | return passedMsec(then, Clock::now()); 117 | } 118 | #endif 119 | 120 | double getCoordinates(double tow, const GlonassMessage& eph, Point* p) 121 | { 122 | // auto start = Clock::now(); 123 | 124 | double y0[6] = {ldexp(eph.x, -11), ldexp(eph.y, -11), ldexp(eph.z, -11), 125 | ldexp(eph.dx, -20), ldexp(eph.dy, -20), ldexp(eph.dz, -20)}; 126 | double A[3] = {ldexp(eph.ddx, -30), ldexp(eph.ddy, -30), ldexp(eph.ddz, -30)}; 127 | 128 | // These all fake 129 | uint32_t glotime = eph.getGloTime(); 130 | uint32_t gloT0e = getGlonassT0e(glotime + 820368000, eph.Tb); 131 | uint32_t ephtow = (gloT0e - 820368000) % (7*86400); 132 | 133 | double DT = tow - ephtow; 134 | int n = abs (DT / 100) + 1; // integrate in roughly 100 second steps 135 | double h = DT / n; 136 | for (int j = 0; j < n; j ++) 137 | rk4step (A, y0, h); 138 | 139 | *p = Point (1E3*y0[0], 1E3*y0[1], 1E3*y0 [2]); 140 | // static double total=0; 141 | // cout<<"Took: "<<(total+=passedMsec(start))<<" ms" < 2 | #include "fmt/format.h" 3 | #include "fmt/printf.h" 4 | #include 5 | #include 6 | #include 7 | #include "ubx.hh" 8 | using namespace std; 9 | /* TRK-MEAS 10 | 11 | 2 U1 Number of channels tracked 12 | ? 13 | 8 U4 Poweron counter milliseconds 14 | 12 U4 "session time counter" milliseconds 15 | 16 | 104 + n*56 + 0 U1 Channel (?) 17 | 104 + n*56 + 1 U1 Quality indicator 0 idle, 1 search, 2 acquired, 3 unsusable, 4 code lock, 5/6/7 carrier lock) 18 | + 2 ? 19 | + 3 ? 20 | 104 + n*56 + 4 U1 GNSS (?) 21 | 104 + n*56 + 5 U1 Satellite ID 22 | ? 23 | 104 + n*56 + 7 U1 GLONASS Frequency 24 | 104 + n*56 + 8 U1 Tracking status 25 | ? 26 | + 16 U1 Code lock "count" 27 | + 17 U1 Phase lock "count" 28 | ? 29 | + 20 U2 256*dB 30 | ? 31 | 104 + n*56 + 24 I8 Transmission time, in units of 2^-32 milliseconds (?) // ts=I8(p+24)*P2_32/1000.0; 32 | 33 | + 32 I8 "ADR" in units of 2^-32, plus 0.5 if 'bit' 0x40 is set in tracking status 34 | + 40 I4 Doppler in units of 2^-10 deca-hertz (?) // I4(p+40)*P2_10*10.0; 35 | + 44 ? 36 | .. 37 | + 56 38 | */ 39 | 40 | extern "C" { 41 | int ubxdecrypt(const unsigned char crypto[16], unsigned char plaintext[16]) __attribute__((weak)); 42 | int ubxdecrypt(const unsigned char crypto[16], unsigned char plaintext[16]) 43 | { 44 | return -1; 45 | } 46 | } 47 | 48 | vector parseTrkMeas(std::basic_string_view payload) 49 | { 50 | uint8_t plainchunk[16]; 51 | std::basic_string plaintext; 52 | /* 53 | cerr<<"payload.size(): "< ret; 60 | for(unsigned int n=4 ; n < payload.size(); n+=16) { 61 | if(ubxdecrypt(&payload[n], plainchunk) < 0) 62 | return ret; 63 | plaintext.append(plainchunk, plainchunk+16); 64 | } 65 | /* 66 | int msgsize = (payload[2]+256*payload[3]) - 6; 67 | cerr<<"msgsize: "< maxtr) 76 | maxtr = tr; 77 | } 78 | 79 | 80 | 81 | for(int n = 0 ; n < plaintext[8]; ++n) { 82 | int offset = 110+n*56; 83 | int32_t rdoppler; 84 | int64_t tr; 85 | memcpy(&rdoppler, &plaintext[offset+40], 4); 86 | 87 | memcpy(&tr, &plaintext[offset+24], 8); 88 | 89 | int gnssid = plaintext[offset+4]; 90 | int sv = plaintext[offset+5]; 91 | double doppler = ldexp(1.0*rdoppler,-10)*10; 92 | int plcount = (unsigned int)plaintext[offset+17]; 93 | /* 94 | int trkstat = plaintext[offset+8]; 95 | cerr<<" gnssid " << gnssid <<" sv "<< sv; 96 | cerr<<" qi "<<(unsigned int)plaintext[offset+1] <<" c "<<(unsigned int)plaintext[offset]; 97 | cerr<<" ? "<<(unsigned int)plaintext[offset+2] << " ? " <<(unsigned int)plaintext[offset+3]; 98 | cerr<<" trkstat " << trkstat<<" db " << (unsigned int) plaintext[offset+21] << " doppler "<< 99 | doppler << "Hz tr " << tr<< " cl-count " << (unsigned int)plaintext[offset+16]<< " pl-count " << plcount; 100 | cerr<<" tau " << ldexp((tr - maxtr), -32)/1000.0; 101 | cerr<<" pr " << 3e5*ldexp((tr - maxtr), -32)/1000.0; 102 | cerr< 2) { 105 | struct TrkSatStat tss; 106 | tss.sv = sv; 107 | tss.gnss = gnssid; 108 | tss.dopplerHz = doppler; 109 | tss.tr = tr; 110 | ret.push_back(tss); 111 | } 112 | 113 | } 114 | /* 115 | for(int n = 0 ; n < plaintext[8]; ++n) { 116 | int offset = 110+n*56; 117 | cerr<<" gnssid " << (unsigned int)plaintext[offset+4]<<" sv "<<(unsigned int) plaintext[offset+5]< 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace std; 10 | 11 | 12 | std::string getPath(std::string_view root, time_t s, uint64_t sourceid, bool create) 13 | { 14 | auto comps = getPathComponents(root, s, sourceid); 15 | std::string path; 16 | for(unsigned int pos = 0; pos < comps.size() - 1 ; ++pos) { 17 | path += comps[pos] +"/"; 18 | if(create) 19 | mkdir(path.c_str(), 0770); 20 | } 21 | path += comps[comps.size()-1]+".gnss"; 22 | return path; 23 | } 24 | 25 | 26 | vector getPathComponents(std::string_view root, time_t s, uint64_t sourceid) 27 | { 28 | // path: source/year/month/day/hour.pb 29 | vector ret; 30 | ret.push_back((string)root); 31 | ret.push_back(fmt::sprintf("%08x", sourceid)); 32 | 33 | struct tm tm; 34 | gmtime_r(&s, &tm); 35 | 36 | ret.push_back(to_string(tm.tm_year+1900)); 37 | ret.push_back(to_string(tm.tm_mon+1)); 38 | ret.push_back(to_string(tm.tm_mday)); 39 | ret.push_back(to_string(tm.tm_hour)+".pb"); 40 | return ret; 41 | } 42 | 43 | bool getNMM(int fd, NavMonMessage& nmm, uint32_t& offset) 44 | { 45 | char bert[4]; 46 | if(read(fd, bert, 4) != 4 || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') { 47 | return false; 48 | } 49 | 50 | uint16_t len; 51 | if(read(fd, &len, 2) != 2) 52 | return false; 53 | len = htons(len); 54 | char buffer[len]; 55 | if(read(fd, buffer, len) != len) 56 | return false; 57 | 58 | nmm.ParseFromString(string(buffer, len)); 59 | offset += 4 + 2 + len; 60 | return true; 61 | } 62 | 63 | bool getNMM(FILE* fp, NavMonMessage& nmm, uint32_t& offset) 64 | { 65 | char bert[4]; 66 | if(fread(bert, 1, 4, fp) != 4 || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') { 67 | return false; 68 | } 69 | 70 | uint16_t len; 71 | if(fread(&len, 1, 2, fp) != 2) 72 | return false; 73 | len = htons(len); 74 | char buffer[len]; 75 | if(fread(buffer, 1, len, fp) != len) 76 | return false; 77 | 78 | nmm.ParseFromString(string(buffer, len)); 79 | offset += 4 + 2 + len; 80 | return true; 81 | } 82 | 83 | // if you rely on offset, this function is atomic wrt partial reads, 84 | // the offset will only get updated on a whole message 85 | bool getRawNMM(int fd, timespec& t, string& raw, uint32_t& offset) 86 | { 87 | char bert[4]; 88 | int res; 89 | if((res=read(fd, bert, 4)) != 4 || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') { 90 | if(res != 4) { 91 | return false; 92 | } 93 | 94 | for(int s=0;; ++s ) { 95 | cerr<<"Skipping character hunting for good magic.. "< 3 | #include 4 | #include 5 | #include 6 | #include "navmon.hh" 7 | 8 | using std::cout; 9 | using std::endl; 10 | using std::cerr; 11 | 12 | 13 | ZStdCompressor::ZStdCompressor(const std::function& emit, int compressionLevel): d_emit(emit) 14 | { 15 | d_z=ZSTD_createCStream(); 16 | ZSTD_initCStream(d_z, compressionLevel); 17 | d_outcapacity=ZSTD_CStreamOutSize(); 18 | d_out.dst = malloc(d_outcapacity); // ???? 19 | d_out.pos=0; 20 | d_out.size=d_outcapacity; 21 | } 22 | 23 | int ZStdCompressor::maxCompressionLevel() 24 | { 25 | return ZSTD_maxCLevel(); 26 | } 27 | 28 | ZStdCompressor::~ZStdCompressor() 29 | { 30 | for(;;) { 31 | auto res = ZSTD_endStream(d_z, &d_out); 32 | d_outputBytes += d_out.pos; 33 | try { 34 | d_emit((const char*)d_out.dst, d_out.pos); 35 | } 36 | catch(...){} 37 | // cout<<"res: "< src(inputcapacity); 144 | input.src = &src[0]; 145 | 146 | auto outputcapacity = ZSTD_DStreamOutSize(); 147 | std::vector dst(outputcapacity); 148 | output.dst = &dst[0]; 149 | 150 | for(;;) { 151 | input.pos=0; 152 | int ret = read(d_sourcefd, (char*)input.src, inputcapacity); 153 | if(ret <= 0) { 154 | // cerr<<"Got EOF on input fd "< 2 | #include "rinex.hh" 3 | #include "navmon.hh" 4 | #include 5 | #include "fmt/format.h" 6 | #include "fmt/printf.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "ephemeris.hh" 13 | #include 14 | 15 | using namespace std; 16 | 17 | struct svstat 18 | { 19 | int health{0}; 20 | int napa{0}; 21 | int stale{0}; 22 | }; 23 | 24 | template 25 | struct HanderOuter 26 | { 27 | HanderOuter(const std::deque& things) : d_things(things) 28 | {} 29 | 30 | 31 | bool getOne(T& str) 32 | { 33 | std::lock_guard mut(d_mut); 34 | if(d_things.empty()) 35 | return false; 36 | str = d_things.front(); 37 | d_things.pop_front(); 38 | return true; 39 | } 40 | std::deque d_things; 41 | 42 | std::mutex d_mut; 43 | 44 | }; 45 | 46 | typedef map> stat_t; 47 | 48 | auto worker(HanderOuter* ho) 49 | { 50 | std::string file; 51 | auto stat = std::make_unique(); 52 | while(ho->getOne(file)) { 53 | try { 54 | RINEXReader rr(file); 55 | RINEXEntry e; 56 | 57 | while(rr.get(e)) { 58 | if(e.gnss != 2 || e.sv == 14 || e.sv == 18) 59 | continue; 60 | SatID sid{(uint32_t)e.gnss, (uint32_t)e.sv, 0}; 61 | auto& h=(*stat)[sid][e.t/3600]; 62 | 63 | if(e.sisa < 0) { 64 | h.napa++; 65 | } 66 | 67 | if(e.health) { 68 | h.health++; 69 | } 70 | 71 | 72 | if(fabs(ephAge(e.tow, e.toe)) > 4*3600) { 73 | // cout << humanTime(e.t)<<": "< files; 91 | while(getline(filefile, fname)) 92 | files.push_back(fname); 93 | 94 | HanderOuter ho(files); 95 | 96 | vector>> futs; 97 | for(int n=0; n < 5; ++n) 98 | futs.push_back(std::async(worker, &ho)); 99 | 100 | stat_t stat; 101 | for(auto& f : futs) { 102 | auto s = f.get(); 103 | for(const auto& e : *s) { 104 | for(const auto& h : e.second) { 105 | auto& u = stat[e.first][h.first]; 106 | u.napa += h.second.napa; 107 | u.health += h.second.health; 108 | u.stale += h.second.stale; 109 | } 110 | } 111 | } 112 | 113 | int totnapa{0}, tothours{0}, totstale{0}, totissue{0}; 114 | int tothealth{0}; 115 | ofstream dump("dump"); 116 | set hours; 117 | for(const auto& sv : stat) { 118 | cout<first; 120 | time_t stop = 3600*sv.second.rbegin()->first; 121 | cout< phours = hours; 160 | for(const auto& h : sv.second) { 161 | phours.erase(h.first); 162 | } 163 | for(const auto& missing : phours) { 164 | misnum++; 165 | cout<<" "< /etc/apt/sources.list.d/galmon.list 14 | echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list 15 | ``` 16 | 17 | Install the file used to ensure the software is verified. 18 | ```sh 19 | apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc 20 | ``` 21 | 22 | Update your package list and install galmon. Then create a configuration file and start the daemon. 23 | If you have a typical device using the onboard USB at /dev/ttyACM0, drop the directory element 24 | and refer to ttyACM0 in both the default variable file and the unit name. 25 | ```sh 26 | apt-get update && apt-get install -y galmon 27 | cp /etc/default/galmon /etc/default/ubxtool-ttyACM0 28 | systemctl enable --now ubxtool@ttyACM0 29 | ``` 30 | 31 | Alternate or multiple devices just repeats that, updating the device name: 32 | ```sh 33 | cp /etc/default/galmon /etc/default/ubxtool-ttyACM3 34 | systemctl enable --now ubxtool@ttyACM3 35 | ``` 36 | Both the ubxtool-ttyXYZn file and /dev/ttyXYZn device must exist for the unit file conditions to pass. 37 | 38 | ### Automatic Updates 39 | 40 | Armbian and Raspbian have apt-daily timers enabled by default. 41 | However, most configurations for unattended installs require customization. 42 | 43 | A simple timer is included that will apply any galmon upgrades every three days: 44 | ```sh 45 | systemctl enable --now galmon-upgrade.timer 46 | ``` 47 | 48 | You can perform an immediate update by hand: 49 | ```sh 50 | apt-get update && apt-get -y install galmon && systemctl restart ubxtool@* 51 | ``` 52 | 53 | ## Reference Information 54 | 55 | You can stop reading here if your interest was limited to installing a compiled package. 56 | 57 | ### One time steps for bootstrapping package build on a fresh git repo 58 | 59 | Run debmake in the source directory. It tries to autocreate 90% of everything you need in the debian folder. 60 | Key files: copyright, changelog, control, and anything else that looks interesting to cat. Once they exist, we're done. 61 | Refer to the manual's [tutorial](https://www.debian.org/doc/manuals/debmake-doc/ch04.en.html). 62 | 63 | ### One time steps for creating package-specific files and scripts 64 | 65 | Inside the debian directory are files that begin with galmon, the name of the package as defined in the control file. 66 | - galmon.postinst: this script is run after installation to verify a system account exists. 67 | - galmon.default: this file is installed as /etc/default/galmon 68 | - galmon.ubxtool@.service: this unit file uses %i as a reference to the device for computers with multiple inputs. 69 | 70 | ### How to build the package locally 71 | 72 | In short you need to set some variables, refer to profile-debuild.sh in the debian/ directory. 73 | After that, use debuild to install the package. Signing of the end result may fail and creating 74 | GPG key pairs is beyond the scope of this document but just make sure you match the email in the changelog. 75 | ```sh 76 | apt-get install -y build-essential devscripts lintian diffutils patch patchutils 77 | git clone $flags galmon.git ; cd galmon 78 | ./make-githash.h 79 | # create and source variables in /etc/profile.d/debuild.sh 80 | debuild 81 | dpkg -i ../*.deb 82 | ``` 83 | 84 | ### Future maintenance considerations 85 | 86 | The githash.h files cannot change after the debuild process has started. 87 | For now, the Makefile used by debuild does not run that script and 88 | the files must be created before starting the debuild process. 89 | 90 | ### Real World Build Results in January 2020 91 | 92 | Avoid compiling on arm6 computers, it is slow. The arm6 used in cheap Raspberry Pi models is an expensive model to support 93 | relative to the much faster arm7 and arm8 computers available. Compiling approaches 90 minutes at O3. 94 | 95 | The arm7 and arm8 both compile in 20 to 30 minutes at -j1 -O3 but the 64 bit arm8 has approximately 96 | double the RAM requirements during compilation. To avoid swapping, increasing compile time 150%, 97 | use hardware with at least 1GB of RAM. The NanoPi Neo2 and NanoPi ZeroPi models with 512MB of RAM 98 | are perfect clients, but the OrangePi PCs with 1GB of RAM and the Allwinner H3 or H5 are 99 | better suited for smooth building. For comparison, a VM on a low-end AMD Ryzen 3 2200G builds the package at -j1 in about two minutes. 100 | 101 | These are fast multi-core computers but we turn off parallel compiles because of limited RAM. 102 | Limiting optimizations to -O0 cuts the compile time in half approximately. 103 | 104 | ### Why do this? 105 | 106 | Convenience, uniformity, and scalability: 107 | Hand-compiling software is fun, but vendor package management solutions 108 | exist to give us reliable unattended installations for free. 109 | 110 | ### Signing key 111 | 112 | GPG Public Key [86E7F51C04FBAAB0](debian/86E7F51C04FBAAB0.asc) 113 | -------------------------------------------------------------------------------- /coverage.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "galileo.hh" 3 | #include "minivec.hh" 4 | #include "navmon.hh" 5 | #include "navparse.hh" 6 | #include "ephemeris.hh" 7 | #include "fmt/format.h" 8 | #include "fmt/printf.h" 9 | #include 10 | 11 | #include 12 | using Eigen::MatrixXd; 13 | 14 | 15 | using namespace std; 16 | 17 | xDOP getDOP(Point& us, vector sats) 18 | { 19 | xDOP ret; 20 | if(sats.size() < 4) { 21 | return ret; 22 | } 23 | MatrixXd G(sats.size(), 4); // 4 columns 24 | 25 | // (x1 - x)/R1 (y1 -y)/R1 (z1 - z)/R1 -1 26 | 27 | for(size_t n = 0 ; n < sats.size(); ++n) { 28 | const auto& s = sats[n]; 29 | auto R = Vector(us, s).length(); 30 | 31 | G(n, 0) = (s.x - us.x)/R; 32 | G(n, 1) = (s.y - us.y)/R; 33 | G(n, 2) = (s.z - us.z)/R; 34 | G(n, 3) = -1; 35 | } 36 | 37 | // cout<<"Matrix: "<=0 && Qenu(1,1) >=0) 65 | ret.hdop = sqrt(Qenu(0,0) + Qenu(1,1)); 66 | if(Qenu(2,2)>=0) 67 | ret.vdop = sqrt(Qenu(2,2)); 68 | return ret; 69 | }; 70 | 71 | // in covmap: 72 | // 0 73 | // lon, 74 | // 1 2 3 75 | // numsats5, numsats10, numsats20 76 | // 4 5 6 77 | // pdop5, pdop10, pdop20 78 | // hdop5, hdop10, hdop10 79 | // vdop5, vdop10, vdop20 80 | covmap_t emitCoverage(const vector& sats) 81 | { 82 | covmap_t ret; 83 | // ofstream cmap("covmap.csv"); 84 | // cmap<<"latitude longitude count5 count10 count20"< -90; latitude-=2) { // north-south 87 | double phi = M_PI* latitude / 180; 88 | double longsteps = 1 + 360.0 * cos(phi); 89 | double step = 4*180.0 / longsteps; 90 | // this does sorta equi-distanced measurements 91 | vector> latvect; 92 | for(double longitude = -180; longitude < 180; longitude += step) { // east - west 93 | Point p; 94 | // phi = latitude, lambda = longitude 95 | 96 | double lambda = M_PI* longitude / 180; 97 | p.x = R * cos(phi) * cos(lambda); 98 | p.y = R * cos(phi) * sin(lambda); 99 | p.z = R * sin(phi); 100 | 101 | if(longitude == -180) { 102 | // auto longlat = getLongLat(p.x, p.y, p.z); 103 | // cout< satposs5, satposs10, satposs20; 109 | for(const auto& s : sats) { 110 | // double getElevationDeg(const Point& sat, const Point& our); 111 | double elev = getElevationDeg(s, p); 112 | if(elev > 5.0) { 113 | satposs5.push_back(s); 114 | numsats5++; 115 | } 116 | if(elev > 10.0) { 117 | satposs10.push_back(s); 118 | numsats10++; 119 | } 120 | if(elev > 20.0) { 121 | satposs20.push_back(s); 122 | numsats20++; 123 | } 124 | } 125 | latvect.push_back(make_tuple(longitude, 126 | numsats5, numsats10, numsats20, 127 | getDOP(p, satposs5).pdop, 128 | getDOP(p, satposs10).pdop, 129 | getDOP(p, satposs20).pdop, 130 | getDOP(p, satposs5).hdop, 131 | getDOP(p, satposs10).hdop, 132 | getDOP(p, satposs20).hdop, 133 | getDOP(p, satposs5).vdop, 134 | getDOP(p, satposs10).vdop, 135 | getDOP(p, satposs20).vdop 136 | 137 | )); 138 | // cmap << longitude <<" " < 3 | #include 4 | #include 5 | extern "C" { 6 | #include 7 | } 8 | using namespace std; 9 | 10 | RSCodec::RSCodec(const std::vector& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad, unsigned int bits) 11 | : d_N((1<< (bits)) - pad -1), 12 | d_K((1<< (bits)) - pad - 1 - nroots), 13 | d_nroots(nroots), 14 | d_bits(bits) 15 | { 16 | if(d_bits > 8) 17 | throw std::runtime_error("This encoder supports 8 bits at most"); 18 | 19 | for(const auto& r : roots) 20 | d_gfpoly |= (1< d_K) 31 | throw std::runtime_error("Can't encode message longer than "+std::to_string(d_K)+" bytes"); 32 | msg.append(d_K - msg.size(), 0); 33 | 34 | // void encode_rs_char(void *rs,unsigned char *data, 35 | // unsigned char *parity); 36 | uint8_t parity[d_nroots]; 37 | encode_rs_char(d_rs, (uint8_t*)msg.c_str(), parity); 38 | msg.append((char*)&parity[0], (char*)&parity[d_nroots]); 39 | } 40 | 41 | int RSCodec::decode(const std::string& in, std::string& out, vector* corrs) 42 | { 43 | // int decode_rs_char(void *rs,unsigned char *data,int *eras_pos, 44 | // int no_eras); 45 | 46 | unsigned char data[in.length()]; 47 | memcpy(data, in.c_str(), in.length()); 48 | vector eras_pos; 49 | int eras_no=0; 50 | if(corrs) { 51 | for(const auto& c : *corrs) { 52 | eras_pos.push_back(c); 53 | eras_no++; 54 | } 55 | } 56 | eras_pos.resize(d_nroots); 57 | int ret = decode_rs_char(d_rs, data, &eras_pos[0], eras_no); 58 | /* 59 | 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 60 | 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 61 | least nroots elements to prevent a possible buffer overflow. 62 | */ 63 | if(ret < 0) 64 | throw std::runtime_error("Could not correct message"); 65 | if(corrs) 66 | corrs->clear(); 67 | if(ret && corrs) { 68 | for(int n=0; n < ret; ++n) 69 | corrs->push_back(eras_pos.at(n)); 70 | } 71 | 72 | out.assign((char*) data, (char*)data + d_N); 73 | return ret; 74 | } 75 | 76 | RSCodec::~RSCodec() 77 | { 78 | if(d_rs) 79 | free_rs_char(d_rs); 80 | } 81 | 82 | //// 83 | 84 | RSCodecInt::RSCodecInt(const std::vector& roots, unsigned int fcr, unsigned int prim, unsigned int nroots, unsigned int pad, unsigned int bits) 85 | : d_N((1<< (bits)) - pad -1), 86 | d_K((1<< (bits)) - pad - 1 - nroots), 87 | d_nroots(nroots), 88 | d_bits(bits) 89 | { 90 | if(d_bits > 32) 91 | throw std::runtime_error("This encoder supports 32 bits at most"); 92 | 93 | for(const auto& r : roots) 94 | d_gfpoly |= (1<& msg) 103 | { 104 | if(msg.size() > d_K) 105 | throw std::runtime_error("Can't encode message longer than "+std::to_string(d_K)+" bytes"); 106 | msg.resize(d_K); 107 | 108 | vector parity(d_nroots); 109 | 110 | encode_rs_int(d_rs, (int*)&msg[0], (int*)&parity[0]); 111 | for(const auto& i : parity) 112 | msg.push_back(i); 113 | } 114 | 115 | int RSCodecInt::decode(const std::vector& in, std::vector& out, vector* corrs) 116 | { 117 | // int decode_rs_char(void *rs,unsigned char *data,int *eras_pos, 118 | // int no_eras); 119 | 120 | vector data = in; 121 | vector eras_pos; 122 | int eras_no=0; 123 | if(corrs) { 124 | for(const auto& c : *corrs) { 125 | eras_pos.push_back(c); 126 | eras_no++; 127 | } 128 | } 129 | eras_pos.resize(d_nroots); 130 | int ret = decode_rs_int(d_rs, (int*)&data[0], (int*)&eras_pos[0], eras_no); 131 | /* 132 | 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 133 | 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 134 | least nroots elements to prevent a possible buffer overflow. 135 | */ 136 | if(ret < 0) 137 | throw std::runtime_error("Could not correct message"); 138 | if(corrs) 139 | corrs->clear(); 140 | if(ret && corrs) { 141 | for(int n=0; n < ret; ++n) 142 | corrs->push_back(eras_pos[n]); 143 | } 144 | out = data; 145 | out.resize(d_N); 146 | return ret; 147 | } 148 | 149 | RSCodecInt::~RSCodecInt() 150 | { 151 | if(d_rs) 152 | free_rs_int(d_rs); 153 | } 154 | -------------------------------------------------------------------------------- /influxpush.cc: -------------------------------------------------------------------------------- 1 | #include "influxpush.hh" 2 | #include "minicurl.hh" 3 | using namespace std; 4 | 5 | 6 | InfluxPusher::InfluxPusher(std::string_view dbname) : d_dbname(dbname) 7 | { 8 | if(dbname=="null") { 9 | d_mute = true; 10 | cout<<"Not sending data to influxdb"<>& values, double t, std::optional satid) 22 | { 23 | if(d_mute) 24 | return; 25 | 26 | if(t > 2200000000 || t < 0) { 27 | cerr<<"Unable to store item "<gnss)+ +",sv=" +to_string(satid->sv)+",sigid="+to_string(satid->sigid); 38 | } 39 | 40 | buffer+= " "; 41 | bool lefirst=true; 42 | for(const auto& v : values) { 43 | if(!v.first) // trick to null out certain fields 44 | continue; 45 | d_numvalues++; 46 | if(!lefirst) { 47 | buffer +=","; 48 | } 49 | lefirst=false; 50 | buffer += string(v.first) + "="+to_string(v.second); 51 | } 52 | buffer += " " + to_string((uint64_t)(t* 1000000000))+"\n"; 53 | d_nummsmts++; 54 | d_msmtmap[(string)name]++; 55 | queueValue(buffer); 56 | } 57 | 58 | 59 | void InfluxPusher::addValue(const SatID& id, string_view name, const initializer_list>& values, double t, std::optional src, std::optional tag) 60 | { 61 | 62 | vector> tags{{"sv", id.sv}, {"gnssid", id.gnss}, {"sigid", id.sigid}}; 63 | 64 | if(src) { 65 | tags.push_back({*tag, *src}); 66 | } 67 | addValue(tags, name, values, t); 68 | } 69 | 70 | void InfluxPusher::addValue(const vector>& tags, string_view name, const initializer_list>& values, double t) 71 | { 72 | if(d_mute) 73 | return; 74 | 75 | if(t > 2200000000 || t < 0) { 76 | cerr<<"Unable to store item "<(&p.second)) 84 | if(isnan(*ptr)) 85 | return; 86 | } 87 | 88 | string buffer = string(name); 89 | for(const auto& t : tags) { 90 | buffer += ","+t.first + "="; 91 | std::visit([&buffer](auto&& arg) { 92 | using T = std::decay_t; 93 | if constexpr (std::is_same_v) { 94 | // string tags really don't need a " 95 | buffer += arg; 96 | } 97 | else { 98 | // resist the urge to do integer tags, it sucks 99 | buffer += to_string(arg); 100 | } 101 | }, t.second); 102 | } 103 | 104 | buffer+= " "; 105 | bool lefirst=true; 106 | for(const auto& v : values) { 107 | if(!v.first) // trick to null out certain fields 108 | continue; 109 | 110 | d_numvalues++; 111 | if(!lefirst) { 112 | buffer +=","; 113 | } 114 | lefirst=false; 115 | buffer += string(v.first) + "="; 116 | 117 | std::visit([&buffer](auto&& arg) { 118 | using T = std::decay_t; 119 | if constexpr (std::is_same_v) 120 | buffer += "\""+arg+"\""; 121 | else { 122 | buffer += to_string(arg); 123 | if constexpr (!std::is_same_v) 124 | buffer+="i"; 125 | } 126 | }, v.second); 127 | } 128 | buffer += " " + to_string((uint64_t)(t*1000000000))+"\n"; 129 | d_nummsmts++; 130 | d_msmtmap[(string)name]++; 131 | queueValue(buffer); 132 | } 133 | 134 | 135 | void InfluxPusher::checkSend() 136 | { 137 | if(d_buffer.size() > 10000 || (time(0) - d_lastsent) > 10) { 138 | set buffer; 139 | buffer.swap(d_buffer); 140 | // thread t([buffer,this]() { 141 | if(!d_mute) 142 | doSend(buffer); 143 | // }); 144 | // t.detach(); 145 | d_lastsent=time(0); 146 | } 147 | } 148 | 149 | void InfluxPusher::doSend(const set& buffer) 150 | { 151 | MiniCurl mc; 152 | MiniCurl::MiniCurlHeaders mch; 153 | if(!buffer.empty()) { 154 | string newout; 155 | for(const auto& nl: buffer) 156 | newout.append(nl); 157 | 158 | /* 159 | ofstream infl; 160 | infl.open ("infl.txt", std::ofstream::out | std::ofstream::app); 161 | infl << newout; 162 | */ 163 | try { 164 | mc.postURL("http://127.0.0.1:8086/write?db="+d_dbname, newout, mch); 165 | } 166 | catch(std::exception& e) { 167 | if(strstr(e.what(), "retention")) 168 | return; 169 | throw; 170 | } 171 | } 172 | } 173 | 174 | InfluxPusher::~InfluxPusher() 175 | { 176 | if(d_dbname != "null") 177 | doSend(d_buffer); 178 | } 179 | -------------------------------------------------------------------------------- /rtcmtool.cc: -------------------------------------------------------------------------------- 1 | #include "rtcm.hh" 2 | #include "bits.hh" 3 | #include 4 | #include 5 | #include 6 | #include "nmmsender.hh" 7 | #include "CLI/CLI.hpp" 8 | #include "swrappers.hh" 9 | #include "sclasses.hh" 10 | #include "version.hh" 11 | 12 | using namespace std; 13 | 14 | bool RTCMReader::get(RTCMFrame& rf) 15 | { 16 | int c; 17 | bool skipped=false; 18 | while( ((c=fgetc(d_fp)) != -1) && c != 211) { 19 | skipped=true; 20 | cerr<<"."; 21 | continue; 22 | } 23 | if(skipped) 24 | cerr< buf(size); 41 | if((int)fread(&buf[0], 1, size, d_fp) != size) 42 | return false; 43 | 44 | // cout<<"Returning true"< destinations; 119 | 120 | bool doVERSION{false}, doSTDOUT{false}; 121 | CLI::App app(program); 122 | app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address"); 123 | app.add_option("--station", g_srcid, "Station id")->required(); 124 | app.add_flag("--version", doVERSION, "show program version and copyright"); 125 | app.add_flag("--stdout", doSTDOUT, "Emit output to stdout"); 126 | try { 127 | app.parse(argc, argv); 128 | } catch(const CLI::Error &e) { 129 | return app.exit(e); 130 | } 131 | 132 | if(doVERSION) { 133 | showVersion(program, g_gitHash); 134 | exit(0); 135 | } 136 | 137 | 138 | signal(SIGPIPE, SIG_IGN); 139 | NMMSender ns; 140 | ns.d_debug = true; 141 | for(const auto& s : destinations) { 142 | auto res=resolveName(s, true, true); 143 | if(res.empty()) { 144 | cerr<<"Unable to resolve '"<set_contents(rf.payload); 168 | ns.emitNMM(nmm); 169 | 170 | // rm.parse(rf.payload); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/Timer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | // On GCC < 4.8, the following define is often missing. Due to the 10 | // fact that this library only uses sleep_for, this should be safe 11 | #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8 12 | #define _GLIBCXX_USE_NANOSLEEP 13 | #endif 14 | 15 | #include 16 | #include // NOLINT(build/c++11) 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace CLI { 23 | 24 | /// This is a simple timer with pretty printing. Creating the timer starts counting. 25 | class Timer { 26 | protected: 27 | /// This is a typedef to make clocks easier to use 28 | using clock = std::chrono::steady_clock; 29 | 30 | /// This typedef is for points in time 31 | using time_point = std::chrono::time_point; 32 | 33 | /// This is the type of a printing function, you can make your own 34 | using time_print_t = std::function; 35 | 36 | /// This is the title of the timer 37 | std::string title_; 38 | 39 | /// This is the function that is used to format most of the timing message 40 | time_print_t time_print_; 41 | 42 | /// This is the starting point (when the timer was created) 43 | time_point start_; 44 | 45 | /// This is the number of times cycles (print divides by this number) 46 | std::size_t cycles{1}; 47 | 48 | public: 49 | /// Standard print function, this one is set by default 50 | static std::string Simple(std::string title, std::string time) { return title + ": " + time; } 51 | 52 | /// This is a fancy print function with --- headers 53 | static std::string Big(std::string title, std::string time) { 54 | return std::string("-----------------------------------------\n") + "| " + title + " | Time = " + time + "\n" + 55 | "-----------------------------------------"; 56 | } 57 | 58 | public: 59 | /// Standard constructor, can set title and print function 60 | explicit Timer(std::string title = "Timer", time_print_t time_print = Simple) 61 | : title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {} 62 | 63 | /// Time a function by running it multiple times. Target time is the len to target. 64 | std::string time_it(std::function f, double target_time = 1) { 65 | time_point start = start_; 66 | double total_time; 67 | 68 | start_ = clock::now(); 69 | std::size_t n = 0; 70 | do { 71 | f(); 72 | std::chrono::duration elapsed = clock::now() - start_; 73 | total_time = elapsed.count(); 74 | } while(n++ < 100u && total_time < target_time); 75 | 76 | std::string out = make_time_str(total_time / static_cast(n)) + " for " + std::to_string(n) + " tries"; 77 | start_ = start; 78 | return out; 79 | } 80 | 81 | /// This formats the numerical value for the time string 82 | std::string make_time_str() const { 83 | time_point stop = clock::now(); 84 | std::chrono::duration elapsed = stop - start_; 85 | double time = elapsed.count() / static_cast(cycles); 86 | return make_time_str(time); 87 | } 88 | 89 | // LCOV_EXCL_START 90 | /// This prints out a time string from a time 91 | std::string make_time_str(double time) const { 92 | auto print_it = [](double x, std::string unit) { 93 | const unsigned int buffer_length = 50; 94 | std::array buffer; 95 | std::snprintf(buffer.data(), buffer_length, "%.5g", x); 96 | return buffer.data() + std::string(" ") + unit; 97 | }; 98 | 99 | if(time < .000001) 100 | return print_it(time * 1000000000, "ns"); 101 | else if(time < .001) 102 | return print_it(time * 1000000, "us"); 103 | else if(time < 1) 104 | return print_it(time * 1000, "ms"); 105 | else 106 | return print_it(time, "s"); 107 | } 108 | // LCOV_EXCL_STOP 109 | 110 | /// This is the main function, it creates a string 111 | std::string to_string() const { return time_print_(title_, make_time_str()); } 112 | 113 | /// Division sets the number of cycles to divide by (no graphical change) 114 | Timer &operator/(std::size_t val) { 115 | cycles = val; 116 | return *this; 117 | } 118 | }; 119 | 120 | /// This class prints out the time upon destruction 121 | class AutoTimer : public Timer { 122 | public: 123 | /// Reimplementing the constructor is required in GCC 4.7 124 | explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {} 125 | // GCC 4.7 does not support using inheriting constructors. 126 | 127 | /// This destructor prints the string 128 | ~AutoTimer() { std::cout << to_string() << std::endl; } 129 | }; 130 | 131 | } // namespace CLI 132 | 133 | /// This prints out the time if shifted into a std::cout like stream. 134 | inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); } 135 | -------------------------------------------------------------------------------- /influxdb.md: -------------------------------------------------------------------------------- 1 | InfluxDB schema 2 | --------------- 3 | 4 | Influxdb knows about *measurements* which live in a database. Each 5 | measurement has multiple values. Such values can be tagged, and each set of 6 | tags forms a series. 7 | 8 | Our schema is not yet very consistent, but this documentation is a start. 9 | 10 | Measurements 11 | ------------ 12 | These measurements are tagged by gnssid, sv and sigid. Sigid represents the 13 | band on which this data was received. 14 | 15 | 16 | * ephemeris, updated every frame (so, a lot) 17 | * iod-live: current IOD number 18 | * eph-age: age of this ephemeris (distance from t0e) 19 | * sisa, updated every frame 20 | * value: raw Galileo SISA value 21 | * gpsura, updated every frame 22 | * value: raw GPS URA value 23 | * beidouurai, updated every frame 24 | * value: raw BeiDou URAI value (more or less same as GPS) 25 | * FT, GLONASS specific FT value (SISA) 26 | * clock, clock information, updated every frame 27 | * offset\_ns: time offset of this clock wrt GST/GPS time/Beidou time 28 | * t0c: t0 of the clock parameters 29 | * af0, af1, af2: clock polynomial parameters, in Galileo raw units, even for non-galileo SVs 30 | * clock\_jump\_ns 31 | * value: number of nanoseconds jump in clock correction from this 32 | ephemeris to the previous one 33 | * iono, ionospheric parameters 34 | * ai0, ai1, ai2: Galileo NeQuick parameters 35 | * sf1-sf5: The as yet unused 'storm flags' 36 | * galbgd, Galileo Broadcast Group Delay 37 | * BGDE1E5a in raw galileo values 38 | * BGDE1E5b in raw galileo values 39 | * galhealth, Galileo-specific health bits, values according to ICD 40 | * e1bhs 41 | * e5bhs 42 | * e1bdvs 43 | * e5bdvs 44 | * gpshealth, GPS-specific health bits 45 | * value 46 | * beidouhealth, BeiDou-specific health bits 47 | * sath1 48 | * glohealth, GLONASS-specific health bits 49 | * Bn 50 | * glo\_taun\_ns, GLONASS-specific TauN 51 | * value, in nanoseconds 52 | * FT, GLONASS specific FT value 53 | * utcoffset, for GPS, Galileo, Beidou 54 | * a0, in Galileo units 55 | * a1, in Galileo units 56 | * delta, in nanoseconds 57 | * t0t, in seconds 58 | * gpsoffset, for Galileo, BeiDou does not fill this out. GPS doesn't need to 59 | * a0g, in Galileo units 60 | * a1g, in Galileo units 61 | * delta, in nanoseconds 62 | * t0g, in seconds 63 | * eph-disco, statistics about ephemeris transitions 64 | * x,y,z: ECEF coordinates according to new ephemeris at new t0e 65 | * oldx,oldy,oldz: ECEF coordinates according to old ephemeris at new t0e 66 | * iod, oldiod: new and old IOD 67 | 68 | RTCM SSR corrections: 69 | 70 | These measurements are tagged by gnssid, sv 71 | 72 | * rtcm-eph-correction: 73 | * iod: iod this correction corresponds to 74 | * radial: radial error (millimeters) 75 | * along: error along track 76 | * cross: error across track 77 | * dradial: velocity error in millimeters/second 78 | * dalong: along track velocity error 79 | * dcross: across track velocity error 80 | * ssr-iod 81 | * ssr-provider 82 | * ssr-solution 83 | * total-dist 84 | * tow 85 | * udi 86 | * rtcm-clock-correction 87 | * dclock0: in meters 88 | * dclock1: meters/s 89 | * dclock2 90 | * ssr-iod 91 | * ssr-provider 92 | * tow 93 | * udi 94 | 95 | Observer measurements: 96 | 97 | * fix 98 | * x,y,z: ECEF coordinates of receiver 99 | * lat, lon: degrees latitude and longitude 100 | * h: hight above WGS84 ellipsoid 101 | * acc: accuracy (meters) 102 | * groundspeed: m/s 103 | * observer\_details 104 | * clock\_offset\_ns: receiver reported internal clock offset 105 | * clock\_drift\_ns: drift rate, ns/s 106 | * clock\_acc\_ns: clock accuracy (ns) 107 | * freq\_acc\_ps: picosecond/s frequency accuracy 108 | * uptime: uptime in seconds 109 | 110 | 111 | Observer and SV measurements: 112 | 113 | * rfdata 114 | * carrierphase 115 | * doppler (Hz) 116 | * locktime (milliseconds) 117 | * pseudorange (meters) 118 | * prstd (pseudorange standard deviation) 119 | * dostd (doppler standard deviation) 120 | * correlator 121 | * delta\_hz\_corr: Doppler residual against active ephemeris, corrected for 122 | receiver clock drift 123 | * delta\_hz: Doppler residual, uncorrected 124 | * elevation: Elevation of SV over horizon 125 | * prres: pseudorange residual according to receiver 126 | * qi: 0-7, quality indicator according to receiver 127 | * hz: Doppler Hz offset reported by receiver (uncorrected) 128 | * recdata 129 | * db: receiver reported dB (can be non-sensical) 130 | * azi: calculated azimuth for SV from this receiver 131 | * ele: calculated elevation for SV from this receiver 132 | * prres: pseudorange residual according to receiver 133 | * qi: 0-7, quality indicator according to receiver 134 | * used: did the receiver use this SV? 135 | * ubx\_jamming 136 | * noise\_per\_ms: the Ublox noisePerMS field 137 | * agccnt: the Ublox automatic gain correction "count" 138 | * jamind: The Ublox jamming indicator 139 | * flag: The Ublox jamming flag field 140 | 141 | Fed by separate tool: 142 | 143 | SP3 design, tagged by GNSSID, SV: 144 | * sp3\_data: 145 | * x 146 | * y 147 | * z 148 | * clk 149 | * provider 150 | 151 | ephemeris, tagged by GNSSID, SV, SIGID: 152 | * active-ephemeris 153 | * all the raw parameters 154 | 155 | GDOP/PDOP stats? 156 | * covdop 157 | * lat 158 | * lon 159 | * cov5 160 | * cov10 161 | * cov20 162 | * hdop5 163 | * hdop10 164 | * hdop20 165 | 166 | etc 167 | 168 | -------------------------------------------------------------------------------- /debian/86E7F51C04FBAAB0.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBF4kzeMBEAClLiZy741lfdoZHvlCbU1k4o9CglhVAPnSqTo7tH0AkdXmp/sG 4 | Sxk20miLlNNaxTYhHLyWIMhLoR3ogAJSoUsbMo4cpC5owdai06Q2nINTGiD1Vwak 5 | MmSStQl/aG01G1u9Ei/FVSr/K6Nd8u+dvUoU6tFSoXtXXaRB5pkyLOaZXq6UTwhw 6 | tI5f+uH2d8kgkoGNWl/jevn7K5AqCWmklfzYFCeogXemcNMdisrm2RzYzay70Qo+ 7 | 3qKHzQUN5S6Ne2yYKYN1UacGg+38+zhoSNGy8fB2XfCf8mbW2NnnZBdPEmpu1yos 8 | uvjj/oauZlLgkx/ZzK4PzmWqpe+aEiV7Z8lPO3Y2U/cwkU56aMIFQq2DU4FgQUdn 9 | 6J+tLeMPh0sbIfYeOfF5IdFi85PdD4w7C8hmxu7aHl/B/GCnOFYAsSChpf4bpgWB 10 | G1Pne56evHeSNYXQCdE4rjyYGqB/F+EWn/NYcOdKSkOxxE5bL/aU/uqPss5E54nt 11 | z+qsN9ynGNWsnkPQECulM3svS3/nffnmgFSuDZyON4Y8/LFIkPizBiqOtrVEAKdK 12 | CqKz+A2fgSamM5IbJbCSkXWMcU6W0Sz0DzSpMbVf5kKER7WeDoeWr6yUHqAkPyHz 13 | bx/YLWwrAjuuN00/MuIl8XXHWBHsfRqIrqLSBbie5mbOv2MloBEdrvZqvwARAQAB 14 | tDRHYWxtb24gRGViaWFuIFByaW1hcnkgU2lnbmluZyBLZXkgPGRlYmlhbkBnYWxt 15 | b24uZXU+iQJUBBMBCAA+FiEEbe3qslh61wzGNEF6huf1HAT7qrAFAl4kzeMCGwMF 16 | CRaOagAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQhuf1HAT7qrCwxxAAj2fs 17 | 9qcWKJSCPPpDNU6gzjuqFj2fUmvihf0Ki9LbBLMt5pA5q4RJLCMj/iSz31s8Cm8P 18 | +oWX3ZFxWzG5ZiWHWhpmgSYIPbfEwniwskLJXfZSEr+kcLoVnZsbWOxwLCmdanZl 19 | wWRNyFW4waRJaCf+jsPPoR5LEh5d0ustpP/2EtbP52OLY865bfr18SUhPllrX0ia 20 | L3zSCMC/S7H45xzOdSneYrYNA1W199hY/4lBafh7t8a07C2CFMqN/jaUJOFHQIzS 21 | VIsVHvpVQlpslxcPGOOZsh5tJwEIKuepHm6nqsZbKMuO6Qzi5hXfwsI3cjD+NZY8 22 | Hg2H0IDXfXKWD/mcxeAjgc3lzzchGZ56EOMN75Fzl/HLCBu6pYuTbOmwG9whnaYv 23 | VbNhDSzC+x39o4O2nMsfixOe4tx2eK04nzWKIEfYeIJZc+9xfJW16hbmErFx1VWR 24 | x2Pi55hbOCEeOVxnhc50rqFN+tXiLvVzZaLN7H/S7Bqk2qCA/b6HjxY/cZ+KyQSo 25 | gMc2Mz5/dwROBEuFryjpFzTOFRfnu8krZAaHim1TlDCNFI4+NO5xu1AkFpepYphQ 26 | +7qWjLPqXqHH54C79Qs5ey+q+iEh3ulK3u5yeyvL9+rjaPJOQxd3yTfcstHPpeoG 27 | ePCEhrpLvkfukrMhfgg8R4gtkST2LWQNrvSCnFa5Ag0EXiTN4wEQAMGv94CKgsE/ 28 | H6V0YPxNFTfUFgZ+Rbse+DpGBUHSc2Bnp79ks9zq5zN6rRD8oztA2nyZAB39JJGq 29 | Y55yRRaQx/JqB1+WzSQywIdaAEt/D+PVfukfzjkNQ9/nELfBtx4W2xG5xiowzUqf 30 | XbWJvEmfOhKFYZrVayhVbc0DwZc1lBrO+1FX/z7NqyjgTkUusKIpjTjxl6afa79x 31 | 8B8O8vcpwZO5I6LIVf75W5YcY0vJ0tXqYqF5/JOkWtRTGCcZQvovXsPg/LQtRz39 32 | 61DuA0J02bCv/dUOPfwtk/PY4UzvIWmYH+xvfuJ4txX71VZVDVeSIMp7Iv9LVQL/ 33 | pefdyIf4RTya30qHTLLJmXhQNTS7fE2EFoOfi9xlP8CE+vEnf5gxUsLQ98aoIaWu 34 | +DpZelPTUpEJ45RNFxizmhv+X/d1WHyFS5bU53guxDSrFOKNKilCEqO5ukxYhMSY 35 | va0REZQh4iwuPFO3R4D1NM3HSJa2YmgVp8tHNJBTQImYTCd+NfY/wLlGFfgrjOLh 36 | XkIO2mHzANiCmoPcc4yoXGpsAr8N8yVe5sOKsbUvfMpisJv500BJA33ONQU60wWk 37 | S1g/RVn/dzJffSC+WOgxLkLAGaA0clrmJVmD/mlYhv8P/Ptwh0zAnkZsWk+NI15T 38 | EW2efOEj5QBg5PdQFyxJuOQXs7B+vRF9ABEBAAGJAjwEGAEIACYWIQRt7eqyWHrX 39 | DMY0QXqG5/UcBPuqsAUCXiTN4wIbDAUJFo5qAAAKCRCG5/UcBPuqsAvVD/9v/91W 40 | QyRLH+0EU/1vJM9pw/oNAPmLWB1sVy8n9Fk0z6sPtZbDTpP9+t0K+ZHiGOoa0B2I 41 | cGhd/VWTpZo6IfaqEjPkXyC9iVRyazlJIdkzwl9QOEqqPhO75IxwXN7LuBV6Vx12 42 | 3iDPh651EpN2NdbxmtMVymsWBjgfjKmTK8ye3r513F4I7Rp1EZOSNvmPutr3QaqT 43 | 3lAqX9S6bMsRhQA0kDL7wtk8V++tIjko70/y/I/nrY3IQD61CTLkjOSw1Uj5FUGR 44 | Y61Yl+ANai4R9R/AkTuRinzpolFyPpOM9bGJ5xzG+O29eWH7fq68fk/c4t/uQu2g 45 | rULrNGBVTCkka0p6uNjjFUn+sJzkibNucHYtS1DEiV4SXZ+mANohLjaocVr7MZ6p 46 | nhAAKIYD8EWi8IwXXQXgUBNqr6qQB5yFnPcAhR5aCLFgh+RCMqVVXzZ7FO4oc0Fh 47 | IaVijpzACjLCZWTo++W9T0RPP/2KQpij2ZIOtB80hF8dfeWZeP0/OEaPkjUZEHla 48 | y5kwSAI6zkqnRtZRJpXPyvum8H6ACniMUHjRmtoFtNrlLZMM2i1Q4Qf8B4eqga6r 49 | +ehY3JUxfk3BtV0Y8DgKCBXSd3N2YvVn623Z546o/Bxio8Db+gkprgAW+5F3RRin 50 | iK7xxd0ZLzHgO0ASOgAKLbglZ2T+80NSMbWUbrkCDQReJM5qARAAuenyMXbUjin5 51 | 2bntoz2I6b5dLMQlqRiggksMX1x/FFAO0cOaXxNtNpTzblN5Y2uZC6T+i5fnfXQ9 52 | yWkXIBLCXaaqziLx5wCg7rYsnS2/HYTVtd0ptYzqjKO++YAjHGcNnhUWdTJFRRMe 53 | CrYpRWtCH2YhdNPdfvsabLmF3LqI2GZp6x30q2oLxqLZFVA10d+AFCXEI5OcR9OW 54 | 1N4okodA5HE9o3e+wYnRbJJUW4Lmxc8BFli7eqLNIhoI//tx276EKBhatBM6nAlZ 55 | AQJuEoQk08RnXPaMewC9HHjZQBjdM+ej3gl9PeYj6ptqcTqreN4AuRtcvvOgS80b 56 | TwixWYpDe58XUnT9JUF/C+n4AP+JdvSZHtK0FG9eNbLjcCSarsvOV98CkHthidJz 57 | mfUjP2txPZVxoB2CxXfUjYZ4Ib981CmdPON+HN/wtDpsWj5mOKeER9dWdXwr1d/a 58 | eRYZviTXEHMnEB/LnWzNx01RmyIUhw13fGr+6KBU3E4AogKwp/xFgoccvNR0LfSA 59 | HhNCRUPCh85bg9yTVroPmYeW6u0UWHflY6mVid08QDYFkHWGkGrEwsslpu9FpSBu 60 | J9lAuzVGlfWK2mFA8Lq8NmpuzJLUURYnXDfaQB2SxOjf9hjV7Z7GbNa95HBHZzQa 61 | vOExLxEUzspwR9d0isytL4Ytz9v3QpUAEQEAAYkEcgQYAQgAJhYhBG3t6rJYetcM 62 | xjRBeobn9RwE+6qwBQJeJM5qAhsCBQkFxJAAAkAJEIbn9RwE+6qwwXQgBBkBCAAd 63 | FiEEbws8jxbNPEKXXCt7wPFsNLeCUjAFAl4kzmoACgkQwPFsNLeCUjCbyRAAttzx 64 | zxca+YOlki8VQSwr+vktRBtwKRkzr3LCSJ7gY1SQeeJ9wQDXlBeNLxN1ev8+Xhce 65 | JNMv39gG3mfUUwnq+nlOA92mY1ZNWgdtN3ZUcJeQSTL+Xs2NC6tC/lMTBTm4kPlz 66 | c5f7GIPRURfoNFnytHuPvIEL+i1iAQfsLxCWHUWlJcsvbiFhv3mTKHhGVgpQlLDo 67 | kww1tJzBg+GfvBVNgUGoTBHLx6s1lbcODF6jrumYBQHYL93FwXBTKXeTcPdwypzt 68 | SHRioPh8aJ+EVfmt6n0LWMmNOAZVD0Ps5jKaq8ymrYABdS8wgrVZ6TRTDuAfmZwr 69 | 4SY8gFqDLYEqXbl9iZZ3FFc8mWuBs7eqR5dxkVULgyjuYSdeppIhL2uuZmtsjAuJ 70 | 8ewzuTGWTmrmZCDhD9AwGyqsrOOtqejgsE85yKsPiADUFHq7JNSIRfsb+HBU9j/a 71 | yi0fIwW4bzhL4xXrlsx1HtH2obCGyfeO4WXdZOCqYBfC21jzyJPY+KO7I79BBANd 72 | HamkAy1971SiPKvjFPtY93Fp8dKWywc6tq6oe8SMGvKk0SZ+OXSMeBomZWjbdjR7 73 | CUiyf/va5+v5QKLzApRGrhvBxgwAxgvK4zfLoKOC4WZQl8lYhgFYGZcFYNclcGvE 74 | wy9SsCpIIOT1z8NUl2K68qC/7A5eWU4MHfGkDaX8/g/+MdflD32Ldmwiv9lptDMm 75 | CtzE/uD/J902v7BuQeXX+pdHkgWeK8zTAJkIPEMB1kNFIoKVcGoic0msB+cN4RYy 76 | VZN56+kkhMRP+0m+xjPKyZ687VF88j8mEo/ybFxhwh58LydKNBqkdNeY9UbTT5bv 77 | WUlHWO5qClsCbRIPwjvsTgZk9qjAWrWbIFivXpPcC8z7NDCFVD5JTBxnkBbcXyvP 78 | W68+BvknNJ2Ilnww2aNNp5zmzGeDLSV28MTtUJXb3w6cXfeOazqcydgHqPO1gyTq 79 | oarCkgPEQlyLey/3qDmsmUskHQ8c81bQKPyGKH4dLiz0qdayRB/GICEIN7O+uk7/ 80 | GdSem/sTXa/k9IpOm2eimdKG0y9C0k2kQb/IAr4cYMpbtN/gsyOr/UhqkzjawRCx 81 | wgHjKnXTNYtKcxuCpBpaPLQPFOE6IjG6Acpkh+LEdIYrY4hZmlaQqE3rXcye94rr 82 | F8ga+2XPNAwVH9zngrk1gZrTZmPkNkTG94kTfFwuAEmFWd6Dyf4IBoMacdpUyLTV 83 | 99zduIWc4SOdzEawU0JdD50dkSlHDJsoNamhHe60MELbLOQjN43n3KqKHjIpgZcd 84 | 2k2OFa/4uOXYoj3KmWxTKCV+BIcMuQfaaLJLHd1Uar59DExIatvw0aVWuj2M70tu 85 | J7VNNdmxD1RF3zsMFsPTQWY= 86 | =fqVo 87 | -----END PGP PUBLIC KEY BLOCK----- 88 | -------------------------------------------------------------------------------- /ext/CLI11/CLI/Split.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner 2 | // under NSF AWARD 1414736 and by the respective contributors. 3 | // All rights reserved. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "Error.hpp" 15 | #include "StringTools.hpp" 16 | 17 | namespace CLI { 18 | namespace detail { 19 | 20 | // Returns false if not a short option. Otherwise, sets opt name and rest and returns true 21 | inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { 22 | if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { 23 | name = current.substr(1, 1); 24 | rest = current.substr(2); 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true 31 | inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { 32 | if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { 33 | auto loc = current.find_first_of('='); 34 | if(loc != std::string::npos) { 35 | name = current.substr(2, loc - 2); 36 | value = current.substr(loc + 1); 37 | } else { 38 | name = current.substr(2); 39 | value = ""; 40 | } 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | // Returns false if not a windows style option. Otherwise, sets opt name and value and returns true 47 | inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { 48 | if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { 49 | auto loc = current.find_first_of(':'); 50 | if(loc != std::string::npos) { 51 | name = current.substr(1, loc - 1); 52 | value = current.substr(loc + 1); 53 | } else { 54 | name = current.substr(1); 55 | value = ""; 56 | } 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | // Splits a string into multiple long and short names 63 | inline std::vector split_names(std::string current) { 64 | std::vector output; 65 | std::size_t val; 66 | while((val = current.find(",")) != std::string::npos) { 67 | output.push_back(trim_copy(current.substr(0, val))); 68 | current = current.substr(val + 1); 69 | } 70 | output.push_back(trim_copy(current)); 71 | return output; 72 | } 73 | 74 | /// extract default flag values either {def} or starting with a ! 75 | inline std::vector> get_default_flag_values(const std::string &str) { 76 | std::vector flags = split_names(str); 77 | flags.erase(std::remove_if(flags.begin(), 78 | flags.end(), 79 | [](const std::string &name) { 80 | return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && 81 | (name.back() == '}')) || 82 | (name[0] == '!')))); 83 | }), 84 | flags.end()); 85 | std::vector> output; 86 | output.reserve(flags.size()); 87 | for(auto &flag : flags) { 88 | auto def_start = flag.find_first_of('{'); 89 | std::string defval = "false"; 90 | if((def_start != std::string::npos) && (flag.back() == '}')) { 91 | defval = flag.substr(def_start + 1); 92 | defval.pop_back(); 93 | flag.erase(def_start, std::string::npos); 94 | } 95 | flag.erase(0, flag.find_first_not_of("-!")); 96 | output.emplace_back(flag, defval); 97 | } 98 | return output; 99 | } 100 | 101 | /// Get a vector of short names, one of long names, and a single name 102 | inline std::tuple, std::vector, std::string> 103 | get_names(const std::vector &input) { 104 | 105 | std::vector short_names; 106 | std::vector long_names; 107 | std::string pos_name; 108 | 109 | for(std::string name : input) { 110 | if(name.length() == 0) { 111 | continue; 112 | } 113 | if(name.length() > 1 && name[0] == '-' && name[1] != '-') { 114 | if(name.length() == 2 && valid_first_char(name[1])) 115 | short_names.emplace_back(1, name[1]); 116 | else 117 | throw BadNameString::OneCharName(name); 118 | } else if(name.length() > 2 && name.substr(0, 2) == "--") { 119 | name = name.substr(2); 120 | if(valid_name_string(name)) 121 | long_names.push_back(name); 122 | else 123 | throw BadNameString::BadLongName(name); 124 | } else if(name == "-" || name == "--") { 125 | throw BadNameString::DashesOnly(name); 126 | } else { 127 | if(pos_name.length() > 0) 128 | throw BadNameString::MultiPositionalNames(name); 129 | pos_name = name; 130 | } 131 | } 132 | 133 | return std::tuple, std::vector, std::string>( 134 | short_names, long_names, pos_name); 135 | } 136 | 137 | } // namespace detail 138 | } // namespace CLI 139 | -------------------------------------------------------------------------------- /gps.cc: -------------------------------------------------------------------------------- 1 | #include "gps.hh" 2 | 3 | // this strips out spare bits + parity, and leaves 10 clean 24 bit words 4 | std::basic_string getCondensedGPSMessage(std::basic_string_view payload) 5 | { 6 | uint8_t buffer[10*24/8]; 7 | 8 | // ingests 32 bit words, per word ignores first 2 bits, and then takes 24 9 | for(int w = 0 ; w < 10; ++w) { 10 | setbitu(buffer, 24*w, 24, getbitu(&payload[0], 2 + w*32, 24)); 11 | } 12 | 13 | return std::basic_string(buffer, 30); 14 | 15 | } 16 | 17 | // expects input as 24 bit read to to use messages, returns frame number 18 | int GPSState::parseGPSMessage(std::basic_string_view cond, uint8_t* pageptr) 19 | { 20 | using namespace std; 21 | int frame = getbitu(&cond[0], 24+19, 3); 22 | // 10 * 4 bytes in payload now 23 | tow = 1.5*(getbitu(&cond[0], 24, 17)*4); 24 | // cerr << "Preamble: "< 56 113 | // page 25 -> 63 114 | // 2-10 -> 25 -> 32 ?? 115 | } 116 | 117 | if(frame == 5 || frame==4) { // this is a caroussel frame 118 | gpsalma.dataid = getbitu(&cond[0], 2*24, 2); 119 | gpsalma.sv = getbitu(&cond[0], 2*24+2, 6); 120 | 121 | if(pageptr) 122 | *pageptr= gpsalma.sv; 123 | 124 | 125 | gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16); 126 | gpsalma.t0a = getbitu(&cond[0], 3*24, 8); 127 | gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16); 128 | gpsalma.omegadot = getbits(&cond[0], 4*24, 16); 129 | gpsalma.health = getbitu(&cond[0], 4*24 +16, 8); 130 | gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24); 131 | gpsalma.Omega0 = getbits(&cond[0], 6*24, 24); 132 | gpsalma.omega = getbits(&cond[0], 7*24, 24); 133 | gpsalma.M0 = getbits(&cond[0], 8*24, 24); 134 | gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3); 135 | gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11); 136 | // cerr<<"Frame 5, SV: "< 2 | #include 3 | #include 4 | #include 5 | #include "fmt/format.h" 6 | #include "fmt/printf.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "galileo.hh" 13 | #include "navmon.pb.h" 14 | #include 15 | #include "navmon.hh" 16 | 17 | #include "CLI/CLI.hpp" 18 | #include "version.hh" 19 | 20 | static char program[]="navdisplay"; 21 | 22 | using namespace std; 23 | 24 | extern const char* g_gitHash; 25 | 26 | struct WinKeeper 27 | { 28 | WinKeeper(); 29 | struct Window 30 | { 31 | WINDOW *header, *text; 32 | int sv; 33 | void setHeader(std::string_view line) 34 | { 35 | wclear(header); 36 | wmove(header, 0, 0); 37 | wattron(header, A_BOLD | A_UNDERLINE); 38 | wprintw(header, "%s", &line[0]); 39 | wattroff(header, A_BOLD | A_UNDERLINE); 40 | wrefresh(header); 41 | } 42 | void setStatus(std::string_view line) 43 | { 44 | wmove(header, 1, 0); 45 | wattron(header, A_BOLD); 46 | wprintw(header, "%s", &line[0]); 47 | wattroff(header, A_BOLD); 48 | wrefresh(header); 49 | } 50 | 51 | void emitLine(std::string_view line) 52 | { 53 | wprintw(text, "%s\n", &line[0]); 54 | wrefresh(text); 55 | } 56 | 57 | void clear() 58 | { 59 | wclear(header); 60 | wrefresh(header); 61 | wclear(text); 62 | wrefresh(text); 63 | } 64 | time_t lastTime{0}; 65 | }; 66 | vector d_windows; 67 | static int d_h, d_w; 68 | void emitLine(int sv, std::string_view line); 69 | void setStatus(int sv, std::string_view line); 70 | 71 | }; 72 | 73 | int WinKeeper::d_h; 74 | int WinKeeper::d_w; 75 | 76 | WinKeeper::WinKeeper() 77 | { 78 | initscr(); 79 | getmaxyx(stdscr, d_h, d_w); 80 | 81 | int winwidth = d_w / 7; 82 | for(int n=0; n < 8 ; ++n) { 83 | d_windows.push_back({ 84 | newwin(3, winwidth, 0, n*(winwidth+2)), 85 | newwin(d_h-3, winwidth, 3, n*(winwidth+2)), 86 | 0}); 87 | scrollok(d_windows[n].text, 1); 88 | } 89 | }; 90 | 91 | void WinKeeper::emitLine(int sv, std::string_view line) 92 | { 93 | for(auto& w: d_windows) { 94 | if(w.sv == sv) { 95 | w.emitLine(line); 96 | return; 97 | } 98 | } 99 | // nothing matched, need to find a new window 100 | for(auto& w: d_windows) { 101 | if(!w.sv) { 102 | w.sv = sv; 103 | w.setHeader(std::to_string(sv)); 104 | w.emitLine(line); 105 | return; 106 | } 107 | } 108 | // all windows are in use, take over oldest one 109 | int age=0; 110 | time_t now = time(0); 111 | auto *wptr = &*d_windows.begin(); 112 | for(auto& w: d_windows) { 113 | if(now - w.lastTime > age) { 114 | age = now - w.lastTime; 115 | wptr = &w; 116 | } 117 | } 118 | 119 | wptr->sv = sv; 120 | wptr->setHeader(std::to_string(sv)); 121 | wptr->emitLine(line); 122 | wptr->lastTime = now; 123 | } 124 | 125 | void WinKeeper::setStatus(int sv, std::string_view line) 126 | { 127 | for(auto& w: d_windows) { 128 | if(w.sv == sv) { 129 | w.setStatus(line); 130 | return; 131 | } 132 | } 133 | // nothing matched 134 | for(auto& w: d_windows) { 135 | if(!w.sv) { 136 | w.sv = sv; 137 | w.setHeader(std::to_string(sv)); 138 | w.setStatus(line); 139 | return; 140 | } 141 | } 142 | throw std::runtime_error("Ran out of windows searching for sv "+std::to_string(sv)); 143 | } 144 | 145 | 146 | int main(int argc, char** argv) 147 | { 148 | bool doVERSION{false}; 149 | 150 | CLI::App app(program); 151 | 152 | app.add_flag("--version", doVERSION, "show program version and copyright"); 153 | 154 | try { 155 | app.parse(argc, argv); 156 | } catch(const CLI::Error &e) { 157 | return app.exit(e); 158 | } 159 | 160 | if(doVERSION) { 161 | showVersion(program, g_gitHash); 162 | exit(0); 163 | } 164 | 165 | WinKeeper wk; 166 | for(;;) { 167 | char bert[4]; 168 | if(readn2(0, bert, 4) != 4 || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') { 169 | cerr<<"EOF or bad magic"< gms; 189 | GalileoMessage& gm = gms[nmm.gi().gnsssv()]; 190 | 191 | basic_string inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size()); 192 | int wtype =gm.parse(inav); 193 | wk.emitLine(sv, "src "+to_string(nmm.sourceid())+" wtype " + to_string(wtype)); 194 | // 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) ); 195 | // wk.emitLine(sv, "GST-UTC6, a0="+to_string(a0)+", a1="+to_string(a1)+", age="+to_string(tow/3600-t0t)+"h, dw="+to_string(dw) 196 | // +", wn0t="+to_string(wn0t)+", wn8="+to_string(wn&0xff)); 197 | // wk.emitLine(sv, "GST-UTC6, a0="+to_string(a0)+", a1="+to_string(a1)); 198 | 199 | // wk.emitLine(sv, "Alm"+to_string(wtype)+" IODa="+to_string(ioda)); 200 | // wk.emitLine(sv, "GST-GPS, a0g="+to_string(a0g)+", a1g="+to_string(a1g)+", t0g="+to_string(t0g)+", age="+to_string(tow/3600-t0g)+"h, dw="+to_string(dw) 201 | // +", wn0g="+to_string(wn0g)+", wn6="+to_string(wn&(1+2+4+8+16+32))); 202 | } 203 | } 204 | } 205 | --------------------------------------------------------------------------------