3145 lines
116 KiB
C++
3145 lines
116 KiB
C++
#include <stdio.h>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <arpa/inet.h>
|
|
#include "fmt/format.h"
|
|
#include "fmt/printf.h"
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <bitset>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <signal.h>
|
|
#include <mutex>
|
|
#include "ext/powerblog/h2o-pp.hh"
|
|
#include "minicurl.hh"
|
|
#include <time.h>
|
|
#include "ubx.hh"
|
|
#include "bits.hh"
|
|
#include "minivec.hh"
|
|
#include "navmon.pb.h"
|
|
#include "ephemeris.hh"
|
|
#include "gps.hh"
|
|
#include "glonass.hh"
|
|
#include "beidou.hh"
|
|
#include "galileo.hh"
|
|
#include "tle.hh"
|
|
#include <optional>
|
|
#include "navmon.hh"
|
|
#include <Tle.h>
|
|
#include "navparse.hh"
|
|
#include <fenv.h>
|
|
#include "influxpush.hh"
|
|
#include "sbas.hh"
|
|
#include <sys/resource.h>
|
|
|
|
#include "CLI/CLI.hpp"
|
|
#include "gpscnav.hh"
|
|
#include "rtcm.hh"
|
|
#include "version.hh"
|
|
//#include "nequick.hh"
|
|
|
|
static char program[]="navparse";
|
|
|
|
using namespace std;
|
|
|
|
extern const char* g_gitHash;
|
|
|
|
struct ObserverFacts
|
|
{
|
|
Point pos;
|
|
double groundSpeed{-1};
|
|
double accuracy{-1.0};
|
|
string serialno;
|
|
string hwversion;
|
|
string swversion;
|
|
string mods;
|
|
string vendor;
|
|
double clockOffsetNS{-1};
|
|
double clockOffsetDriftNS{-1};
|
|
double clockAccuracyNS{-1};
|
|
double freqAccuracyPS{-1};
|
|
time_t uptime;
|
|
string githash;
|
|
string owner;
|
|
string remark;
|
|
time_t lastSeen{0};
|
|
};
|
|
std::map<int, ObserverFacts> g_srcpos;
|
|
|
|
struct SBASAndReceiverStatus
|
|
{
|
|
SBASState status;
|
|
struct PerRecv
|
|
{
|
|
time_t last_seen{0};
|
|
};
|
|
map<int, PerRecv> perrecv;
|
|
};
|
|
typedef map<int, SBASAndReceiverStatus> sbas_t;
|
|
sbas_t g_sbas;
|
|
GetterSetter<sbas_t> g_sbaskeeper;
|
|
|
|
map<int, BeidouAlmanacEntry> g_beidoualma;
|
|
map<int, pair<GlonassMessage, GlonassMessage>> g_glonassalma;
|
|
map<int, GalileoMessage::Almanac> g_galileoalma;
|
|
map<int, GPSAlmanac> g_gpsalma;
|
|
|
|
GetterSetter<map<int, BeidouAlmanacEntry>> g_beidoualmakeeper;
|
|
GetterSetter<map<int, pair<GlonassMessage, GlonassMessage>>> g_glonassalmakeeper;
|
|
GetterSetter<map<int, GalileoMessage::Almanac>> g_galileoalmakeeper;
|
|
GetterSetter<map<int, GPSAlmanac>> g_gpsalmakeeper;
|
|
|
|
TLERepo g_tles;
|
|
struct GNSSReceiver
|
|
{
|
|
Point position;
|
|
};
|
|
|
|
double g_GSTUTCOffset, g_GSTGPSOffset, g_GPSUTCOffset, g_BeiDouUTCOffset, g_GlonassUTCOffset, g_GlonassGPSOffset;
|
|
|
|
/*
|
|
The situation. We have a single ephemeris function that is able to operate on
|
|
Galileo, GPS1 and Beidou structs. It does so using functions like getT0e().
|
|
This ephemeris function also does speed and doppler.
|
|
|
|
We have structs for all four GNSS.
|
|
|
|
All GNSS have ephemerides that are spread out over multiple messages.
|
|
Because of that, we have to do some kind of atomic update.
|
|
|
|
We also have an SVStat that has some knowledge about IODs.
|
|
It would be great is we could ask the svstat about "the latest ephemeris"
|
|
and get a useful answer.
|
|
|
|
More concretely, we could ask the svstat about the latest *position* or *speed*.
|
|
It would be easier to do that. There is no unified "ephemeris object" between all the GNSS.
|
|
|
|
We do need a unified "do we have a complete ephemeris method".
|
|
|
|
For Galileo this is messages 1, 2, 3 and 4
|
|
For GPS it is 2 and 3
|
|
For BeiDou it is message 2 and message 3 in succession (?)
|
|
sow is in each message, forms a link
|
|
For GLONASS I don't really know
|
|
|
|
We also care about ephemeris discontinuities.
|
|
|
|
Idea is that SVStat has four structs around, one for each GNSS, each containing "the lastest complete ephemeris".
|
|
ephglomsg etc
|
|
|
|
It also has infrastructure for storing the ephemerides as they are being assembled.
|
|
|
|
Open question:
|
|
|
|
*/
|
|
|
|
|
|
// XXX conversion glonass??
|
|
int SVStat::wn() const
|
|
{
|
|
if(gnss == 0)
|
|
return gpsmsg.wn;
|
|
else if(gnss == 2)
|
|
return galmsg.wn;
|
|
else if(gnss == 3)
|
|
return beidoumsg.wn;
|
|
else if(gnss == 6) {
|
|
uint32_t glotime = glonassMessage.getGloTime(); // this starts GLONASS time at 31st of december 1995, 00:00 UTC
|
|
return glotime / (7*86400);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SVStat::tow() const
|
|
{
|
|
if(gnss == 0)
|
|
return gpsmsg.tow;
|
|
else if(gnss == 2)
|
|
return galmsg.tow;
|
|
else if(gnss == 3)
|
|
return beidoumsg.sow;
|
|
else if(gnss == 6) {
|
|
uint32_t glotime = glonassMessage.getGloTime(); // this starts GLONASS time at 31st of december 1995, 00:00 UTC
|
|
return glotime % (7*86400);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const GPSLikeEphemeris& SVStat::liveIOD() const
|
|
{
|
|
if(gnss == 0)
|
|
return ephgpsmsg;
|
|
else if(gnss == 2)
|
|
return ephgalmsg;
|
|
else if(gnss == 3)
|
|
return ephBeidoumsg;
|
|
|
|
throw std::runtime_error("Asked for GPS like ephemeris for gnss " + to_string(gnss));
|
|
}
|
|
|
|
const GPSLikeEphemeris& SVStat::prevIOD() const
|
|
{
|
|
if(gnss == 0)
|
|
return oldephgpsmsg;
|
|
else if(gnss == 2)
|
|
return oldephgalmsg;
|
|
else if(gnss == 3)
|
|
return oldephBeidoumsg;
|
|
|
|
throw std::runtime_error("Asked for old-GPS like ephemeris for gnss " + to_string(gnss));
|
|
}
|
|
|
|
|
|
bool SVStat::completeIOD() const
|
|
{
|
|
if(gnss == 6)
|
|
return false;
|
|
// yeah now what
|
|
return true;
|
|
}
|
|
|
|
|
|
double SVStat::getCoordinates(double tow, Point* p, bool quiet) const
|
|
{
|
|
if(gnss == 6)
|
|
return ::getCoordinates(tow, ephglomsg, p);
|
|
|
|
// getCoordinates needs to be overloaded for GLONASS
|
|
return ::getCoordinates(tow, liveIOD(), p, quiet);
|
|
|
|
}
|
|
|
|
double SVStat::getOldEphCoordinates(double tow, Point* p, bool quiet) const
|
|
{
|
|
if(gnss == 6)
|
|
return ::getCoordinates(tow, oldephglomsg, p);
|
|
|
|
// getCoordinates needs to be overloaded for GLONASS
|
|
return ::getCoordinates(tow, prevIOD(), p, quiet);
|
|
|
|
}
|
|
|
|
|
|
void SVStat::getSpeed(double tow, Vector* v) const
|
|
{
|
|
return ::getSpeed(tow, liveIOD(), v);
|
|
}
|
|
DopplerData SVStat::doDoppler(double tow, const Point& us, double freq) const
|
|
{
|
|
if(gnss == 6)
|
|
return ::doDoppler(tow, us, ephglomsg, freq);
|
|
else
|
|
return ::doDoppler(tow, us, liveIOD(), freq);
|
|
}
|
|
|
|
double satUTCTime(const SatID& id);
|
|
|
|
void SVStat::reportNewEphemeris(const SatID& id, InfluxPusher& idb)
|
|
{
|
|
int ephage;
|
|
if(gnss==6)
|
|
ephage = ephAge(ephglomsg.getT0e(), oldephglomsg.getT0e());
|
|
else
|
|
ephage = ephAge(liveIOD().getT0e(), prevIOD().getT0e());
|
|
|
|
Point p, oldp;
|
|
getCoordinates(tow(), &p);
|
|
getOldEphCoordinates(tow(), &oldp);
|
|
|
|
double hours = ephage / 3600;
|
|
double disco = Vector(p, oldp).length();
|
|
// cout<<id.first<<","<<id.second<<" discontinuity after "<< hours<<" hours: "<< disco <<endl;
|
|
|
|
if(hours < 4) {
|
|
latestDisco= disco;
|
|
latestDiscoAge= ephage;
|
|
}
|
|
else
|
|
latestDisco= -1;
|
|
|
|
if(gnss==0) { // GPS
|
|
const auto& eg = ephgpsmsg;
|
|
|
|
|
|
idb.addValue(id, "ephemeris-actual", {
|
|
{"iod", eg.getIOD()},
|
|
{"t0e", eg.t0e},
|
|
{"sqrta", eg.sqrtA},
|
|
{"e", eg.e},
|
|
{"cuc", eg.cuc},
|
|
{"cus", eg.cus},
|
|
{"crc", eg.crc},
|
|
{"crs", eg.crs},
|
|
{"m0", eg.m0},
|
|
{"deltan", eg.deltan},
|
|
{"i0", eg.i0},
|
|
{"cic", eg.cic},
|
|
{"cis", eg.cis},
|
|
{"omegadot", eg.omegadot},
|
|
{"omega0", eg.omega0},
|
|
{"idot", eg.idot},
|
|
{"af0", eg.af0},
|
|
{"af1", eg.af1},
|
|
{"af2", eg.af2},
|
|
{"t0c", eg.t0c},
|
|
{"omega", eg.omega}}, satUTCTime(id));
|
|
|
|
}
|
|
|
|
|
|
if(gnss==2) {
|
|
const auto& eg = ephgalmsg;
|
|
|
|
idb.addValue(id, "ephemeris-actual", {
|
|
{"iod", eg.getIOD()},
|
|
{"t0e", eg.t0e},
|
|
{"sqrta", eg.sqrtA},
|
|
{"e", eg.e},
|
|
{"cuc", eg.cuc},
|
|
{"cus", eg.cus},
|
|
{"crc", eg.crc},
|
|
{"crs", eg.crs},
|
|
{"m0", eg.m0},
|
|
{"deltan", eg.deltan},
|
|
{"i0", eg.i0},
|
|
{"cic", eg.cic},
|
|
{"cis", eg.cis},
|
|
{"omegadot", eg.omegadot},
|
|
{"omega0", eg.omega0},
|
|
{"idot", eg.idot},
|
|
{"af0", eg.af0},
|
|
{"af1", eg.af1},
|
|
{"af2", eg.af2},
|
|
{"t0c", eg.t0c},
|
|
{"omega", eg.omega}}, satUTCTime(id));
|
|
|
|
}
|
|
|
|
if(hours < 24) {
|
|
idb.addValue(id, "eph-disco",
|
|
{{"meters", disco},
|
|
{"seconds", hours*3600.0},
|
|
{"oldx", oldp.x},
|
|
{"oldy", oldp.y},
|
|
{"oldz", oldp.z},
|
|
{"x", p.x},
|
|
{"y", p.y},
|
|
{"z", p.z},
|
|
{"iod", gnss == 6 ? -1.0 : 1.0*liveIOD().getIOD()},
|
|
{"oldiod", gnss== 6 ? -1.0 : 1.0*prevIOD().getIOD()}}, satUTCTime(id));
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
svstats_t g_svstats;
|
|
|
|
GetterSetter<svstats_t> g_statskeeper;
|
|
|
|
int latestWN(int gnssid, const svstats_t& stats)
|
|
{
|
|
map<int, SatID> ages;
|
|
for(const auto& s: stats)
|
|
if(s.first.gnss == (unsigned int)gnssid)
|
|
ages[7*s.second.wn()*86400 + s.second.tow()]= s.first;
|
|
if(ages.empty())
|
|
throw runtime_error("Asked for latest WN for "+to_string(gnssid)+": we don't know it yet ("+to_string(stats.size())+")");
|
|
|
|
return stats.find(ages.rbegin()->second)->second.wn();
|
|
}
|
|
|
|
int latestTow(int gnssid, const svstats_t& stats)
|
|
{
|
|
map<int, SatID> ages;
|
|
for(const auto& s: stats)
|
|
if(s.first.gnss == (unsigned int) gnssid)
|
|
ages[7*s.second.wn()*86400 + s.second.tow()]= s.first;
|
|
if(ages.empty())
|
|
throw runtime_error("Asked for latest TOW for "+to_string(gnssid)+": we don't know it yet ("+to_string(stats.size())+")");
|
|
return stats.find(ages.rbegin()->second)->second.tow();
|
|
}
|
|
|
|
|
|
// nanoseconds posix time from that gnss WN and TOW
|
|
int64_t nanoTime(int gnssid, int wn, double tow)
|
|
{
|
|
int offset;
|
|
if(gnssid == 0) // GPS
|
|
offset = 315964800;
|
|
if(gnssid == 2) // Galileo, 22-08-1999
|
|
offset = 935280000;
|
|
if(gnssid == 3) {// Beidou, 01-01-2006 - I think leap seconds count differently in Beidou!! XXX
|
|
offset = 1136073600;
|
|
return 1000000000ULL*(offset + wn * 7*86400 + tow - g_dtLSBeidou);
|
|
}
|
|
if(gnssid == 6) { // GLONASS
|
|
offset = 820368000;
|
|
return 1000000000ULL*(offset + wn * 7*86400 + tow); // no leap seconds in glonass
|
|
}
|
|
|
|
return 1000000000ULL*(offset + wn * 7*86400 + tow - g_dtLS);
|
|
}
|
|
|
|
// same in seconds
|
|
double satUTCTime(const SatID& id)
|
|
{
|
|
return nanoTime(id.gnss, g_svstats[id].wn(), g_svstats[id].tow())/1000000000.0;
|
|
}
|
|
|
|
|
|
/* The GST start epoch is defined as 13 seconds before midnight between 21st August and
|
|
22nd August 1999, i.e. GST was equal to 13 seconds at 22nd August 1999 00:00:00 UTC. */
|
|
|
|
std::string humanTime(int gnssid, int wn, int tow)
|
|
{
|
|
time_t t = nanoTime(gnssid, wn, tow)/1000000000;
|
|
|
|
struct tm tm;
|
|
gmtime_r(&t, &tm);
|
|
|
|
char buffer[80];
|
|
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", &tm);
|
|
return buffer;
|
|
}
|
|
|
|
std::optional<double> getHzCorrection(time_t now, int src, unsigned int gnssid, unsigned int sigid, const svstats_t& svstats)
|
|
{
|
|
|
|
std::optional<double> allHzCorr;
|
|
double alltot=0;
|
|
int allcount=0;
|
|
// cout<<"getHzCorrection called for src "<<src<<" gnss "<<gnssid <<" sigid "<< sigid <<endl;
|
|
for(const auto& s: svstats) {
|
|
if(s.first.gnss != gnssid)
|
|
continue;
|
|
if(s.first.sigid != sigid)
|
|
continue;
|
|
if(auto iter = s.second.perrecv.find(src); iter != s.second.perrecv.end() && now - iter->second.deltaHzTime < 60) {
|
|
// cout<<" Found entry for SV "<<s.first.gnss<<","<<s.first.sv<<","<<s.first.sigid<<" from src "<<iter->first<<", deltaHz: "<<iter->second.deltaHz<< " age " << now - iter->second.deltaHzTime<<" db "<<iter->second.db<<endl;
|
|
alltot+=iter->second.deltaHz;
|
|
allcount++;
|
|
}
|
|
}
|
|
if(allcount > 3) {
|
|
allHzCorr = alltot/allcount;
|
|
// cout<<"Returning "<<*allHzCorr<<endl;
|
|
}
|
|
else
|
|
; // cout<<"Not enough data"<<endl;
|
|
return allHzCorr;
|
|
}
|
|
|
|
|
|
|
|
std::string humanBhs(int bhs)
|
|
{
|
|
static vector<string> options{"ok", "out of service", "will be out of service", "test"};
|
|
if(bhs >= (int)options.size()) {
|
|
cerr<<"Asked for humanBHS "<<bhs<<endl;
|
|
return "??";
|
|
}
|
|
return options.at(bhs);
|
|
}
|
|
|
|
void addHeaders(h2o_req_t* req)
|
|
{
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL,
|
|
NULL, H2O_STRLIT("max-age=3"));
|
|
|
|
// Access-Control-Allow-Origin
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
NULL, H2O_STRLIT("*"));
|
|
|
|
}
|
|
|
|
time_t getSatelliteUTC(svstats_t& svstats)
|
|
{
|
|
time_t ret;
|
|
try {
|
|
ret=utcFromGST(latestWN(2, svstats), latestTow(2, svstats));
|
|
}
|
|
catch(...)
|
|
{
|
|
ret=utcFromGPS(latestWN(0, svstats), latestTow(0, svstats));
|
|
// if this throws, we are done
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static double get_cpu_seconds_total()
|
|
{
|
|
struct rusage usage;
|
|
getrusage(RUSAGE_SELF, &usage);
|
|
return usage.ru_utime.tv_sec + usage.ru_utime.tv_usec/1000000.0 + usage.ru_stime.tv_sec + usage.ru_stime.tv_usec/1000000.0;
|
|
}
|
|
|
|
|
|
void storeSelfStats(InfluxPusher& idb, time_t t)
|
|
{
|
|
map<int, int> receivers;
|
|
time_t now;
|
|
try {
|
|
now=getSatelliteUTC(g_svstats);
|
|
}
|
|
catch(...) {
|
|
return;
|
|
}
|
|
map<SatID, int> svcount, sigcount;
|
|
map<int, int> siggnsscount, svgnsscount;
|
|
for(const auto& sv : g_svstats) {
|
|
bool fresh=false;
|
|
for(const auto& pr : sv.second.perrecv) {
|
|
int age = now - pr.second.t;
|
|
if(age < 30) {
|
|
fresh= true;
|
|
receivers[pr.first]++;
|
|
}
|
|
}
|
|
if(fresh) {
|
|
sigcount[sv.first]++;
|
|
siggnsscount[sv.first.gnss]++;
|
|
SatID id = sv.first;
|
|
id.sigid=0;
|
|
svcount[id]++;
|
|
}
|
|
}
|
|
for(const auto& sv : svcount)
|
|
svgnsscount[sv.first.gnss]++;
|
|
|
|
|
|
vector<pair<string,InfluxPusher::var_t>> tags;
|
|
idb.addValue(tags, "self", {
|
|
{"measurements", idb.d_nummsmts},
|
|
{"dedup-msmts", idb.d_numdedupmsmts},
|
|
{"values", idb.d_numvalues},
|
|
{"total-live-svs", (int64_t)svcount.size()},
|
|
{"total-live-signals", (int64_t)sigcount.size()},
|
|
{"gps-svs", svgnsscount[0]},
|
|
{"galileo-svs", svgnsscount[2]},
|
|
{"beidou-svs",svgnsscount[3]},
|
|
{"glonass-svs",svgnsscount[6]},
|
|
{"gps-sigs", siggnsscount[0]},
|
|
{"galileo-sigs", siggnsscount[2]},
|
|
{"beidou-sigs", siggnsscount[3]},
|
|
{"glonass-sigs", siggnsscount[6]},
|
|
{"total-cpu-msec", 1000.0*get_cpu_seconds_total()},
|
|
{"total-live-receivers", (int64_t)receivers.size()}
|
|
}, t);
|
|
|
|
for(const auto& p : idb.d_msmtmap) {
|
|
idb.addValue({{"metric", p.first}}, "msmts", {{"value", p.second}}, t);
|
|
}
|
|
}
|
|
|
|
|
|
// GALILEO ONLY FOR NOW
|
|
void storeCoverageStats(InfluxPusher& idb, time_t t)
|
|
try {
|
|
int tow;
|
|
|
|
tow=latestTow(2, g_svstats);
|
|
vector<Point> sats;
|
|
|
|
for(const auto &g : g_galileoalma) {
|
|
Point sat;
|
|
getCoordinates(tow, g.second, &sat);
|
|
|
|
if(g.first < 0)
|
|
continue;
|
|
SatID id{2,(uint32_t)g.first,1};
|
|
const auto& svstat = g_svstats[id];
|
|
if(svstat.completeIOD() && svstat.galmsg.sisa == 255) {
|
|
continue;
|
|
}
|
|
if(svstat.galmsg.e1bhs || svstat.galmsg.e1bdvs) {
|
|
continue;
|
|
}
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
vector<SatID> aux{{2, 14, 1}, {2,18,1}};
|
|
for(const auto& id : aux) {
|
|
if(!g_svstats.count(id))
|
|
continue;
|
|
|
|
const auto& svstat = g_svstats[id];
|
|
|
|
if(svstat.completeIOD() && svstat.galmsg.sisa == 255) {
|
|
continue;
|
|
}
|
|
if(svstat.galmsg.e1bhs || svstat.galmsg.e1bdvs) {
|
|
continue;
|
|
}
|
|
Point sat;
|
|
getCoordinates(tow, svstat.galmsg, &sat);
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
// cout<<endl;
|
|
auto cov = emitCoverage(sats);
|
|
int cells=0;
|
|
int pdopexceeds5=0, pdopexceeds10=0, pdopexceeds20=0;
|
|
int covlow5=0, covlow10=0, covlow20=0;
|
|
for(const auto& latvect : cov) {
|
|
for(const auto& longpair : latvect.second) {
|
|
cells++;
|
|
if(get<4>(longpair) >= 6.0 || get<4>(longpair) < 0.0 )
|
|
pdopexceeds5++;
|
|
if(get<5>(longpair) >= 6.0 || get<5>(longpair) < 0.0 )
|
|
pdopexceeds10++;
|
|
if(get<6>(longpair) >= 6.0 || get<6>(longpair) < 0.0 )
|
|
pdopexceeds20++;
|
|
|
|
if(get<1>(longpair) < 4)
|
|
covlow5++;
|
|
if(get<2>(longpair) < 4)
|
|
covlow10++;
|
|
if(get<3>(longpair) < 4)
|
|
covlow20++;
|
|
|
|
|
|
// else
|
|
//cout<<get<6>(longpair) << endl;
|
|
}
|
|
}
|
|
/*
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells exceeded PDOP 6 for 5 degrees horizon (%d sats)\n", humanTime(t), 100.0*pdopexceeds5/cells, pdopexceeds5, cells, sats.size());
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells exceeded PDOP 6 for 10 degrees horizon (%d sats)\n", humanTime(t), 100.0*pdopexceeds10/cells, pdopexceeds10, cells, sats.size());
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells exceeded PDOP 6 for 20 degrees horizon (%d sats)\n", humanTime(t), 100.0*pdopexceeds20/cells, pdopexceeds20, cells, sats.size());
|
|
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells have less than 4 sats in view for 5 degrees horizon (%d sats)\n", humanTime(t), 100.0*covlow5/cells, covlow5, cells, sats.size());
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells have less than 4 sats in view for 10 degrees horizon (%d sats)\n", humanTime(t), 100.0*covlow10/cells, covlow10, cells, sats.size());
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells have less than 4 sats in view for 20 degrees horizon (%d sats)\n", humanTime(t), 100.0*covlow20/cells, covlow20, cells, sats.size());
|
|
*/
|
|
idb.addValue({{"sigid", 1},{"gnss", 2}}, "quality", {
|
|
{"pdop5perc", 100.0*pdopexceeds5/cells},
|
|
{"pdop10perc", 100.0*pdopexceeds10/cells},
|
|
{"pdop20perc", 100.0*pdopexceeds20/cells},
|
|
{"covlow5", 100.0*covlow5/cells},
|
|
{"covlow10", 100.0*covlow10/cells},
|
|
{"covlow20", 100.0*covlow20/cells},
|
|
{"sats", (int)sats.size()}
|
|
}, t);
|
|
|
|
}
|
|
catch(std::exception&e) {
|
|
cout<<"Error with coverage: "<<e.what()<<endl;
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
try
|
|
{
|
|
bool doVERSION{false};
|
|
|
|
CLI::App app(program);
|
|
string localAddress("127.0.0.1:29599");
|
|
string htmlDir("./html");
|
|
string influxDBName("null");
|
|
bool doGalileoReportSpeedup{false};
|
|
bool doLogRFData{false};
|
|
app.add_flag("--version", doVERSION, "show program version and copyright");
|
|
app.add_flag("--log-rf-data", doLogRFData, "store per station RF/correlator data");
|
|
app.add_flag("--gal-report-speedup", doGalileoReportSpeedup, "skip debugging data, glonass, beidou, SBAS");
|
|
app.add_option("--bind,-b", localAddress, "Address to bind to");
|
|
app.add_option("--html", htmlDir, "Where to source the HTML & JavaScript");
|
|
app.add_option("--influxdb", influxDBName, "Name of influxdb database");
|
|
|
|
try {
|
|
app.parse(argc, argv);
|
|
} catch(const CLI::Error &e) {
|
|
return app.exit(e);
|
|
}
|
|
|
|
if(doVERSION) {
|
|
showVersion(program, g_gitHash);
|
|
exit(0);
|
|
}
|
|
|
|
// feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW );
|
|
|
|
|
|
// g_tles.parseFile("active.txt");
|
|
|
|
g_tles.parseFile("galileo.txt");
|
|
g_tles.parseFile("glo-ops.txt");
|
|
g_tles.parseFile("gps-ops.txt");
|
|
g_tles.parseFile("beidou.txt");
|
|
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
InfluxPusher idb(influxDBName);
|
|
MiniCurl::init();
|
|
|
|
H2OWebserver h2s("galmon");
|
|
|
|
h2s.addHandler("/global.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
|
|
nlohmann::json ret = nlohmann::json::object();
|
|
auto svstats = g_statskeeper.get();
|
|
ret["leap-seconds"] = g_dtLS;
|
|
try {
|
|
// if we haven't seen galileo, we have no idea
|
|
ret["last-seen"]=utcFromGST(latestWN(2, svstats), latestTow(2, svstats));
|
|
}
|
|
catch(...)
|
|
{
|
|
try {
|
|
ret["last-seen"]=utcFromGPS(latestWN(0, svstats), latestTow(0, svstats));
|
|
}
|
|
catch(...)
|
|
{}
|
|
}
|
|
|
|
ret["gst-utc-offset-ns"] = g_GSTUTCOffset;
|
|
ret["gst-gps-offset-ns"] = g_GSTGPSOffset;
|
|
ret["gps-utc-offset-ns"] = g_GPSUTCOffset;
|
|
ret["beidou-utc-offset-ns"] = g_BeiDouUTCOffset;
|
|
ret["glonass-utc-offset-ns"] = g_GlonassUTCOffset;
|
|
ret["glonass-gps-offset-ns"] = g_GlonassGPSOffset;
|
|
|
|
map<int, int> siggnsscount, svgnsscount;
|
|
map<SatID, int> svcount, sigcount;
|
|
map<int, int> receivers;
|
|
time_t now=time(0);
|
|
for(const auto& sv : svstats) {
|
|
bool fresh=false;
|
|
for(const auto& pr : sv.second.perrecv) {
|
|
int age = now - pr.second.t;
|
|
if(age < 30) {
|
|
fresh= true;
|
|
receivers[pr.first]++;
|
|
}
|
|
}
|
|
if(fresh) {
|
|
|
|
sigcount[sv.first]++;
|
|
siggnsscount[sv.first.gnss]++;
|
|
SatID id = sv.first;
|
|
id.sigid=0;
|
|
svcount[id]++;
|
|
}
|
|
}
|
|
for(const auto& sv : svcount)
|
|
svgnsscount[sv.first.gnss]++;
|
|
|
|
ret["total-live-receivers"] = receivers.size();
|
|
ret["total-live-svs"] = svcount.size();
|
|
ret["total-live-signals"] = sigcount.size();
|
|
ret["gps-svs"] = svgnsscount[0];
|
|
ret["galileo-svs"] = svgnsscount[2];
|
|
ret["beidou-svs"] = svgnsscount[3];
|
|
ret["glonass-svs"] = svgnsscount[6];
|
|
|
|
ret["gps-sigs"] = siggnsscount[0];
|
|
ret["galileo-sigs"] = siggnsscount[2];
|
|
ret["beidou-sigs"] = siggnsscount[3];
|
|
ret["glonass-sigs"] = siggnsscount[6];
|
|
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/almanac.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
|
|
auto beidoualma = g_beidoualmakeeper.get();
|
|
auto svstats = g_statskeeper.get();
|
|
nlohmann::json ret = nlohmann::json::object();
|
|
for(const auto& ae : beidoualma) {
|
|
|
|
nlohmann::json item = nlohmann::json::object();
|
|
item["gnssid"]=3;
|
|
if(ae.second.alma.getT0e() > 7*86400)
|
|
continue;
|
|
Point sat;
|
|
getCoordinates(latestTow(3, svstats), ae.second.alma, &sat);
|
|
item["eph-ecefX"]= sat.x/1000;
|
|
item["eph-ecefY"]= sat.y/1000;
|
|
item["eph-ecefZ"]= sat.z/1000;
|
|
|
|
auto longlat = getLongLat(sat.x, sat.y, sat.z);
|
|
item["eph-longitude"] = 180*longlat.first/M_PI;
|
|
item["eph-latitude"]= 180*longlat.second/M_PI;
|
|
item["t0e"] = ae.second.alma.getT0e();
|
|
item["t"]= ephAge(ae.second.alma.getT0e(), latestTow(3, svstats))/86400.0;
|
|
item["inclination"] = 180 * ae.second.alma.getI0() /M_PI;
|
|
|
|
item["observed"]=false;
|
|
if(auto iter = svstats.find({3, (uint32_t)ae.first, 0}); iter != svstats.end()) {
|
|
if(time(0) - nanoTime(3, iter->second.wn(), iter->second.tow())/1000000000 < 300)
|
|
item["observed"] = true;
|
|
}
|
|
|
|
if(ephAge(ae.second.alma.getT0e(), latestTow(3, svstats)) < 0) {
|
|
auto match = g_tles.getBestMatch(nanoTime(3, latestWN(3, svstats), latestTow(3, svstats))/1000000000.0,
|
|
sat.x, sat.y, sat.z);
|
|
|
|
if(match.distance < 200000) {
|
|
item["best-tle"] = match.name;
|
|
item["best-tle-norad"] = match.norad;
|
|
item["best-tle-int-desig"] = match.internat;
|
|
item["best-tle-dist"] = match.distance/1000.0;
|
|
|
|
item["tle-ecefX"] = match.ecefX/1000;
|
|
item["tle-ecefY"] = match.ecefY/1000;
|
|
item["tle-ecefZ"] = match.ecefZ/1000;
|
|
|
|
item["tle-eciX"] = match.eciX/1000;
|
|
item["tle-eciY"] = match.eciY/1000;
|
|
item["tle-eciZ"] = match.eciZ/1000;
|
|
|
|
item["tle-latitude"] = 180*match.latitude/M_PI;
|
|
item["tle-longitude"] = 180*match.longitude/M_PI;
|
|
item["tle-altitude"] = match.altitude;
|
|
}
|
|
}
|
|
auto name = fmt::sprintf("C%02d", ae.first);
|
|
item["name"]=name;
|
|
ret[name] = item;
|
|
}
|
|
|
|
auto glonassalma = g_glonassalmakeeper.get();
|
|
for(const auto& ae : glonassalma) {
|
|
nlohmann::json item = nlohmann::json::object();
|
|
|
|
// ae.second.first -> even ae.second.sceond -> odd
|
|
item["gnssid"]=6;
|
|
item["e"] = ae.second.first.getE();
|
|
item["inclination"] = 180 * ae.second.first.getI0() /M_PI;
|
|
item["health"] = ae.second.first.CnA;
|
|
item["tlambdana"] = ae.second.second.gettLambdaNa();
|
|
item["lambdana"] = ae.second.second.getLambdaNaDeg();
|
|
item["hna"] = ae.second.second.hna;
|
|
|
|
item["observed"] = false;
|
|
for(uint32_t sigid : {0,1,2}) { // XXX SIGIDS
|
|
if(auto iter = svstats.find({6, (uint32_t)ae.first, sigid}); iter != svstats.end()) {
|
|
if(time(0) - nanoTime(6, iter->second.wn(), iter->second.tow())/1000000000 < 300) {
|
|
item["observed"] = true;
|
|
auto longlat = getLongLat(iter->second.glonassMessage.x, iter->second.glonassMessage.y, iter->second.glonassMessage.z);
|
|
item["eph-longitude"] = 180*longlat.first/M_PI;
|
|
item["eph-latitude"]= 180*longlat.second/M_PI;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
auto name = fmt::sprintf("R%02d", ae.first);
|
|
item["name"]=name;
|
|
ret[name] = item;
|
|
}
|
|
|
|
auto galileoalma = g_galileoalmakeeper.get();
|
|
for(const auto& ae : galileoalma) {
|
|
nlohmann::json item = nlohmann::json::object();
|
|
item["gnssid"]=2;
|
|
item["e"] = ae.second.getE();
|
|
item["e1bhs"] = ae.second.e1bhs;
|
|
item["e5bhs"] = ae.second.e5bhs;
|
|
item["t0e"] = ae.second.getT0e();
|
|
item["t"]= ephAge(ae.second.getT0e(), latestTow(2, svstats))/86400.0;
|
|
item["eph-age"] = ephAge(latestTow(2, svstats), ae.second.getT0e());
|
|
item["i0"] = 180.0 * ae.second.getI0()/ M_PI;
|
|
item["inclination"] = 180 * ae.second.getI0() /M_PI;
|
|
|
|
item["omega"] = ae.second.getOmega();
|
|
item["sqrtA"]= ae.second.getSqrtA();
|
|
item["M0"] = ae.second.getM0();
|
|
item["delta-n"] = ae.second.getDeltan();
|
|
item["omega-dot"] = ae.second.getOmegadot();
|
|
item["omega0"] = ae.second.getOmega0();
|
|
item["idot"] = ae.second.getIdot();
|
|
item["t0e"] = ae.second.getT0e();
|
|
|
|
Point sat;
|
|
double E=getCoordinates(latestTow(2, svstats), ae.second, &sat);
|
|
item["E"]=E;
|
|
item["eph-ecefX"]= sat.x/1000;
|
|
item["eph-ecefY"]= sat.y/1000;
|
|
item["eph-ecefZ"]= sat.z/1000;
|
|
|
|
auto longlat = getLongLat(sat.x, sat.y, sat.z);
|
|
item["eph-longitude"] = 180*longlat.first/M_PI;
|
|
item["eph-latitude"]= 180*longlat.second/M_PI;
|
|
|
|
|
|
item["observed"] = false;
|
|
for(uint32_t sigid : {0,1,5}) {
|
|
if(auto iter = svstats.find({2, (uint32_t)ae.first, sigid}); iter != svstats.end()) {
|
|
|
|
if(iter->second.completeIOD()) {
|
|
item["sisa"] = iter->second.galmsg.sisa;
|
|
}
|
|
// if we hit an 'observed', stop trying sigids
|
|
if(time(0) - nanoTime(2, iter->second.wn(), iter->second.tow())/1000000000 < 300) {
|
|
item["observed"] = true;
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
auto match = g_tles.getBestMatch(nanoTime(2, latestWN(2, svstats), latestTow(2, svstats))/1000000000.0,
|
|
sat.x, sat.y, sat.z);
|
|
|
|
if(match.distance < 200000) {
|
|
item["best-tle"] = match.name;
|
|
item["best-tle-norad"] = match.norad;
|
|
item["best-tle-int-desig"] = match.internat;
|
|
item["best-tle-dist"] = match.distance/1000.0;
|
|
|
|
item["tle-ecefX"] = match.ecefX/1000;
|
|
item["tle-ecefY"] = match.ecefY/1000;
|
|
item["tle-ecefZ"] = match.ecefZ/1000;
|
|
|
|
item["tle-eciX"] = match.eciX/1000;
|
|
item["tle-eciY"] = match.eciY/1000;
|
|
item["tle-eciZ"] = match.eciZ/1000;
|
|
|
|
item["tle-latitude"] = 180*match.latitude/M_PI;
|
|
item["tle-longitude"] = 180*match.longitude/M_PI;
|
|
item["tle-altitude"] = match.altitude;
|
|
}
|
|
auto name = fmt::sprintf("E%02d", ae.first);
|
|
item["name"]= name;
|
|
ret[name] = item;
|
|
}
|
|
|
|
auto gpsalma = g_gpsalmakeeper.get();
|
|
for(const auto& ae : gpsalma) {
|
|
nlohmann::json item = nlohmann::json::object();
|
|
item["gnssid"]=0;
|
|
item["e"] = ae.second.getE();
|
|
item["health"] = ae.second.health;
|
|
item["t0e"] = ae.second.getT0e();
|
|
item["t"]= ephAge(ae.second.getT0e(), latestTow(0, svstats))/86400.0;
|
|
item["eph-age"] = ephAge(latestTow(0, svstats), ae.second.getT0e());
|
|
item["i0"] = 180.0 * ae.second.getI0()/ M_PI;
|
|
item["inclination"] = 180 * ae.second.getI0() /M_PI;
|
|
Point sat;
|
|
getCoordinates(latestTow(0, svstats), ae.second, &sat);
|
|
item["eph-ecefX"]= sat.x/1000;
|
|
item["eph-ecefY"]= sat.y/1000;
|
|
item["eph-ecefZ"]= sat.z/1000;
|
|
|
|
auto longlat = getLongLat(sat.x, sat.y, sat.z);
|
|
item["eph-longitude"] = 180*longlat.first/M_PI;
|
|
item["eph-latitude"]= 180*longlat.second/M_PI;
|
|
|
|
item["observed"] = false;
|
|
for(uint32_t sigid : {0,1,4}) {
|
|
if(auto iter = svstats.find({0, (uint32_t)ae.first, sigid}); iter != svstats.end()) {
|
|
if(time(0) - nanoTime(0, iter->second.wn(), iter->second.tow())/1000000000 < 300)
|
|
item["observed"] = true;
|
|
}
|
|
}
|
|
|
|
|
|
auto match = g_tles.getBestMatch(nanoTime(0, latestWN(0, svstats), latestTow(0, svstats))/1000000000.0,
|
|
sat.x, sat.y, sat.z);
|
|
|
|
if(match.distance < 200000) {
|
|
item["best-tle"] = match.name;
|
|
item["best-tle-norad"] = match.norad;
|
|
item["best-tle-int-desig"] = match.internat;
|
|
item["best-tle-dist"] = match.distance/1000.0;
|
|
|
|
item["tle-ecefX"] = match.ecefX/1000;
|
|
item["tle-ecefY"] = match.ecefY/1000;
|
|
item["tle-ecefZ"] = match.ecefZ/1000;
|
|
|
|
item["tle-eciX"] = match.eciX/1000;
|
|
item["tle-eciY"] = match.eciY/1000;
|
|
item["tle-eciZ"] = match.eciZ/1000;
|
|
|
|
item["tle-latitude"] = 180*match.latitude/M_PI;
|
|
item["tle-longitude"] = 180*match.longitude/M_PI;
|
|
item["tle-altitude"] = match.altitude;
|
|
}
|
|
auto name = fmt::sprintf("G%02d", ae.first);
|
|
item["name"]=name;
|
|
ret[name] = item;
|
|
}
|
|
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/observers.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
|
|
nlohmann::json ret = nlohmann::json::array();
|
|
for(const auto& src : g_srcpos) {
|
|
nlohmann::json obj;
|
|
obj["id"] = src.first;
|
|
auto latlonh = ecefToWGS84(src.second.pos.x, src.second.pos.y, src.second.pos.z);
|
|
get<0>(latlonh) *= 180.0/M_PI;
|
|
get<1>(latlonh) *= 180.0/M_PI;
|
|
get<0>(latlonh) = ((int)(10*get<0>(latlonh)))/10.0;
|
|
get<1>(latlonh) = ((int)(10*get<1>(latlonh)))/10.0;
|
|
|
|
get<2>(latlonh) = ((int)(10*get<2>(latlonh)))/10.0;
|
|
|
|
obj["latitude"] = get<0>(latlonh);
|
|
obj["longitude"] = get<1>(latlonh);
|
|
obj["last-seen"] = src.second.lastSeen;
|
|
obj["ground-speed"] = src.second.groundSpeed;
|
|
obj["swversion"] = src.second.swversion;
|
|
obj["hwversion"] = src.second.hwversion;
|
|
obj["mods"] = src.second.mods;
|
|
obj["serialno"] = src.second.serialno;
|
|
obj["clockoffsetns"]= src.second.clockOffsetNS;
|
|
obj["clockdriftns"]= src.second.clockOffsetDriftNS;
|
|
obj["clockacc"]= src.second.clockAccuracyNS;
|
|
obj["freqacc"]= src.second.freqAccuracyPS;
|
|
obj["uptime"]= src.second.uptime;
|
|
obj["githash"]= src.second.githash;
|
|
obj["owner"]= src.second.owner;
|
|
obj["vendor"]= src.second.vendor;
|
|
obj["remark"]= src.second.remark;
|
|
|
|
obj["acc"] = src.second.accuracy;
|
|
obj["h"] = get<2>(latlonh);
|
|
auto svstats = g_statskeeper.get();
|
|
nlohmann::json svs = nlohmann::json::object();
|
|
|
|
for(const auto& sv : svstats) {
|
|
if(auto iter = sv.second.perrecv.find(src.first); iter != sv.second.perrecv.end()) {
|
|
if(iter->second.db > 0 && time(0) - iter->second.t < 120) {
|
|
nlohmann::json svo = nlohmann::json::object();
|
|
svo["db"] = iter->second.db;
|
|
|
|
svo["elev"] = roundf(10.0*iter->second.el)/10.0;
|
|
svo["azi"] = roundf(10.0*iter->second.azi)/10.0;
|
|
|
|
Point sat;
|
|
|
|
if((sv.first.gnss == 0 || sv.first.gnss == 2) && sv.second.completeIOD()) {
|
|
svo["delta_hz"] = truncPrec(iter->second.deltaHz, 2);
|
|
auto hzCorrection = getHzCorrection(time(0), src.first , sv.first.gnss, sv.first.sigid, svstats);
|
|
if(hzCorrection)
|
|
svo["delta_hz_corr"] = truncPrec(iter->second.deltaHz - *hzCorrection, 2);
|
|
|
|
sv.second.getCoordinates(latestTow(sv.first.gnss, svstats), & sat);
|
|
}
|
|
if(sv.first.gnss == 3 && sv.second.ephBeidoumsg.sow >= 0 && sv.second.ephBeidoumsg.sqrtA != 0) {
|
|
getCoordinates(latestTow(sv.first.gnss, svstats), sv.second.ephBeidoumsg, &sat);
|
|
}
|
|
if(sv.first.gnss == 6 && sv.second.wn() > 0) {
|
|
getCoordinates(latestTow(6, svstats), sv.second.glonassMessage, &sat);
|
|
}
|
|
if(sat.x) {
|
|
Point our = g_srcpos[iter->first].pos;
|
|
svo["elev"] = roundf(10.0*getElevationDeg(sat, our))/10.0;
|
|
svo["azi"] = roundf(10.0*getAzimuthDeg(sat, our))/10.0;
|
|
}
|
|
|
|
svo["prres"] = truncPrec(iter->second.prres, 2);
|
|
svo["qi"] = iter->second.qi;
|
|
svo["used"] = iter->second.used;
|
|
svo["age-s"] = time(0) - iter->second.t;
|
|
svo["last-seen"] = iter->second.t;
|
|
svo["gnss"] = sv.first.gnss;
|
|
svo["sv"] = sv.first.sv;
|
|
svo["sigid"] = sv.first.sigid;
|
|
svo["fullName"] = makeSatIDName(sv.first);
|
|
svo["name"] = makeSatPartialName(sv.first);
|
|
|
|
svs[makeSatIDName(sv.first)] = svo;
|
|
}
|
|
|
|
}
|
|
}
|
|
obj["svs"]=svs;
|
|
|
|
ret.push_back(obj);
|
|
}
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/sv.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
string_view path = convert(req->path);
|
|
nlohmann::json ret = nlohmann::json::object();
|
|
|
|
SatID id;
|
|
auto pos = path.find("sv=");
|
|
if(pos == string::npos) {
|
|
return ret;
|
|
}
|
|
id.sv = atoi(&path[0] + pos+3);
|
|
|
|
pos = path.find("gnssid=");
|
|
if(pos == string::npos) {
|
|
return ret;
|
|
}
|
|
id.gnss = atoi(&path[0]+pos+7);
|
|
|
|
id.sigid = 1;
|
|
pos = path.find("sigid=");
|
|
if(pos != string::npos) {
|
|
id.sigid = atoi(&path[0]+pos+6);
|
|
}
|
|
|
|
auto svstats = g_statskeeper.get();
|
|
|
|
ret["sv"] = id.sv;
|
|
ret["gnssid"] =id.gnss;
|
|
ret["sigid"] = id.sigid;
|
|
auto iter = svstats.find(id);
|
|
if(iter == svstats.end())
|
|
return ret;
|
|
const auto& s= iter->second;
|
|
// XXX CONVERSION
|
|
/*
|
|
ret["a0"] = s.a0;
|
|
ret["a1"] = s.a1;
|
|
ret["a0g"] = s.a0g;
|
|
ret["a1g"] = s.a1g;
|
|
*/
|
|
if(id.gnss == 2) {
|
|
ret["sf1"] = s.galmsg.sf1;
|
|
ret["sf2"] = s.galmsg.sf2;
|
|
ret["sf3"] = s.galmsg.sf3;
|
|
ret["sf4"] = s.galmsg.sf4;
|
|
ret["sf5"] = s.galmsg.sf5;
|
|
ret["BGDE1E5a"] = s.galmsg.BGDE1E5a;
|
|
ret["BGDE1E5b"] = s.galmsg.BGDE1E5b;
|
|
ret["e5bdvs"]=s.galmsg.e5bdvs;
|
|
ret["e1bdvs"]=s.galmsg.e1bdvs;
|
|
ret["e5bhs"]=s.galmsg.e5bhs;
|
|
ret["e1bhs"]=s.galmsg.e1bhs;
|
|
}
|
|
// XXX CONVERSION
|
|
/*
|
|
ret["ai0"] = s.ai0;
|
|
ret["ai1"] = s.ai1;
|
|
ret["ai2"] = s.ai2;
|
|
*/
|
|
ret["wn"] = s.wn();
|
|
ret["tow"] = s.tow();
|
|
// XXX CONVERSION
|
|
/*
|
|
ret["dtLS"] = s.dtLS;
|
|
ret["dtLSF"] = s.dtLSF;
|
|
ret["wnLSF"] = s.wnLSF;
|
|
ret["dn"] = s.dn;
|
|
*/
|
|
|
|
if(id.gnss == 3 && svstats[id].ephBeidoumsg.sow >= 0 && svstats[id].ephBeidoumsg.sqrtA != 0) {
|
|
const auto& iod = svstats[id].ephBeidoumsg;
|
|
ret["e"] = iod.getE();
|
|
ret["omega"] = iod.getOmega();
|
|
ret["sqrtA"]= iod.getSqrtA();
|
|
ret["M0"] = iod.getM0();
|
|
ret["i0"] = iod.getI0();
|
|
ret["delta-n"] = iod.getDeltan();
|
|
ret["omega-dot"] = iod.getOmegadot();
|
|
ret["omega0"] = iod.getOmega0();
|
|
ret["idot"] = iod.getIdot();
|
|
ret["t0e"] = iod.getT0e();
|
|
ret["t0c"] = iod.getT0c();
|
|
ret["cuc"] = iod.getCuc();
|
|
ret["cus"] = iod.getCus();
|
|
ret["crc"] = iod.getCrc();
|
|
ret["crs"] = iod.getCrs();
|
|
ret["cic"] = iod.getCic();
|
|
ret["cis"] = iod.getCis();
|
|
// ret["sisa"] = iod.sisa;
|
|
ret["af0"] = iod.a0;
|
|
ret["af1"] = iod.a1;
|
|
ret["af2"] = iod.a2;
|
|
Point p;
|
|
s.getCoordinates(s.tow(), &p);
|
|
ret["x"] = p.x;
|
|
ret["y"] = p.y;
|
|
ret["z"] = p.z;
|
|
auto longlat = getLongLat(p.x, p.y, p.z);
|
|
ret["longitude"] = 180.0*longlat.first/M_PI;
|
|
ret["latitude"] = 180.0*longlat.second/M_PI;
|
|
|
|
}
|
|
else if(svstats[id].completeIOD()) {
|
|
const auto& iod = svstats[id].liveIOD();
|
|
ret["iod"]= svstats[id].liveIOD().getIOD();
|
|
ret["e"] = iod.getE();
|
|
ret["omega"] = iod.getOmega();
|
|
ret["sqrtA"]= iod.getSqrtA();
|
|
ret["M0"] = iod.getM0();
|
|
ret["i0"] = iod.getI0();
|
|
ret["delta-n"] = iod.getDeltan();
|
|
ret["omega-dot"] = iod.getOmegadot();
|
|
ret["omega0"] = iod.getOmega0();
|
|
ret["idot"] = iod.getIdot();
|
|
ret["t0e"] = iod.getT0e();
|
|
// XXX conversion
|
|
// ret["t0c"] = iod.getT0c();
|
|
ret["cuc"] = iod.getCuc();
|
|
ret["cus"] = iod.getCus();
|
|
ret["crc"] = iod.getCrc();
|
|
ret["crs"] = iod.getCrs();
|
|
ret["cic"] = iod.getCic();
|
|
ret["cis"] = iod.getCis();
|
|
// XXX conversion
|
|
/*
|
|
ret["sisa"] = iod.sisa;
|
|
ret["af0"] = iod.af0;
|
|
ret["af1"] = iod.af1;
|
|
ret["af2"] = iod.af2;
|
|
*/
|
|
Point p;
|
|
s.getCoordinates(s.tow(), &p);
|
|
ret["x"] = p.x;
|
|
ret["y"] = p.y;
|
|
ret["z"] = p.z;
|
|
auto longlat = getLongLat(p.x, p.y, p.z);
|
|
ret["longitude"] = 180.0*longlat.first/M_PI;
|
|
ret["latitude"] = 180.0*longlat.second/M_PI;
|
|
|
|
}
|
|
|
|
nlohmann::json recvs = nlohmann::json::object();
|
|
|
|
for(const auto& perrecv : s.perrecv) {
|
|
nlohmann::json recv = nlohmann::json::object();
|
|
recv["db"] = perrecv.second.db;
|
|
recv["qi"] = perrecv.second.qi;
|
|
recv["used"] = perrecv.second.used;
|
|
recv["prres"] = truncPrec(perrecv.second.prres, 2);
|
|
recv["elev"] = perrecv.second.el;
|
|
recv["last-seen-s"] = time(0) - perrecv.second.t;
|
|
recvs[std::to_string(perrecv.first)] = recv;
|
|
|
|
}
|
|
ret["recvs"]=recvs;
|
|
return ret;
|
|
}
|
|
);
|
|
|
|
h2s.addHandler("/cov.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
vector<Point> sats;
|
|
auto galileoalma = g_galileoalmakeeper.get();
|
|
auto gpsalma = g_gpsalmakeeper.get();
|
|
auto beidoualma = g_beidoualmakeeper.get();
|
|
auto svstats = g_statskeeper.get();
|
|
// cout<<"pseudoTow "<<pseudoTow<<endl;
|
|
string_view path = convert(req->path);
|
|
|
|
bool doGalileo{true}, doGPS{false}, doBeidou{false}, doGlonass{false};
|
|
auto pos = path.find("gps=");
|
|
if(pos != string::npos) {
|
|
doGPS = (path[pos+4]=='1');
|
|
}
|
|
pos = path.find("galileo=");
|
|
if(pos != string::npos) {
|
|
doGalileo = (path[pos+8]=='1');
|
|
}
|
|
pos = path.find("beidou=");
|
|
if(pos != string::npos) {
|
|
doBeidou = (path[pos+7]=='1');
|
|
}
|
|
pos = path.find("glonass=");
|
|
if(pos != string::npos) {
|
|
doGlonass = (path[pos+8]=='1');
|
|
}
|
|
|
|
if(doGalileo)
|
|
for(const auto &g : galileoalma) {
|
|
Point sat;
|
|
getCoordinates(latestTow(2, svstats), g.second, &sat);
|
|
|
|
if(g.first < 0)
|
|
continue;
|
|
SatID id{2,(uint32_t)g.first,1};
|
|
if(svstats[id].completeIOD() && svstats[id].galmsg.sisa == 255) {
|
|
continue;
|
|
}
|
|
if(svstats[id].galmsg.e1bhs || svstats[id].galmsg.e1bdvs)
|
|
continue;
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
if(doGPS)
|
|
for(const auto &g : gpsalma) {
|
|
Point sat;
|
|
getCoordinates(latestTow(0, svstats), g.second, &sat);
|
|
|
|
if(g.first < 0)
|
|
continue;
|
|
SatID id{0,(uint32_t)g.first,0};
|
|
if(svstats[id].completeIOD() && svstats[id].gpsmsg.ura == 16) {
|
|
// cout<<"Skipping G"<<id.sv<<" because of URA"<<endl;
|
|
continue;
|
|
}
|
|
if(svstats[id].gpsmsg.gpshealth) {
|
|
// cout<<"Skipping G"<<id.sv<<" because of health"<<endl;
|
|
continue;
|
|
}
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
if(doBeidou)
|
|
for(const auto &g : beidoualma) {
|
|
Point sat;
|
|
getCoordinates(latestTow(3, svstats), g.second.alma, &sat);
|
|
|
|
if(g.first < 0)
|
|
continue;
|
|
SatID id{3,(uint32_t)g.first,0};
|
|
/*
|
|
if(svstats[id].completeIOD() && svstats[id].ura == 16) {
|
|
cout<<"Skipping G"<<id.sv<<" because of URA"<<endl;
|
|
continue;
|
|
}
|
|
*/
|
|
if(svstats[id].gpsmsg.gpshealth) {
|
|
// cout<<"Skipping C"<<id.sv<<" because of health"<<endl;
|
|
continue;
|
|
}
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
// fake almanac from svstats -- to avoid ejecting Glonasses to another galaxy due
|
|
// to excessive extrapolation, they freeze where they were last seen.
|
|
if(doGlonass)
|
|
for(const auto &s : svstats) {
|
|
if(s.first.gnss == 6 && s.second.wn() > 0) {
|
|
Point sat;
|
|
getCoordinates(s.second.tow(), s.second.glonassMessage, &sat);
|
|
if(svstats[s.first].glonassMessage.Bn & 7) {
|
|
continue;
|
|
}
|
|
sats.push_back(sat);
|
|
}
|
|
}
|
|
|
|
|
|
auto cov = emitCoverage(sats);
|
|
auto ret = nlohmann::json::array();
|
|
|
|
// ret =
|
|
// [ [90, [[-180, 3,2,1], [-179, 3,2,1], ... [180,3,2,1] ]]
|
|
// [89, [[-180, 4], [-179, 4], ... [180,2] ]]
|
|
// ]
|
|
for(const auto& latvect : cov) {
|
|
auto jslatvect = nlohmann::json::array();
|
|
auto jslongvect = nlohmann::json::array();
|
|
for(const auto& longpair : latvect.second) {
|
|
auto jsdatum = nlohmann::json::array();
|
|
jsdatum.push_back((int)get<0>(longpair));
|
|
jsdatum.push_back(get<1>(longpair));
|
|
jsdatum.push_back(get<2>(longpair));
|
|
jsdatum.push_back(get<3>(longpair));
|
|
jsdatum.push_back((int)(10*get<4>(longpair)));
|
|
jsdatum.push_back((int)(10*get<5>(longpair)));
|
|
jsdatum.push_back((int)(10*get<6>(longpair)));
|
|
|
|
jsdatum.push_back((int)(10*get<7>(longpair)));
|
|
jsdatum.push_back((int)(10*get<8>(longpair)));
|
|
jsdatum.push_back((int)(10*get<9>(longpair)));
|
|
|
|
jsdatum.push_back((int)(10*get<10>(longpair)));
|
|
jsdatum.push_back((int)(10*get<11>(longpair)));
|
|
jsdatum.push_back((int)(10*get<12>(longpair)));
|
|
jslongvect.push_back(jsdatum);
|
|
}
|
|
jslatvect.push_back(latvect.first);
|
|
jslatvect.push_back(jslongvect);
|
|
|
|
ret.push_back(jslatvect);
|
|
}
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/sbas.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
auto svstats = g_statskeeper.get();
|
|
auto sbas = g_sbaskeeper.get();
|
|
nlohmann::json ret = nlohmann::json::object();
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL,
|
|
NULL, H2O_STRLIT("max-age=3"));
|
|
|
|
// Access-Control-Allow-Origin
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
NULL, H2O_STRLIT("*"));
|
|
|
|
|
|
for(const auto& s: sbas) {
|
|
nlohmann::json item = nlohmann::json::object();
|
|
item["last-seen"] = s.second.status.d_lastSeen;
|
|
item["last-seen-s"] = time(0) - s.second.status.d_lastSeen;
|
|
|
|
item["last-type-0"] = s.second.status.d_lastDNU;
|
|
item["last-type-0-s"] = time(0) - s.second.status.d_lastDNU;
|
|
|
|
// this 59 is to make sure galmonmon doesn't trigger on a single message
|
|
if(s.second.status.d_lastSeen - s.second.status.d_lastDNU < 59)
|
|
item["health"]="DON'T USE";
|
|
else
|
|
item["health"]="OK";
|
|
|
|
nlohmann::json perrecv = nlohmann::json::object();
|
|
for(const auto& recv : s.second.perrecv) {
|
|
perrecv[to_string(recv.first)]["last-seen"] = recv.second.last_seen;
|
|
perrecv[to_string(recv.first)]["last-seen-s"] = time(0) - recv.second.last_seen;
|
|
}
|
|
item["perrecv"]=perrecv;
|
|
|
|
ret[to_string(s.first)]=item;
|
|
}
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/svs.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
auto svstats = g_statskeeper.get();
|
|
nlohmann::json ret = nlohmann::json::object();
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL,
|
|
NULL, H2O_STRLIT("max-age=3"));
|
|
|
|
// Access-Control-Allow-Origin
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
NULL, H2O_STRLIT("*"));
|
|
|
|
|
|
for(const auto& s: svstats) {
|
|
nlohmann::json item = nlohmann::json::object();
|
|
if(!s.second.tow()) // I know, I know, will suck briefly
|
|
continue;
|
|
|
|
item["gnssid"] = s.first.gnss;
|
|
item["svid"] = s.first.sv;
|
|
item["sigid"] = s.first.sigid;
|
|
// perhaps check ephBeidoumsg for sow >=0 as 'completeIOD'?
|
|
|
|
if(s.first.gnss == 3) { // beidou
|
|
item["sisa"]=humanUra(s.second.ephBeidoumsg.urai);
|
|
item["sisa-m"]=numUra(s.second.ephBeidoumsg.urai);
|
|
if(s.second.completeIOD()) {
|
|
item["eph-age-m"] = ephAge(s.second.tow(), s.second.ephBeidoumsg.getT0e())/60.0;
|
|
|
|
}
|
|
|
|
Point p;
|
|
if(s.second.ephBeidoumsg.sqrtA != 0) {
|
|
getCoordinates(s.second.tow(), s.second.ephBeidoumsg, &p);
|
|
auto beidoualma = g_beidoualmakeeper.get();
|
|
if(auto iter = beidoualma.find(s.first.sv); iter != beidoualma.end()) {
|
|
Point almapos;
|
|
getCoordinates(s.second.tow(), iter->second.alma, &almapos);
|
|
item["alma-dist"] = truncPrec(Vector(almapos, p).length()/1000.0, 1);
|
|
}
|
|
}
|
|
}
|
|
else if(s.first.gnss == 6) { // glonass
|
|
if(s.second.glonassMessage.FT < 16) {
|
|
item["sisa"] = humanFt(s.second.glonassMessage.FT);
|
|
item["sisa-m"] = numFt(s.second.glonassMessage.FT);
|
|
}
|
|
item["aode"] = s.second.ephglomsg.En*24;
|
|
item["iod"] = s.second.ephglomsg.Tb;
|
|
|
|
time_t glonow = nanoTime(6, s.second.wn(), s.second.tow())/1000000000.0;
|
|
// the 820368000 stuff is to rebase to 'day 1' so the % works
|
|
auto pseudoTow = (getGlonassT0e(glonow, s.second.glonassMessage.Tb) - 820368000) % (7*86400);
|
|
|
|
// cout<<std::fixed<<"wn: "<<s.second.wn() <<" tow "<<s.second.tow() <<" " << (int) s.second.glonassMessage.Tb << " " << glonow <<" " << humanTime(glonow) <<" ";
|
|
// cout<< pseudoTow <<" " << ephAge(s.second.tow(), getGlonassT0e(pseudoTow, s.second.glonassMessage.Tb))/60.0<<endl;
|
|
item["eph-age-m"] = ephAge(s.second.tow(), getGlonassT0e(pseudoTow, s.second.glonassMessage.Tb))/60.0;
|
|
|
|
// this should actually use local time!
|
|
Point p;
|
|
s.second.getCoordinates(s.second.tow(), &p);
|
|
auto match = g_tles.getBestMatch(nanoTime(s.first.gnss, s.second.wn(), s.second.tow())/1000000000.0,
|
|
p.x, p.y, p.z);
|
|
|
|
item["best-tle"] = match.name;
|
|
item["best-tle-norad"] = match.norad;
|
|
item["best-tle-int-desig"] = match.internat;
|
|
item["best-tle-dist"] = match.distance/1000.0;
|
|
}
|
|
if(s.second.completeIOD()) {
|
|
item["iod"]=s.second.liveIOD().getIOD();
|
|
if(s.first.gnss == 0 || s.first.gnss == 3) {
|
|
item["sisa"]=humanUra(s.second.gpsmsg.ura);
|
|
item["sisa-m"]=truncPrec(numUra(s.second.gpsmsg.ura),3);
|
|
// cout<<"Asked to convert "<<s.second.ura<<" for sv "<<s.first.first<<","<<s.first.second<<endl;
|
|
}
|
|
else {
|
|
item["sisa"]=humanSisa(s.second.galmsg.sisa);
|
|
item["sisa-m"]=numSisa(s.second.galmsg.sisa);
|
|
}
|
|
item["eph-age-m"] = ephAge(s.second.tow(), s.second.liveIOD().getT0e())/60;
|
|
// CONVERSION
|
|
item["af0"] = 0; // s.second.liveIOD().af0;
|
|
item["af1"] = 0; //s.second.liveIOD().af1;
|
|
item["af2"] = 0; // (int)s.second.liveIOD().af2;
|
|
// XXX conversion
|
|
// item["t0c"] = s.second.liveIOD().getT0c();
|
|
|
|
|
|
if(s.second.rtcmEphDelta.id.gnss == s.first.gnss && s.second.rtcmEphDelta.id.sv == s.first.sv && s.second.rtcmEphDelta.iod == s.second.liveIOD().getIOD()
|
|
&& abs(s.second.rtcmEphDelta.sow - s.second.tow())<60) {
|
|
const auto& ed = s.second.rtcmEphDelta;
|
|
item["rtcm-eph-delta-cm"] = truncPrec(sqrt(ed.radial*ed.radial + ed.along*ed.along + ed.cross*ed.cross)/10.0, 2);
|
|
item["rtcm-eph-radial-cm"] = truncPrec(ed.radial/10.0, 2);
|
|
item["rtcm-eph-along-cm"] = truncPrec(ed.along/10.0, 2);
|
|
item["rtcm-eph-cross-cm"] = truncPrec(ed.cross/10.0, 2);
|
|
item["rtcm-eph-dradial-cm"] = truncPrec(ed.dradial/10.0, 2);
|
|
item["rtcm-eph-dalong-cm"] = truncPrec(ed.dalong/10.0, 2);
|
|
item["rtcm-eph-dcross-cm"] = truncPrec(ed.dcross/10.0, 2);
|
|
|
|
const auto& cd = s.second.rtcmClockDelta;
|
|
// so.. the clock delta message has no iod. It relates to "the previous ephemeris message" it seems
|
|
if(cd.id.gnss == s.first.gnss && cd.id.sv == s.first.sv && abs(s.second.rtcmClockDelta.sow - s.second.tow())<60) {
|
|
auto corrclock = cd.dclock0 - 3e8*ldexp(s.second.galmsg.BGDE1E5a - s.second.galmsg.BGDE1E5b, -32);
|
|
item["rtcm-clock-dclock0"] = truncPrec(corrclock * 100, 2); // we turn this into centimeters
|
|
item["rtcm-clock-dclock1"] = truncPrec(cd.dclock1 * 100, 2); // we turn this into centimeters
|
|
item["rtcm-clock-dclock2"] = truncPrec(cd.dclock2 * 100, 2); // we turn this into centimeters
|
|
}
|
|
|
|
}
|
|
|
|
Point p;
|
|
Point core;
|
|
|
|
// this should actually use local time!
|
|
s.second.getCoordinates(s.second.tow(), &p);
|
|
auto match = g_tles.getBestMatch(nanoTime(s.first.gnss, s.second.wn(), s.second.tow())/1000000000.0,
|
|
p.x, p.y, p.z);
|
|
|
|
item["best-tle"] = match.name;
|
|
item["best-tle-norad"] = match.norad;
|
|
item["best-tle-int-desig"] = match.internat;
|
|
item["best-tle-dist"] = match.distance/1000.0;
|
|
|
|
|
|
item["x"]=p.x;
|
|
item["y"]=p.y;
|
|
item["z"]=p.z;
|
|
|
|
if(s.first.gnss == 0) {
|
|
auto gpsalma = g_gpsalmakeeper.get();
|
|
if(auto iter = gpsalma.find(s.first.sv); iter != gpsalma.end()) {
|
|
Point almapos;
|
|
getCoordinates(s.second.tow(), iter->second, &almapos);
|
|
item["alma-dist"] = Vector(almapos, p).length()/1000.0;
|
|
}
|
|
}
|
|
if(s.first.gnss == 2) {
|
|
if(s.second.osnmaTime >= 0 && ephAge(s.second.galmsg.tow, s.second.osnmaTime) < 60)
|
|
item["osnma"] = s.second.osnma;
|
|
|
|
auto galileoalma = g_galileoalmakeeper.get();
|
|
if(auto iter = galileoalma.find(s.first.sv); iter != galileoalma.end()) {
|
|
Point almapos;
|
|
getCoordinates(s.second.tow(), iter->second, &almapos);
|
|
item["alma-dist"] = Vector(almapos, p).length()/1000.0;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
// XX conversion
|
|
/*
|
|
item["a0"]=s.second.a0;
|
|
item["a1"]=s.second.a1;
|
|
*/
|
|
if(s.first.gnss == 0) { // GPS
|
|
auto deltaUTC = getGPSUTCOffset(s.second.tow(), s.second.wn(), s.second.gpsmsg);
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
|
|
// CONVERSION
|
|
// item["t0t"] = s.second.gpsmsg.t0t;
|
|
// item["wn0t"] = s.second.gpsmsg.wn0t;
|
|
}
|
|
if(s.first.gnss == 2) {
|
|
auto deltaUTC = s.second.galmsg.getUTCOffset(s.second.tow(), s.second.wn());
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
auto deltaGPS = s.second.galmsg.getGPSOffset(s.second.tow(), s.second.wn());
|
|
item["delta-gps"] = fmt::sprintf("%.1f %+.1f/d", deltaGPS.first, deltaGPS.second * 86400);
|
|
item["t0t"] = s.second.galmsg.t0t;
|
|
item["wn0t"] = s.second.galmsg.wn0t;
|
|
}
|
|
if(s.first.gnss == 3) {
|
|
auto deltaUTC = s.second.ephBeidoumsg.getUTCOffset(s.second.ephBeidoumsg.sow);
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
|
|
auto deltaGPS = s.second.ephBeidoumsg.getGPSOffset(s.second.ephBeidoumsg.sow);
|
|
item["delta-gps"] = fmt::sprintf("%.1f %+.1f/d", deltaGPS.first, deltaGPS.second * 86400);
|
|
item["t0g"] =0;
|
|
item["wn0g"] = 0;
|
|
|
|
item["t0t"] = 0;
|
|
item["wn0t"] = 0;
|
|
}
|
|
|
|
|
|
// XXX conversion
|
|
// item["dtLS"]=s.second.dtLS;
|
|
|
|
if(s.first.gnss == 3) { // beidou
|
|
item["a0g"]=s.second.ephBeidoumsg.a0gps;
|
|
item["a1g"]=s.second.ephBeidoumsg.a1gps;
|
|
if(s.second.ephBeidoumsg.aode >= 0)
|
|
item["aode"]=s.second.ephBeidoumsg.aode;
|
|
if(s.second.ephBeidoumsg.aodc >= 0)
|
|
item["aodc"]=s.second.ephBeidoumsg.aodc;
|
|
|
|
item["health"] = s.second.beidoumsg.sath1 ? "NOT OK" : "OK";
|
|
item["healthissue"] = !!s.second.beidoumsg.sath1;
|
|
}
|
|
|
|
if(s.first.gnss == 6) { // GLONASS
|
|
auto deltaUTC = s.second.glonassMessage.getUTCOffset(0);
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
|
|
auto deltaGPS = s.second.glonassMessage.getGPSOffset(0);
|
|
item["delta-gps"] = fmt::sprintf("%.1f %+.1f/d", deltaGPS.first, deltaGPS.second * 86400);
|
|
}
|
|
|
|
if(s.first.gnss == 2) { // galileo
|
|
item["a0g"]=s.second.galmsg.a0g;
|
|
item["a1g"]=s.second.galmsg.a1g;
|
|
item["t0g"]=s.second.galmsg.t0g;
|
|
item["wn0g"]=s.second.galmsg.wn0g;
|
|
|
|
item["health"] =
|
|
humanBhs(s.second.galmsg.e1bhs) +"/" +
|
|
humanBhs(s.second.galmsg.e5bhs) +"/" +
|
|
(s.second.galmsg.e1bdvs ? "NG" : "val") +"/"+
|
|
(s.second.galmsg.e5bdvs ? "NG" : "val");
|
|
item["e5bdvs"]=s.second.galmsg.e5bdvs;
|
|
item["e1bdvs"]=s.second.galmsg.e1bdvs;
|
|
item["e5bhs"]=s.second.galmsg.e5bhs;
|
|
item["e1bhs"]=s.second.galmsg.e1bhs;
|
|
item["healthissue"]=0;
|
|
if(s.second.galmsg.e1bhs == 2 || s.second.galmsg.e5bhs == 2)
|
|
item["healthissue"] = 1;
|
|
if(s.second.galmsg.e1bhs == 3 || s.second.galmsg.e5bhs == 3)
|
|
item["healthissue"] = 1;
|
|
if(s.second.galmsg.e1bdvs || s.second.galmsg.e5bdvs || s.second.galmsg.e1bhs == 1 || s.second.galmsg.e5bhs == 1)
|
|
item["healthissue"] = 2;
|
|
|
|
}
|
|
else if(s.first.gnss == 0) {
|
|
item["health"] =s.second.gpsmsg.gpshealth ? ("NOT OK: "+to_string(s.second.gpsmsg.gpshealth)) : string("OK");
|
|
item["healthissue"]= 2* !!s.second.gpsmsg.gpshealth;
|
|
}
|
|
else if(s.first.gnss == 6) { // GLONASS
|
|
item["health"]= s.second.glonassMessage.Bn ? ("NOT OK: "+to_string(s.second.glonassMessage.Bn)) : string("OK");
|
|
item["healthissue"]= 2* !!s.second.glonassMessage.Bn;
|
|
}
|
|
|
|
nlohmann::json perrecv = nlohmann::json::object();
|
|
for(const auto& pr : s.second.perrecv) {
|
|
if(pr.second.db > 0 || (time(0) - pr.second.t < 1800)) {
|
|
nlohmann::json det = nlohmann::json::object();
|
|
det["elev"] = pr.second.el;
|
|
Point sat;
|
|
|
|
if(s.second.completeIOD())
|
|
s.second.getCoordinates(latestTow(s.first.gnss, svstats), & sat);
|
|
|
|
if(sat.x) {
|
|
Point our = g_srcpos[pr.first].pos;
|
|
det["elev"] = roundf(10.0*getElevationDeg(sat, our))/10.0;
|
|
det["azi"] = roundf(10.0*getAzimuthDeg(sat, our))/10.0;
|
|
}
|
|
else
|
|
det["elev"] = pr.second.el;
|
|
|
|
det["db"] = pr.second.db;
|
|
det["last-seen-s"] = time(0) - pr.second.t;
|
|
det["prres"] = truncPrec(pr.second.prres, 2);
|
|
det["qi"] = pr.second.qi;
|
|
det["used"] = pr.second.used;
|
|
|
|
if(time(0) - pr.second.deltaHzTime < 60) {
|
|
det["delta_hz"] = pr.second.deltaHz;
|
|
auto hzCorrection = getHzCorrection(time(0), pr.first, s.first.gnss, s.first.sigid, svstats);
|
|
if(hzCorrection)
|
|
det["delta_hz_corr"] = pr.second.deltaHz - *hzCorrection;
|
|
}
|
|
|
|
|
|
perrecv[to_string(pr.first)]=det;
|
|
}
|
|
}
|
|
item["perrecv"]=perrecv;
|
|
|
|
item["last-seen-s"] = time(0) - nanoTime(s.first.gnss, s.second.wn(), s.second.tow())/1000000000;
|
|
|
|
if(s.second.latestDisco >=0) {
|
|
item["latest-disco"]= truncPrec(s.second.latestDisco, 3);
|
|
item["latest-disco-age"]= s.second.latestDiscoAge;
|
|
}
|
|
if(s.second.timeDisco > -100 && s.second.timeDisco < 100) {
|
|
item["time-disco"]= truncPrec(s.second.timeDisco, 1);
|
|
}
|
|
|
|
|
|
item["wn"] = s.second.wn();
|
|
item["tow"] = s.second.tow();
|
|
item["fullName"] = makeSatIDName(s.first);
|
|
item["name"] = makeSatPartialName(s.first);
|
|
ret[makeSatIDName(s.first)] = item;
|
|
}
|
|
return ret;
|
|
});
|
|
|
|
h2s.addHandler("/sbstatus.json", [](auto handler, auto req) {
|
|
addHeaders(req);
|
|
auto svstats = g_statskeeper.get();
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL,
|
|
NULL, H2O_STRLIT("max-age=3"));
|
|
|
|
// Access-Control-Allow-Origin
|
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
NULL, H2O_STRLIT("*"));
|
|
|
|
auto ret = nlohmann::json::array();
|
|
|
|
time_t now = time(0);
|
|
for(const auto& s: svstats) {
|
|
for(const auto& sb : s.second.sbas) {
|
|
if(now - sb.second.longterm.lastUpdate > 120)
|
|
continue;
|
|
if(now - sb.second.fast.lastUpdate > 120)
|
|
continue;
|
|
if(sb.second.fast.udrei == 14)
|
|
continue;
|
|
|
|
auto obj = nlohmann::json::object();
|
|
obj["name"] = makeSatPartialName(s.first);
|
|
obj["sbasprn"] = sb.first;
|
|
|
|
obj["dx"] = sb.second.longterm.dx;
|
|
obj["dy"] = sb.second.longterm.dy;
|
|
obj["dz"] = sb.second.longterm.dz;
|
|
obj["dai"] = sb.second.longterm.dai;
|
|
obj["iod8"] = sb.second.longterm.iod8;
|
|
obj["velocity"] = sb.second.longterm.velocity;
|
|
|
|
obj["last-longterm-update-s"] = now - sb.second.longterm.lastUpdate;
|
|
obj["last-fast-update-s"] = now - sb.second.fast.lastUpdate;
|
|
|
|
|
|
if(sb.second.longterm.velocity) {
|
|
obj["toa"] = sb.second.longterm.toa;
|
|
int toadelta = (now % 86400) - sb.second.longterm.toa;
|
|
|
|
obj["toa-delta"] = toadelta;
|
|
|
|
obj["ddx"] = sb.second.longterm.ddx;
|
|
obj["ddy"] = sb.second.longterm.ddy;
|
|
obj["ddz"] = sb.second.longterm.ddz;
|
|
obj["ddai"] = sb.second.longterm.ddai;
|
|
|
|
obj["adx"] = sb.second.longterm.dx + toadelta*sb.second.longterm.ddx;
|
|
obj["ady"] = sb.second.longterm.dy + toadelta*sb.second.longterm.ddy;
|
|
obj["adz"] = sb.second.longterm.dz + toadelta*sb.second.longterm.ddz;
|
|
obj["adai"] = sb.second.longterm.dai + toadelta*sb.second.longterm.ddai;
|
|
|
|
|
|
}
|
|
obj["correction"] = sb.second.fast.correction;
|
|
obj["udrei"] = sb.second.fast.udrei;
|
|
|
|
if(s.second.completeIOD() && (s.second.liveIOD().getIOD() & 0xff) == sb.second.longterm.iod8) {
|
|
Point sat;
|
|
|
|
s.second.getCoordinates(s.second.tow(), &sat);
|
|
Point adjsat=sat;
|
|
adjsat.x += sb.second.longterm.dx;
|
|
adjsat.y += sb.second.longterm.dy;
|
|
adjsat.z += sb.second.longterm.dz;
|
|
Point sbasCenter;
|
|
int prn = sb.first;
|
|
if(prn== 126 || prn == 136 || prn == 123)
|
|
sbasCenter = c_egnosCenter;
|
|
else if(prn == 138 || prn == 131 || prn == 133)
|
|
sbasCenter = c_waasCenter;
|
|
else
|
|
sbasCenter = Point{0,0,0};
|
|
|
|
double dist = Vector(sbasCenter, adjsat).length() - Vector(sbasCenter, sat).length();
|
|
obj["space-shift"] = dist;
|
|
dist -= sb.second.longterm.dai / 3;
|
|
obj["eph-shift"] = dist;
|
|
obj["range-shift"] = dist - sb.second.fast.correction;
|
|
}
|
|
|
|
|
|
ret.push_back(obj);
|
|
}
|
|
}
|
|
return ret;
|
|
});
|
|
|
|
|
|
h2s.addDirectory("/", htmlDir);
|
|
|
|
const char *address = localAddress.c_str();
|
|
std::thread ws([&h2s, address]() {
|
|
auto actx = h2s.addContext();
|
|
ComboAddress listenOn(address);
|
|
h2s.addListener(listenOn, actx);
|
|
cout<<"Listening on "<< listenOn.toStringWithPort() <<endl;
|
|
h2s.runLoop();
|
|
});
|
|
ws.detach();
|
|
|
|
try {
|
|
for(;;) {
|
|
static time_t lastWebSync;
|
|
if(lastWebSync != time(0)) {
|
|
g_statskeeper.set(g_svstats);
|
|
g_sbaskeeper.set(g_sbas);
|
|
g_galileoalmakeeper.set(g_galileoalma);
|
|
g_glonassalmakeeper.set(g_glonassalma);
|
|
g_beidoualmakeeper.set(g_beidoualma);
|
|
g_gpsalmakeeper.set(g_gpsalma);
|
|
lastWebSync = time(0);
|
|
}
|
|
|
|
char bert[6];
|
|
// I apologise deeply
|
|
if(fread(bert, 1, sizeof(bert), stdin) != sizeof(bert) || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') {
|
|
cerr<<"EOF or bad magic"<<endl;
|
|
break;
|
|
}
|
|
|
|
uint16_t len;
|
|
memcpy(&len, bert+4, 2);
|
|
len = htons(len);
|
|
char buffer[len];
|
|
if(fread(buffer, 1, len, stdin) != len)
|
|
break;
|
|
|
|
|
|
NavMonMessage nmm;
|
|
try {
|
|
if(!nmm.ParseFromString(string(buffer, len))) {
|
|
cerr<<"Parsing error nmm"<<endl;
|
|
continue;
|
|
}
|
|
|
|
}
|
|
catch(std::exception& e) {
|
|
cerr<<"nmm exception: "<<e.what();
|
|
continue;
|
|
}
|
|
catch(...) {
|
|
cerr<<"nmm exception"<<endl;
|
|
continue;
|
|
}
|
|
|
|
constexpr auto lastCovInterval = 300;
|
|
static time_t lastCovSyncPoint;
|
|
static time_t holdOffTime;
|
|
if(nmm.localutcseconds() / lastCovInterval > (unsigned int)lastCovSyncPoint) {
|
|
if(!lastCovSyncPoint)
|
|
holdOffTime = nmm.localutcseconds() + 600;
|
|
|
|
if((time_t)nmm.localutcseconds() > holdOffTime)
|
|
storeCoverageStats(idb, nmm.localutcseconds());
|
|
lastCovSyncPoint = nmm.localutcseconds() / lastCovInterval;
|
|
}
|
|
|
|
constexpr auto lastSelfstatInterval = 60;
|
|
static time_t lastSelfstatSyncPoint;
|
|
if(nmm.localutcseconds() / lastSelfstatInterval > (unsigned int)lastSelfstatSyncPoint) {
|
|
storeSelfStats(idb, nmm.localutcseconds());
|
|
lastSelfstatSyncPoint = nmm.localutcseconds() / lastSelfstatInterval;
|
|
}
|
|
|
|
#if 0
|
|
constexpr auto lastIonoInterval = 3600;
|
|
static time_t lastIonoSyncPoint;
|
|
if(nmm.localutcseconds() / lastIonoInterval > (unsigned int)lastIonoSyncPoint) {
|
|
// go over all satellites
|
|
NeQuickInst nqi;
|
|
// cerr<<"Looking at all sats"<<endl;
|
|
for(const auto& s : g_svstats) {
|
|
if(s.first.gnss!=2 || !s.second.completeIOD())
|
|
continue;
|
|
Point sat;
|
|
s.second.getCoordinates(s.second.tow(), &sat);
|
|
for(const auto& pr : s.second.perrecv) {
|
|
// cerr<<"Looking at "<<s.first.sv<<", "<<pr.second.db<<" "<<nmm.localutcseconds()<<", "<<pr.second.t<<" perrecv "<<pr.first<<" count " << g_srcpos.count(pr.first)<<endl;
|
|
if(g_srcpos.count(pr.first) && (pr.second.db > 0 || (nmm.localutcseconds() - pr.second.t < 120))) {
|
|
// cerr<<"Doing it -> "<< s.second.galmsg.ai0 <<" " <<s.second.galmsg.ai1<<" " << s.second.galmsg.ai2<< endl;
|
|
const auto& sp = g_srcpos[pr.first];
|
|
try {
|
|
// cerr<<"Obs "<<pr.first<<" pos: "<<sp.pos.x<<", "<<sp.pos.y<<", "<<sp.pos.z<<endl;
|
|
// cerr<<"Sat " <<s.first.sv<<" pos: "<<sat.x<<", "<<sat.y<<", "<<sat.z<<endl;
|
|
// auto obs = ecefToWGS84Deg(sp.pos.x, sp.pos.y, sp.pos.z);
|
|
// cerr<<"Observer height: "<<get<2>(obs)<<" meters, long "<<get<0>(obs)<<", lat "<<get<1>(obs)<<endl;
|
|
|
|
// auto satdegs = ecefToWGS84Deg(sat.x, sat.y, sat.z);
|
|
// cerr<<"Satellite height: "<<get<2>(satdegs)<<" meters, long "<<get<0>(satdegs)<<", lat "<<get<1>(satdegs)<< " -> elevation "<<getElevationDeg(sat, sp.pos)<<" deg"<<endl;
|
|
|
|
if(getElevationDeg(sat, sp.pos) < 0) // below the horizon
|
|
continue;
|
|
|
|
if(sp.pos.x==0 || isnan(sat.x)) {
|
|
// cerr<<"Fake position, skipping"<<endl;
|
|
continue;
|
|
}
|
|
double meters = (40.3e16/(1575420000.0*1575420000.0)) *
|
|
nqi.getTecu(nmm.localutcseconds(),
|
|
s.second.galmsg.ai0,
|
|
s.second.galmsg.ai1,
|
|
s.second.galmsg.ai2, sp.pos, sat);
|
|
|
|
idb.addValue(s.first, "nequick",
|
|
{
|
|
{"meters", meters}
|
|
}, nmm.localutcseconds() + nmm.localutcnanoseconds()/1000000000.0, pr.first);
|
|
// cerr<<"Meters: "<<meters<<endl;
|
|
}
|
|
catch(std::exception& e) {
|
|
cerr<<"Exception during NeQuick: "<<e.what()<<endl;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
// cerr<<"Done"<<endl;
|
|
lastIonoSyncPoint = nmm.localutcseconds() / lastIonoInterval;
|
|
}
|
|
#endif
|
|
|
|
|
|
if(nmm.type() == NavMonMessage::ReceptionDataType) {
|
|
int gnssid = nmm.rd().gnssid();
|
|
int sv = nmm.rd().gnsssv();
|
|
int sigid = nmm.rd().sigid();
|
|
if(gnssid==2 && sigid == 0)
|
|
sigid = 1;
|
|
|
|
SatID id{(uint32_t)gnssid, (uint32_t)sv, (uint32_t)sigid};
|
|
auto& perrecv = g_svstats[id].perrecv[nmm.sourceid()];
|
|
|
|
perrecv.db = nmm.rd().db();
|
|
|
|
perrecv.el = nmm.rd().el();
|
|
perrecv.azi = nmm.rd().azi();
|
|
|
|
perrecv.prres = nmm.rd().prres();
|
|
if(nmm.rd().has_qi())
|
|
perrecv.qi = nmm.rd().qi();
|
|
else
|
|
perrecv.qi = -1;
|
|
|
|
if(nmm.rd().has_used())
|
|
perrecv.used = nmm.rd().used();
|
|
else
|
|
perrecv.used = -1;
|
|
|
|
Point sat{0,0,0};
|
|
//cout<<"Got recdata for "<<id.gnss<<","<<id.sv<<","<<id.sigid<<": count="<<g_svstats.count(id)<<endl;
|
|
if(g_svstats[id].completeIOD() && !(random() % 16)) {
|
|
g_svstats[id].getCoordinates(g_svstats[id].tow(), &sat);
|
|
}
|
|
|
|
if(sat.x != 0 && g_srcpos[nmm.sourceid()].pos.x != 0) {
|
|
if(doLogRFData && !(random() % 16))
|
|
idb.addValue(id, "recdata",
|
|
{
|
|
{"db", nmm.rd().db()},
|
|
{"azi", getAzimuthDeg(sat, g_srcpos[nmm.sourceid()].pos)},
|
|
{"ele", getElevationDeg(sat, g_srcpos[nmm.sourceid()].pos)},
|
|
{"prres", nmm.rd().prres()},
|
|
{"qi", perrecv.qi},
|
|
{"used", perrecv.used}
|
|
}, nmm.localutcseconds() + nmm.localutcnanoseconds()/1000000000.0, nmm.sourceid());
|
|
}
|
|
|
|
}
|
|
else if(nmm.type() == NavMonMessage::GalileoInavType) {
|
|
basic_string<uint8_t> inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
|
|
int sv = nmm.gi().gnsssv();
|
|
int sigid;
|
|
if(nmm.gi().has_sigid())
|
|
sigid = nmm.gi().sigid();
|
|
else
|
|
sigid = 1; // default to E1B
|
|
SatID id={2,(uint32_t)sv,(uint32_t)sigid};
|
|
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
auto oldgm = svstat.galmsg;
|
|
|
|
auto& gm = svstat.galmsg;
|
|
unsigned int wtype = gm.parse(inav);
|
|
|
|
if(wtype == 5 && svstat.galmsgTyped.count(5)) {
|
|
const auto& old5gm = svstat.galmsgTyped[5];
|
|
if(make_tuple(old5gm.e5bhs, old5gm.e1bhs, old5gm.e5bdvs, old5gm.e1bdvs) !=
|
|
make_tuple(gm.e5bhs, gm.e1bhs, gm.e5bdvs, gm.e1bdvs)) {
|
|
cout<<humanTime(id.gnss, svstat.wn(), svstat.tow())<<" src "<<nmm.sourceid()<<" Galileo "<<id.sv <<" sigid "<<id.sigid<<" change in health: ["<<humanBhs(old5gm.e5bhs)<<", "<<humanBhs(old5gm.e1bhs)<<", "<<(int)old5gm.e5bdvs <<", " << (int)old5gm.e1bdvs<<"] -> ["<< humanBhs(gm.e5bhs)<<", "<< humanBhs(gm.e1bhs)<<", "<< (int)gm.e5bdvs <<", " << (int)gm.e1bdvs<<"], lastseen "<<ephAge(old5gm.tow, gm.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
}
|
|
|
|
svstat.galmsgTyped[wtype] = gm;
|
|
|
|
if(wtype == 1 || wtype == 2 || wtype == 3 || wtype == 4) {
|
|
if(nmm.gi().has_reserved1()) {
|
|
static string off;
|
|
if(off.empty())
|
|
off.append(5, (char)0);
|
|
svstat.osnma = nmm.gi().reserved1() != off;
|
|
if(svstat.osnma) // eventually this will become too much but ok for now
|
|
idb.addValue(id, "osnma", {{"wtype", wtype}, {"field", makeHexDump(nmm.gi().reserved1())}}, satUTCTime(id));
|
|
svstat.osnmaTime = gm.tow;
|
|
}
|
|
idb.addValue(id, "ephemeris", {{"iod-live", svstat.galmsg.iodnav},
|
|
{"eph-age", ephAge(gm.tow, gm.getT0e())}}, satUTCTime(id));
|
|
|
|
int w = 1;
|
|
for(; w < 5; ++w) {
|
|
if(!svstat.galmsgTyped.count(w))
|
|
break;
|
|
if(w > 1 && svstat.galmsgTyped[w-1].iodnav != svstat.galmsgTyped[w].iodnav)
|
|
break;
|
|
}
|
|
if(w==5) { // have complete new ephemeris
|
|
if(svstat.ephgalmsg.iodnav != svstat.galmsgTyped[1].iodnav) {
|
|
svstat.oldephgalmsg = svstat.ephgalmsg;
|
|
svstat.ephgalmsg = svstat.galmsgTyped[wtype];
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
}
|
|
}
|
|
|
|
svstat.perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
|
|
if(wtype >=1 && wtype <= 4) { // ephemeris
|
|
if(wtype == 3) {
|
|
idb.addValue(id, "sisa", {{"value", svstat.galmsg.sisa}}, satUTCTime(id));
|
|
}
|
|
else if(wtype == 4) {
|
|
idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow()).first},
|
|
{"t0c", svstat.galmsg.t0c*60}, // getT0c()??
|
|
{"af0", svstat.galmsg.af0},
|
|
{"af1", svstat.galmsg.af1},
|
|
{"af2", svstat.galmsg.af2}}, satUTCTime(id));
|
|
|
|
|
|
if(oldgm.af0 && oldgm.t0c != svstat.galmsg.t0c) {
|
|
auto oldOffset = oldgm.getAtomicOffset(svstat.tow());
|
|
auto newOffset = svstat.galmsg.getAtomicOffset(svstat.tow());
|
|
svstat.timeDisco = oldOffset.first - newOffset.first;
|
|
if(fabs(svstat.timeDisco) < 10000)
|
|
idb.addValue(id, "clock_jump_ns", {
|
|
{"jump", svstat.timeDisco},
|
|
{"duration", ephAge(svstat.galmsg.t0c * 60, oldgm.t0c * 60)},
|
|
{"old-af0", oldgm.af0},
|
|
{"old-af1", oldgm.af1},
|
|
{"old-af2", oldgm.af2},
|
|
{"old-t0c", oldgm.t0c * 60},
|
|
{"new-af0", svstat.galmsg.af0},
|
|
{"new-af1", svstat.galmsg.af1},
|
|
{"new-af2", svstat.galmsg.af2},
|
|
{"new-t0c", svstat.galmsg.t0c * 60}
|
|
|
|
}, satUTCTime(id));
|
|
}
|
|
}
|
|
}
|
|
else if(wtype == 5) {
|
|
idb.addValue(id, "iono", {
|
|
{"ai0", svstat.galmsg.ai0},
|
|
{"ai1", svstat.galmsg.ai1},
|
|
{"ai2", svstat.galmsg.ai2},
|
|
{"sf1", svstat.galmsg.sf1},
|
|
{"sf2", svstat.galmsg.sf2},
|
|
{"sf3", svstat.galmsg.sf3},
|
|
{"sf4", svstat.galmsg.sf4},
|
|
{"sf5", svstat.galmsg.sf5}}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "galbgd", {
|
|
{"BGDE1E5a", svstat.galmsg.BGDE1E5a},
|
|
{"BGDE1E5b", svstat.galmsg.BGDE1E5b}}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "galhealth", {
|
|
{"e1bhs", svstat.galmsg.e1bhs},
|
|
{"e5bhs", svstat.galmsg.e5bhs},
|
|
{"e5bdvs", svstat.galmsg.e5bdvs},
|
|
{"e1bdvs", svstat.galmsg.e1bdvs}}, satUTCTime(id));
|
|
}
|
|
else if(wtype == 6) { // GST-UTC
|
|
const auto& sv = svstat;
|
|
g_GSTUTCOffset = sv.galmsg.getUTCOffset(sv.tow(), sv.wn()).first;
|
|
idb.addValue(id, "utcoffset", {
|
|
{"a0", sv.galmsg.a0},
|
|
{"a1", sv.galmsg.a1},
|
|
{"t0t", sv.galmsg.t0t},
|
|
{"delta", g_GSTUTCOffset}
|
|
},
|
|
satUTCTime(id));
|
|
|
|
g_dtLS = sv.galmsg.dtLS;
|
|
}
|
|
else if(wtype == 7) {
|
|
// this contains first part of alma1
|
|
}
|
|
else if(wtype == 8) {
|
|
if(gm.tow - svstat.galmsgTyped[7].tow < 5 &&
|
|
svstat.galmsgTyped[7].alma1.svid && gm.iodalmanac == svstat.galmsgTyped[7].iodalmanac) {
|
|
// cout<<(int)wtype<<" alma-sv "<<gm.alma1.svid<< " "<<gm.alma1.deltaSqrtA<<" " <<gm.alma1.t0almanac << endl;
|
|
g_galileoalma[gm.alma1.svid] = gm.alma1;
|
|
}
|
|
|
|
}
|
|
else if(wtype == 9) {
|
|
if(gm.tow - svstat.galmsgTyped[8].tow <= 30 &&
|
|
svstat.galmsgTyped[8].alma2.svid && gm.iodalmanac == svstat.galmsgTyped[8].iodalmanac) {
|
|
// cout<<(int)wtype<<" alma-sv "<<gm.alma2.svid<< " "<<gm.alma2.deltaSqrtA<<" " <<gm.alma2.t0almanac << endl;
|
|
g_galileoalma[gm.alma2.svid] = gm.alma2;
|
|
}
|
|
}
|
|
|
|
else if(wtype == 10) { // GSTT GPS
|
|
if(gm.tow - svstat.galmsgTyped[9].tow < 5 &&
|
|
svstat.galmsgTyped[9].alma3.svid && gm.iodalmanac == svstat.galmsgTyped[9].iodalmanac) {
|
|
// cout<<(int)wtype<<" alma-sv "<<gm.alma3.svid<< " "<<gm.alma3.deltaSqrtA<<" " <<gm.alma3.t0almanac << endl;
|
|
// cout<<(int)wtype<<"b alma-sv "<<svstat.galmsgTyped[9].alma3.svid<< " "<<svstat.galmsgTyped[9].alma3.deltaSqrtA<<" " <<svstat.galmsgTyped[9].alma3.t0almanac << endl;
|
|
g_galileoalma[gm.alma3.svid] = gm.alma3;
|
|
}
|
|
g_GSTGPSOffset = gm.getGPSOffset(gm.tow, gm.wn).first;
|
|
idb.addValue(id, "gpsoffset", {{"a0g", svstat.galmsg.a0g},
|
|
{"a1g", svstat.galmsg.a1g},
|
|
{"t0g", svstat.galmsg.t0g},
|
|
{"wn0g", svstat.galmsg.wn0g},
|
|
{"delta", g_GSTGPSOffset}
|
|
}, satUTCTime(id));
|
|
|
|
}
|
|
}
|
|
else if(nmm.type() == NavMonMessage::GalileoCnavType) {
|
|
SatID id={2,(uint32_t) nmm.gc().gnsssv(), 8}; // E6
|
|
idb.addValue(id, "galcnav", {{"msg", makeHexDump(nmm.gc().contents())}},
|
|
nmm.localutcseconds());
|
|
// ... no idea what this contains
|
|
}
|
|
else if(nmm.type() == NavMonMessage::GalileoFnavType) {
|
|
basic_string<uint8_t> fnav((uint8_t*)nmm.gf().contents().c_str(), nmm.gf().contents().size());
|
|
int sv = nmm.gf().gnsssv();
|
|
SatID id={2,(uint32_t)sv,6}; // E5a
|
|
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
auto oldgm = svstat.galmsg;
|
|
|
|
auto& gm = svstat.galmsg;
|
|
unsigned int wtype = gm.parseFnav(fnav);
|
|
|
|
|
|
if(wtype == 1 && svstat.galmsgTyped.count(1)) {
|
|
const auto& old5gm = svstat.galmsgTyped[1];
|
|
if(make_tuple(old5gm.e5ahs, old5gm.e1bhs, old5gm.e5advs, old5gm.e1bdvs) !=
|
|
make_tuple(gm.e5ahs, gm.e1bhs, gm.e5advs, gm.e1bdvs)) {
|
|
cout<<humanTime(id.gnss, svstat.wn(), svstat.tow())<<" src "<<nmm.sourceid()<<" Galileo "<<id.sv <<" sigid "<<id.sigid<<" change in health: ["<<humanBhs(old5gm.e5ahs)<<", "<<humanBhs(old5gm.e1bhs)<<", "<<(int)old5gm.e5advs <<", " << (int)old5gm.e1bdvs<<"] -> ["<< humanBhs(gm.e5ahs)<<", "<< humanBhs(gm.e1bhs)<<", "<< (int)gm.e5advs <<", " << (int)gm.e1bdvs<<"], lastseen "<<ephAge(old5gm.tow, gm.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
}
|
|
|
|
svstat.galmsgTyped[wtype] = gm;
|
|
|
|
if(wtype == 1 || wtype == 2 || wtype == 3 || wtype == 4) {
|
|
idb.addValue(id, "ephemeris", {{"iod-live", svstat.galmsg.iodnav},
|
|
{"eph-age", ephAge(gm.tow, gm.getT0e())}}, satUTCTime(id));
|
|
|
|
int w = 1;
|
|
for(; w < 5; ++w) {
|
|
if(!svstat.galmsgTyped.count(w))
|
|
break;
|
|
if(w > 1 && svstat.galmsgTyped[w-1].iodnav != svstat.galmsgTyped[w].iodnav)
|
|
break;
|
|
}
|
|
if(w==5) { // have complete new ephemeris
|
|
|
|
if(svstat.ephgalmsg.iodnav != svstat.galmsgTyped[2].iodnav) {
|
|
// cout<<"New F/NAV ephemeris for "<<makeSatIDName(id)<<" iod " << svstat.galmsgTyped[1].iodnav << " t0e " << svstat.galmsgTyped[3].t0e << " af0 "<< gm.af0 <<" iod1 "<<svstat.galmsgTyped[1].iodnav;
|
|
/*
|
|
cout<<" iod2 "<<svstat.galmsgTyped[2].iodnav;
|
|
cout<<" iod3 "<<svstat.galmsgTyped[3].iodnav;
|
|
cout<<" iod4 "<<svstat.galmsgTyped[4].iodnav << endl;
|
|
*/
|
|
svstat.oldephgalmsg = svstat.ephgalmsg;
|
|
svstat.ephgalmsg = svstat.galmsg;
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
}
|
|
}
|
|
|
|
svstat.perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
|
|
if(wtype >=1 && wtype <= 4) { // ephemeris
|
|
if(wtype == 1) {
|
|
idb.addValue(id, "sisa", {{"value", svstat.galmsg.sisa}}, satUTCTime(id));
|
|
|
|
idb.addValue(id, "galbgd", {
|
|
{"BGDE1E5a", svstat.galmsg.BGDE1E5a},
|
|
}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "galhealth", {
|
|
{"e5ahs", svstat.galmsg.e5bhs},
|
|
{"e5advs", svstat.galmsg.e5bdvs}
|
|
}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow()).first},
|
|
{"t0c", svstat.galmsg.t0c*60}, // getT0c()??
|
|
{"af0", svstat.galmsg.af0},
|
|
{"af1", svstat.galmsg.af1},
|
|
{"af2", svstat.galmsg.af2}}, satUTCTime(id));
|
|
|
|
if(oldgm.af0 && oldgm.t0c != svstat.galmsg.t0c) {
|
|
auto oldOffset = oldgm.getAtomicOffset(svstat.tow());
|
|
auto newOffset = svstat.galmsg.getAtomicOffset(svstat.tow());
|
|
svstat.timeDisco = oldOffset.first - newOffset.first;
|
|
if(fabs(svstat.timeDisco) < 10000)
|
|
idb.addValue(id, "clock_jump_ns", {
|
|
{"jump", svstat.timeDisco},
|
|
{"duration", ephAge(svstat.galmsg.t0c * 60, oldgm.t0c * 60)},
|
|
{"old-af0", oldgm.af0},
|
|
{"old-af1", oldgm.af1},
|
|
{"old-af2", oldgm.af2},
|
|
{"old-t0c", oldgm.t0c * 60},
|
|
{"new-af0", svstat.galmsg.af0},
|
|
{"new-af1", svstat.galmsg.af1},
|
|
{"new-af2", svstat.galmsg.af2},
|
|
{"new-t0c", svstat.galmsg.t0c * 60}
|
|
|
|
}, satUTCTime(id));
|
|
}
|
|
|
|
idb.addValue(id, "iono", {
|
|
{"ai0", svstat.galmsg.ai0},
|
|
{"ai1", svstat.galmsg.ai1},
|
|
{"ai2", svstat.galmsg.ai2},
|
|
{"sf1", svstat.galmsg.sf1},
|
|
{"sf2", svstat.galmsg.sf2},
|
|
{"sf3", svstat.galmsg.sf3},
|
|
{"sf4", svstat.galmsg.sf4},
|
|
{"sf5", svstat.galmsg.sf5}}, satUTCTime(id));
|
|
|
|
}
|
|
}
|
|
else if(wtype == 4) {
|
|
const auto& sv = g_svstats[id];
|
|
g_GSTUTCOffset = sv.galmsg.getUTCOffset(sv.tow(), sv.wn()).first;
|
|
idb.addValue(id, "utcoffset", {
|
|
{"a0", sv.galmsg.a0},
|
|
{"a1", sv.galmsg.a1},
|
|
{"t0t", sv.galmsg.t0t},
|
|
{"delta", g_GSTUTCOffset}
|
|
},
|
|
satUTCTime(id));
|
|
|
|
g_dtLS = sv.galmsg.dtLS;
|
|
g_GSTGPSOffset = gm.getGPSOffset(gm.tow, gm.wn).first;
|
|
idb.addValue(id, "gpsoffset", {{"a0g", svstat.galmsg.a0g},
|
|
{"a1g", svstat.galmsg.a1g},
|
|
{"t0g", svstat.galmsg.t0g},
|
|
{"wn0g", svstat.galmsg.wn0g},
|
|
{"delta", g_GSTGPSOffset}
|
|
}, satUTCTime(id));
|
|
}
|
|
}
|
|
else if(nmm.type() == NavMonMessage::ObserverPositionType) {
|
|
g_srcpos[nmm.sourceid()].lastSeen = nmm.localutcseconds();
|
|
g_srcpos[nmm.sourceid()].pos.x = nmm.op().x();
|
|
g_srcpos[nmm.sourceid()].pos.y = nmm.op().y();
|
|
g_srcpos[nmm.sourceid()].pos.z = nmm.op().z();
|
|
if(nmm.op().has_groundspeed()) {
|
|
g_srcpos[nmm.sourceid()].groundSpeed = nmm.op().groundspeed();
|
|
}
|
|
|
|
g_srcpos[nmm.sourceid()].accuracy = nmm.op().acc();
|
|
// idb.addValueObserver(nmm.sourceid(), "accfix", nmm.op().acc(), nmm.localutcseconds());
|
|
|
|
auto latlonh = ecefToWGS84(nmm.op().x(), nmm.op().y(), nmm.op().z());
|
|
idb.addValueObserver(nmm.sourceid(), "fix", {
|
|
{"x", nmm.op().x()},
|
|
{"y", nmm.op().y()},
|
|
{"z", nmm.op().z()},
|
|
{"lat", 180.0*std::get<0>(latlonh)/M_PI},
|
|
{"lon", 180.0*std::get<1>(latlonh)/M_PI},
|
|
{"h", std::get<2>(latlonh)},
|
|
{"acc", nmm.op().acc()},
|
|
{"groundspeed", nmm.op().has_groundspeed() ? nmm.op().groundspeed() : -1.0}
|
|
},
|
|
nmm.localutcseconds());
|
|
}
|
|
else if(nmm.type() == NavMonMessage::RFDataType) {
|
|
SatID id{nmm.rfd().gnssid(), nmm.rfd().gnsssv(), nmm.rfd().sigid()};
|
|
|
|
|
|
if(id.gnss==2 && id.sigid == 0) // old ubxtool output gets this wrong
|
|
id.sigid = 1;
|
|
|
|
// some sources ONLY have RFDatatype & not ReceptionDataType
|
|
g_svstats[id].perrecv[nmm.sourceid()].db = nmm.rfd().cno();
|
|
|
|
|
|
if(doLogRFData)
|
|
idb.addValueObserver(nmm.sourceid(), "rfdata",
|
|
{{"carrierphase", nmm.rfd().carrierphase()},
|
|
{"doppler", nmm.rfd().doppler()},
|
|
{"locktime", nmm.rfd().locktimems()},
|
|
{"pseudorange", nmm.rfd().pseudorange()},
|
|
{"prstd", nmm.rfd().prstd()},
|
|
{"cpstd", nmm.rfd().cpstd()},
|
|
{"dostd", nmm.rfd().dostd()}
|
|
|
|
}, nanoTime(0, nmm.rfd().rcvwn(), nmm.rfd().rcvtow())/1000000000.0, id);
|
|
|
|
if(id.gnss == 3 && g_svstats[id].ephBeidoumsg.sow >= 0 && g_svstats[id].ephBeidoumsg.sqrtA != 0) {
|
|
double freq = 1561.098;
|
|
if(nmm.rfd().sigid() != 0)
|
|
freq = 1207.140;
|
|
|
|
// the magic 14 is because 'rcvtow()' is in GPS/Galileo TOW
|
|
// but BeiDou operates with 14 leap seconds less than GPS/Galileo
|
|
auto res = doDoppler(nmm.rfd().rcvtow()-14, g_srcpos[nmm.sourceid()].pos, g_svstats[id].ephBeidoumsg, freq * 1000000);
|
|
|
|
if(isnan(res.preddop)) {
|
|
cerr<<"Problem with doppler calculation for C"<<id.sv<<": "<<endl;
|
|
exit(1);
|
|
}
|
|
|
|
time_t t = utcFromGPS(nmm.rfd().rcvwn(), nmm.rfd().rcvtow());
|
|
if(t - g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime > 10) {
|
|
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHz = nmm.rfd().doppler() - res.preddop;
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime = t;
|
|
|
|
// idb.addValue(id, "delta_hz", nmm.rfd().doppler() - res.preddop);
|
|
auto corr = getHzCorrection(t, nmm.sourceid(), id.gnss, id.sigid, g_svstats);
|
|
if(corr) {
|
|
// idb.addValue(id, "delta_hz_cor", nmm.rfd().doppler() - res.preddop - (*corr));
|
|
Point sat;
|
|
getCoordinates(nmm.rfd().rcvtow(), g_svstats[id].ephBeidoumsg, &sat);
|
|
|
|
if(doLogRFData)
|
|
idb.addValue(id, "correlator",
|
|
{{"delta_hz_cor", nmm.rfd().doppler() - res.preddop - (*corr)},
|
|
{"delta_hz", nmm.rfd().doppler() - res.preddop},
|
|
{"elevation", getElevationDeg(sat, g_srcpos[nmm.sourceid()].pos)},
|
|
{"hz", nmm.rfd().doppler()},
|
|
{"prres", g_svstats[id].perrecv[nmm.sourceid()].prres},
|
|
{"qi", g_svstats[id].perrecv[nmm.sourceid()].qi},
|
|
{"used", g_svstats[id].perrecv[nmm.sourceid()].used},
|
|
{"db", g_svstats[id].perrecv[nmm.sourceid()].db}
|
|
}, nanoTime(0, nmm.rfd().rcvwn(), nmm.rfd().rcvtow())/1000000000.0, nmm.sourceid()); //this time is supplied in GPS timeframe
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
else if(g_svstats[id].completeIOD() &&
|
|
(id.gnss != 6 || !(random() % 16))) { // GLONASS is too slow
|
|
double freqMHZ = 1575.42;
|
|
if(id.gnss == 2 && id.sigid == 5) // this is exactly the beidou b2i freq?
|
|
freqMHZ = 1207.140;
|
|
|
|
auto res = g_svstats[id].doDoppler(nmm.rfd().rcvtow(), g_srcpos[nmm.sourceid()].pos,freqMHZ * 1000000);
|
|
|
|
Point sat;
|
|
g_svstats[id].getCoordinates(nmm.rfd().rcvtow(), &sat);
|
|
|
|
time_t t = utcFromGPS(nmm.rfd().rcvwn(), nmm.rfd().rcvtow());
|
|
// idb.addValueObserver((int)nmm.sourceid(), "orbit",
|
|
// {{"preddop", res.preddop},
|
|
// {"radvel", res.radvel}}, t, id);
|
|
|
|
|
|
|
|
if(t - g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime > 10) { // only replace after 5 seconds
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHz = nmm.rfd().doppler() - res.preddop;
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime = t;
|
|
// idb.addValue(id, "delta_hz", nmm.rfd().doppler() - res.preddop);
|
|
auto corr = getHzCorrection(t, nmm.sourceid(), id.gnss, id.sigid, g_svstats);
|
|
if(corr) {
|
|
// idb.addValue(id, "delta_hz_cor", nmm.rfd().doppler() - res.preddop - *corr, nmm.sourceid());
|
|
if(doLogRFData)
|
|
idb.addValue(id, "correlator",
|
|
{{"delta_hz_cor", nmm.rfd().doppler() - res.preddop - (*corr)},
|
|
{"delta_hz", nmm.rfd().doppler() - res.preddop},
|
|
{"hz", nmm.rfd().doppler()},
|
|
{"elevation", getElevationDeg(sat, g_srcpos[nmm.sourceid()].pos)},
|
|
{"prres", g_svstats[id].perrecv[nmm.sourceid()].prres},
|
|
{"qi", g_svstats[id].perrecv[nmm.sourceid()].qi},
|
|
{"used", g_svstats[id].perrecv[nmm.sourceid()].used},
|
|
{"db", g_svstats[id].perrecv[nmm.sourceid()].db}
|
|
|
|
}, t, nmm.sourceid());
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(nmm.type()== NavMonMessage::ObserverDetailsType) {
|
|
auto& o = g_srcpos[nmm.sourceid()];
|
|
o.serialno = nmm.od().serialno();
|
|
|
|
o.swversion = nmm.od().swversion();
|
|
o.hwversion = nmm.od().hwversion();
|
|
o.mods = nmm.od().modules();
|
|
if(nmm.od().has_clockoffsetns())
|
|
o.clockOffsetNS = nmm.od().clockoffsetns();
|
|
else
|
|
o.clockOffsetNS = -1;
|
|
|
|
if(nmm.od().has_clockoffsetdriftns())
|
|
o.clockOffsetDriftNS = nmm.od().clockoffsetdriftns();
|
|
else
|
|
o.clockOffsetDriftNS = -1;
|
|
|
|
if(nmm.od().has_clockaccuracyns())
|
|
o.clockAccuracyNS = nmm.od().clockaccuracyns();
|
|
else
|
|
o.clockAccuracyNS = -1;
|
|
|
|
if(nmm.od().has_freqaccuracyps())
|
|
o.freqAccuracyPS = nmm.od().freqaccuracyps();
|
|
else
|
|
o.freqAccuracyPS = -1;
|
|
|
|
if(nmm.od().has_uptime())
|
|
o.uptime = nmm.od().uptime();
|
|
else
|
|
o.uptime = -1;
|
|
|
|
if(nmm.od().has_recvgithash())
|
|
o.githash = nmm.od().recvgithash();
|
|
else
|
|
o.githash.clear();
|
|
|
|
|
|
o.vendor = nmm.od().vendor();
|
|
o.owner = nmm.od().owner();
|
|
o.remark = nmm.od().remark();
|
|
|
|
|
|
idb.addValueObserver(nmm.sourceid(), "observer_details", {
|
|
{"clock_offset_ns", o.clockOffsetNS},
|
|
{"clock_drift_ns", o.clockOffsetDriftNS},
|
|
{"clock_acc_ns", o.clockAccuracyNS},
|
|
{"freq_acc_ps", o.freqAccuracyPS},
|
|
{"uptime", o.uptime}
|
|
},
|
|
nmm.localutcseconds());
|
|
|
|
|
|
}
|
|
else if(nmm.type() == NavMonMessage::UbloxJammingStatsType) {
|
|
/*
|
|
cout<<"noisePerMS "<<nmm.ujs().noiseperms() << " agcCnt "<<
|
|
nmm.ujs().agccnt()<<" flags "<<nmm.ujs().flags()<<" jamind "<<
|
|
nmm.ujs().jamind()<<endl;
|
|
*/
|
|
idb.addValueObserver(nmm.sourceid(), "ubx_jamming", {
|
|
{"noise_per_ms", nmm.ujs().noiseperms()},
|
|
{"agccnt", nmm.ujs().agccnt()},
|
|
{"jamind", nmm.ujs().jamind()},
|
|
{"flags", nmm.ujs().flags()}
|
|
},
|
|
nmm.localutcseconds());
|
|
|
|
|
|
}
|
|
|
|
else if(nmm.type()== NavMonMessage::DebuggingType) {
|
|
if(doGalileoReportSpeedup)
|
|
continue; // speedup
|
|
|
|
auto ret = parseTrkMeas(basic_string<uint8_t>((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size()));
|
|
for(const auto& tss : ret) {
|
|
SatID id{static_cast<uint32_t>(tss.gnss), static_cast<uint32_t>(tss.sv), tss.gnss == 2 ? 1u : 0u};
|
|
if(g_svstats[id].completeIOD()) {
|
|
double freqMHZ = 1575.42;
|
|
double tsat = ldexp(1.0* tss.tr, -32) /1000.0;
|
|
auto res = g_svstats[id].doDoppler(tsat, g_srcpos[nmm.sourceid()].pos, freqMHZ * 1000000);
|
|
|
|
|
|
// idb.addValueObserver((int)nmm.sourceid(), "orbit",
|
|
// {{"preddop", res.preddop},
|
|
// {"radvel", res.radvel}}, (time_t)nmm.localutcseconds(), id);
|
|
|
|
// cout << " preddop "<<res.preddop <<" meas "<<tss.dopplerHz <<" delta " << (tss.dopplerHz - res.preddop);
|
|
double t = utcFromGPS(latestWN(0, g_svstats), tsat);
|
|
if(t - g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime > 5) { // only replace after 5 seconds
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHz = tss.dopplerHz - res.preddop;
|
|
g_svstats[id].perrecv[nmm.sourceid()].deltaHzTime = t;
|
|
// idb.addValue(id, "delta_hz", tss.dopplerHz - res.preddop);
|
|
|
|
auto corr = getHzCorrection(t, nmm.sourceid(), id.gnss, id.sigid, g_svstats);
|
|
if(corr) {
|
|
Point sat;
|
|
g_svstats[id].getCoordinates(tsat, &sat);
|
|
|
|
// idb.addValue(id, "delta_hz_cor", tss.dopplerHz - res.preddop - *corr, nmm.sourceid());
|
|
|
|
if(doLogRFData)
|
|
idb.addValue(id, "correlator",
|
|
{{"delta_hz_cor", tss.dopplerHz - res.preddop - *corr},
|
|
{"delta_hz", tss.dopplerHz - res.preddop},
|
|
{"hz", tss.dopplerHz},
|
|
{"elevation", getElevationDeg(sat, g_srcpos[nmm.sourceid()].pos)},
|
|
{"prres", g_svstats[id].perrecv[nmm.sourceid()].prres},
|
|
{"qi", g_svstats[id].perrecv[nmm.sourceid()].qi},
|
|
{"used", g_svstats[id].perrecv[nmm.sourceid()].used},
|
|
{"db", g_svstats[id].perrecv[nmm.sourceid()].db}
|
|
|
|
}, t, nmm.sourceid());
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
// cout<<endl;
|
|
}
|
|
}
|
|
else if(nmm.type()== NavMonMessage::GPSInavType) {
|
|
|
|
if(nmm.gpsi().sigid()) {
|
|
cout<<"ignoring sigid "<<nmm.gpsi().sigid()<<" for legacy GPS "<<nmm.gpsi().gnsssv()<<endl;
|
|
continue;
|
|
}
|
|
auto cond = getCondensedGPSMessage(std::basic_string<uint8_t>((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size()));
|
|
SatID id{nmm.gpsi().gnssid(), nmm.gpsi().gnsssv(), nmm.gpsi().sigid()};
|
|
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
auto oldsvstat = svstat;
|
|
uint8_t page;
|
|
auto& gm = svstat.gpsmsg;
|
|
auto oldgm = gm;
|
|
int frame = gm.parseGPSMessage(cond, &page);
|
|
|
|
if(frame == 1) {
|
|
|
|
idb.addValue(id, "clock", {{"offset_ns", getGPSAtomicOffset(svstat.tow(), svstat.gpsmsg).first},
|
|
{"t0c", 16*gm.t0c},
|
|
{"af0", 8*gm.af0},
|
|
{"af1", 8*gm.af1},
|
|
{"af2", 16*gm.af2}}, satUTCTime(id));
|
|
|
|
// cout<<"Got ura "<<gm.ura<<" for sv "<<id.first<<","<<id.second<<endl;
|
|
idb.addValue(id, "gpsura", {{"value", gm.ura}}, satUTCTime(id));
|
|
|
|
if(oldgm.af0 && oldgm.ura != gm.ura) { // XX find better way to check
|
|
cout<<humanTime(id.gnss, gm.wn, gm.tow)<<" src "<<nmm.sourceid()<<" wn "<<gm.wn <<" GPS "<<id.sv <<"@"<<id.sigid<<" change in URA ["<< humanUra(oldgm.ura) <<"] -> [" << humanUra(gm.ura)<<"] "<<(int)gm.ura<<", lastseen "<<ephAge(oldgm.tow, gm.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
|
|
if(oldgm.af0 && oldgm.gpshealth != gm.gpshealth) { // XX find better way to check
|
|
cout<<humanTime(id.gnss, gm.wn, gm.tow)<<" src " <<nmm.sourceid()<<" wn "<<gm.wn <<" GPS "<<id.sv <<"@"<<id.sigid<<" change in health ["<< (int)oldgm.gpshealth <<"] -> [" << (int)gm.gpshealth <<"], lastseen "<<ephAge(oldgm.tow, gm.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
|
|
|
|
// double age = ephAge(g_svstats[id].tow, g_svstats[id].t0c * 16);
|
|
|
|
if(oldgm.af0 && oldgm.t0c != gm.t0c) {
|
|
auto oldOffset = getGPSAtomicOffset(gm.tow, oldgm);
|
|
auto newOffset = getGPSAtomicOffset(gm.tow, gm);
|
|
svstat.timeDisco = oldOffset.first - newOffset.first;
|
|
if(fabs(svstat.timeDisco) < 10000) {
|
|
idb.addValue(id, "clock_jump_ns", {
|
|
{"jump", svstat.timeDisco},
|
|
{"duration", ephAge(gm.t0c * 16, oldgm.t0c * 16)},
|
|
{"old-af0", oldgm.af0},
|
|
{"old-af1", oldgm.af1},
|
|
{"old-af2", oldgm.af2},
|
|
{"old-t0c", oldgm.t0c * 16},
|
|
{"new-af0", gm.af0},
|
|
{"new-af1", gm.af1},
|
|
{"new-af2", gm.af2},
|
|
{"new-t0c", gm.t0c * 16}
|
|
|
|
}, satUTCTime(id));
|
|
|
|
}
|
|
}
|
|
}
|
|
else if(frame==2) {
|
|
if(oldgm.gpshealth != gm.gpshealth) {
|
|
cout<<humanTime(id.gnss, gm.wn, gm.tow)<<" src "<<nmm.sourceid()<<" GPS "<<id.sv <<"@"<<id.sigid<<" change in health: ["<< (int)oldgm.gpshealth<<"] -> ["<< (int)gm.gpshealth<<"] , lastseen "<<ephAge(oldgm.tow, gm.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
svstat.gpsmsg2 = gm;
|
|
idb.addValue(id, "gpshealth", {{"value", g_svstats[id].gpsmsg.gpshealth}}, satUTCTime(id));
|
|
|
|
if(svstat.gpsmsg2.gpsiod == svstat.gpsmsg3.gpsiod && gm.gpsiod != svstat.ephgpsmsg.gpsiod) {
|
|
svstat.oldephgpsmsg = svstat.ephgpsmsg;
|
|
svstat.ephgpsmsg = gm;
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
|
|
}
|
|
else if(frame == 3) {
|
|
svstat.gpsmsg3 = gm;
|
|
if(svstat.gpsmsg2.gpsiod == svstat.gpsmsg3.gpsiod && gm.gpsiod != svstat.ephgpsmsg.gpsiod) {
|
|
svstat.oldephgpsmsg = svstat.ephgpsmsg;
|
|
svstat.ephgpsmsg = gm;
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
}
|
|
else if(frame==4 && page==18) {
|
|
const auto& sv = g_svstats[id];
|
|
|
|
g_GPSUTCOffset = getGPSUTCOffset(sv.tow(), sv.wn(), gm).first;
|
|
idb.addValue(id, "utcoffset", {
|
|
{"a0", gm.a0},
|
|
{"a1", gm.a1},
|
|
{"delta", g_GPSUTCOffset}
|
|
},
|
|
satUTCTime(id));
|
|
}
|
|
else if((frame == 5 && page <= 24) || (frame==4 && page >=25 && page<=32)) {
|
|
g_gpsalma[gm.gpsalma.sv] = gm.gpsalma;
|
|
}
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
// XXX conversion possibly vital
|
|
// g_svstats[id].tow = nmm.gpsi().gnsstow();
|
|
// g_svstats[id].wn = nmm.gpsi().gnsswn();
|
|
// if(g_svstats[id].wn < 512) // XXX ROLLOVER
|
|
// g_svstats[id].wn += 2048;
|
|
}
|
|
else if(nmm.type() == NavMonMessage::RTCMMessageType) {
|
|
RTCMMessage rm;
|
|
rm.parse(nmm.rm().contents());
|
|
if(rm.type == 1057 || rm.type == 1240) {
|
|
for(const auto& ed : rm.d_ephs) {
|
|
auto iter = g_svstats.find(ed.id);
|
|
// XXX NAVCAST ONLY
|
|
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod && nmm.sourceid()==302)
|
|
iter->second.rtcmEphDelta = ed;
|
|
|
|
idb.addValue(ed.id, "rtcm-eph-correction", {
|
|
{"iod", ed.iod},
|
|
{"radial", ed.radial},
|
|
{"along", ed.along},
|
|
{"cross", ed.cross},
|
|
{"dradial", ed.dradial},
|
|
{"dalong", ed.dalong},
|
|
{"dcross", ed.dcross},
|
|
{"ssr-iod", rm.ssrIOD},
|
|
{"ssr-provider", rm.ssrProvider},
|
|
{"ssr-solution", rm.ssrSolution},
|
|
{"tow", rm.sow},
|
|
{"udi", rm.udi},
|
|
{"total-dist", sqrt(ed.radial*ed.radial + ed.along*ed.along + ed.cross*ed.cross)}},
|
|
nmm.localutcseconds(),
|
|
nmm.sourceid());
|
|
|
|
}
|
|
}
|
|
else if(rm.type == 1058 || rm.type == 1241) {
|
|
|
|
for(const auto& cd : rm.d_clocks) {
|
|
auto iter = g_svstats.find(cd.id);
|
|
if(iter != g_svstats.end() && nmm.sourceid()==302) /// XXX wrong
|
|
iter->second.rtcmClockDelta = cd;
|
|
|
|
idb.addValue(cd.id, "rtcm-clock-correction", {
|
|
{"dclock0", cd.dclock0},
|
|
{"dclock1", cd.dclock1},
|
|
{"dclock2", cd.dclock2},
|
|
{"ssr-iod", rm.ssrIOD},
|
|
{"ssr-provider", rm.ssrProvider},
|
|
{"ssr-solution", rm.ssrSolution},
|
|
{"tow", rm.sow},
|
|
{"udi", rm.udi}},
|
|
nmm.localutcseconds(),
|
|
nmm.sourceid());
|
|
|
|
}
|
|
}
|
|
else if(rm.type == 1059 || rm.type == 1242) {
|
|
for(const auto& dcb : rm.d_dcbs) {
|
|
idb.addValue(dcb.first, "rtcm-dcb", {
|
|
{"value", dcb.second}},
|
|
nmm.localutcseconds(),
|
|
nmm.sourceid());
|
|
}
|
|
}
|
|
else if(rm.type == 1060 || rm.type == 1243) {
|
|
for(const auto& ed : rm.d_ephs) {
|
|
auto iter = g_svstats.find(ed.id);
|
|
if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod && nmm.sourceid()==302)
|
|
iter->second.rtcmEphDelta = ed;
|
|
|
|
idb.addValue(ed.id, "rtcm-eph-correction", {
|
|
{"iod", ed.iod},
|
|
{"radial", ed.radial},
|
|
{"along", ed.along},
|
|
{"cross", ed.cross},
|
|
{"dradial", ed.dradial},
|
|
{"dalong", ed.dalong},
|
|
{"dcross", ed.dcross},
|
|
{"ssr-iod", rm.ssrIOD},
|
|
{"ssr-provider", rm.ssrProvider},
|
|
{"ssr-solution", rm.ssrSolution},
|
|
{"tow", rm.sow},
|
|
{"udi", rm.udi},
|
|
{"total-dist", sqrt(ed.radial*ed.radial + ed.along*ed.along + ed.cross*ed.cross)}},
|
|
nmm.localutcseconds(),
|
|
nmm.sourceid());
|
|
|
|
}
|
|
}
|
|
else if(rm.type == 1045 || rm.type == 1046) { // Galileo Ephemeris
|
|
// rm.d_gm will now contain an at least partially filled out Galileo ephemeris
|
|
// 1045 is F/NAV, 1046 is I/NAV
|
|
// we have no real need for the I/NAV since we have that in spades
|
|
|
|
if(rm.type == 1045) {
|
|
const auto& eg = rm.d_gm;
|
|
SatID id;
|
|
id.gnss = 2;
|
|
id.sv = rm.d_sv;
|
|
id.sigid = 6; // seems reasonable for E5a
|
|
|
|
static map<pair<int, int>, unsigned int> lastT0e;
|
|
|
|
pair<int, int> key(nmm.sourceid(), rm.d_sv);
|
|
if(!lastT0e.count(key) || lastT0e[key] != eg.t0e) {
|
|
idb.addValue(id, "ephemeris-actual", {
|
|
{"iod", eg.getIOD()},
|
|
{"t0e", eg.t0e},
|
|
{"sqrta", eg.sqrtA},
|
|
{"e", eg.e},
|
|
{"cuc", eg.cuc},
|
|
{"cus", eg.cus},
|
|
{"crc", eg.crc},
|
|
{"crs", eg.crs},
|
|
{"m0", eg.m0},
|
|
{"deltan", eg.deltan},
|
|
{"i0", eg.i0},
|
|
{"cic", eg.cic},
|
|
{"cis", eg.cis},
|
|
{"omegadot", eg.omegadot},
|
|
{"omega0", eg.omega0},
|
|
{"idot", eg.idot},
|
|
{"af0", eg.af0},
|
|
{"af1", eg.af1},
|
|
{"af2", eg.af2},
|
|
{"t0c", eg.t0c},
|
|
{"omega", eg.omega}}, nmm.localutcseconds(), nmm.sourceid());
|
|
}
|
|
lastT0e[key] = eg.t0e;
|
|
}
|
|
|
|
}
|
|
|
|
for(const auto& cd : rm.d_clocks) {
|
|
auto iter = g_svstats.find(cd.id);
|
|
if(iter != g_svstats.end() && nmm.sourceid()==302)
|
|
iter->second.rtcmClockDelta = cd;
|
|
|
|
idb.addValue(cd.id, "rtcm-clock-correction", {
|
|
{"dclock0", cd.dclock0},
|
|
{"dclock1", cd.dclock1},
|
|
{"dclock2", cd.dclock2},
|
|
{"ssr-iod", rm.ssrIOD},
|
|
{"ssr-provider", rm.ssrProvider},
|
|
{"ssr-solution", rm.ssrSolution},
|
|
{"tow", rm.sow},
|
|
{"iod", cd.iod},
|
|
{"udi", rm.udi}},
|
|
nmm.localutcseconds(),
|
|
nmm.sourceid());
|
|
|
|
}
|
|
|
|
}
|
|
else if(nmm.type()== NavMonMessage::GPSCnavType) {
|
|
SatID id{nmm.gpsc().gnssid(), nmm.gpsc().gnsssv(), nmm.gpsc().sigid()};
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
|
|
GPSCNavState gcns;
|
|
parseGPSCNavMessage(
|
|
std::basic_string<uint8_t>((uint8_t*)nmm.gpsc().contents().c_str(),
|
|
nmm.gpsc().contents().size()),
|
|
gcns);
|
|
// cout<<"Got a message from "<<makeSatIDName(id)<<endl;
|
|
|
|
// XXX conversion possibly vital
|
|
// svstat.tow = nmm.gpsc().gnsstow();
|
|
// svstat.wn = nmm.gpsc().gnsswn();
|
|
|
|
}
|
|
else if(nmm.type()== NavMonMessage::BeidouInavTypeD1) {
|
|
if(doGalileoReportSpeedup)
|
|
continue; // speedup
|
|
|
|
try {
|
|
SatID id{nmm.bid1().gnssid(), nmm.bid1().gnsssv(), nmm.bid1().sigid()};
|
|
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
uint8_t pageno;
|
|
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size()));
|
|
auto& bm = svstat.beidoumsg;
|
|
auto oldbm = bm;
|
|
int fraid=bm.parse(cond, &pageno);
|
|
// XXX conversion possibly vital
|
|
// svstat.tow = nmm.bid1().gnsstow();
|
|
// svstat.wn = nmm.bid1().gnsswn();
|
|
if(fraid == 1) {
|
|
|
|
if(oldbm.sath1 != bm.sath1) {
|
|
cout<<humanTime(id.gnss, svstat.wn(), svstat.tow())<<" wn "<<bm.wn<<" sow " <<bm.sow<<" BeiDou C"<<id.sv<<"@"<<id.sigid<<" health changed from "<<(int)oldbm.sath1 <<" to "<< (int)bm.sath1 <<", lastseen "<<ephAge(oldbm.sow, bm.sow)/3600.0<<" hours"<<endl;
|
|
}
|
|
|
|
idb.addValue(id, "clock", {{"offset_ns", 1000000.0*bm.getAtomicOffset().first},
|
|
{"t0c", bm.getT0c()},
|
|
{"af0", bm.a0 * 2},
|
|
{"af1", bm.a1 / 16},
|
|
{"af2", bm.a2 / 128}}, satUTCTime(id)); // scaled to galileo units
|
|
|
|
idb.addValue(id, "beidouhealth", {{"sath1", bm.sath1}}, satUTCTime(id));
|
|
idb.addValue(id, "beidouurai", {{"value", bm.urai}}, satUTCTime(id));
|
|
if(svstat.lastBeidouMessage1.wn >=0 && svstat.lastBeidouMessage1.t0c != bm.t0c) {
|
|
auto oldOffset = svstat.lastBeidouMessage1.getAtomicOffset(bm.sow);
|
|
auto newOffset = bm.getAtomicOffset(bm.sow);
|
|
svstat.timeDisco = oldOffset.first - newOffset.first;
|
|
if(fabs(svstat.timeDisco) < 10000)
|
|
idb.addValue(id, "clock_jump_ns", {{"jump", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
svstat.lastBeidouMessage1 = bm;
|
|
}
|
|
if(fraid == 2) {
|
|
svstat.lastBeidouMessage2 = bm;
|
|
}
|
|
if(fraid == 3) {
|
|
Point oldpoint, newpoint;
|
|
if(bm.sow - svstat.lastBeidouMessage2.sow == 6) {
|
|
if(svstat.ephBeidoumsg.getT0e() != svstat.beidoumsg.getT0e() && bm.sqrtA) {
|
|
svstat.oldephBeidoumsg = svstat.ephBeidoumsg;
|
|
svstat.ephBeidoumsg = bm;
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
}
|
|
}
|
|
else if((fraid == 4 && 1<= pageno && pageno <= 24) ||
|
|
(fraid == 5 && 1<= pageno && pageno <= 6) ||
|
|
(fraid == 5 && 11<= pageno && pageno <= 23) ) {
|
|
|
|
struct BeidouAlmanacEntry bae;
|
|
// bm.alma.AmEpID = svstat.ephBeidoumsg.alma.AmEpID; // this comes from older messages
|
|
|
|
if(processBeidouAlmanac(bm, bae)) {
|
|
g_beidoualma[bae.sv]=bae;
|
|
}
|
|
}
|
|
|
|
if(fraid==5 && pageno == 9) {
|
|
/*
|
|
svstat.a0g = bm.a0gps;
|
|
svstat.a1g = bm.a1gps;
|
|
*/
|
|
}
|
|
|
|
if(fraid==5 && pageno == 10) {
|
|
/*
|
|
svstat.a0 = bm.a0utc;
|
|
svstat.a1 = bm.a1utc;
|
|
*/
|
|
g_dtLSBeidou = bm.deltaTLS;
|
|
g_BeiDouUTCOffset = bm.getUTCOffset(bm.sow).first;
|
|
// cout<<"Beidou leap seconds: "<<g_dtLSBeidou<<endl;
|
|
}
|
|
}catch(std::exception& e) {
|
|
cerr<<"Beidou: "<<e.what()<<endl;
|
|
}
|
|
}
|
|
else if(nmm.type()== NavMonMessage::BeidouInavTypeD2) {
|
|
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid2().contents().c_str(), nmm.bid2().contents().size()));
|
|
/*
|
|
int fraid = getbitu(&cond[0], beidouBitconv(16), 3);
|
|
int sow = getbitu(&cond[0], beidouBitconv(19), 20);
|
|
int pnum = getbitu(&cond[0], beidouBitconv(43), 4);
|
|
int pre = getbitu(&cond[0], beidouBitconv(1), 11);
|
|
|
|
// cout<<"C"<< nmm.bid2().gnsssv() << " sent D2 message, pre "<<pre<<" sow "<<sow<<", FraID "<<fraid;
|
|
// if(fraid == 1)
|
|
// cout <<" pnum "<<pnum;
|
|
// cout<<endl;
|
|
*/
|
|
}
|
|
else if(nmm.type()== NavMonMessage::GlonassInavType) {
|
|
if(doGalileoReportSpeedup)
|
|
continue; // speedup
|
|
|
|
SatID id{nmm.gloi().gnssid(), nmm.gloi().gnsssv(), nmm.gloi().sigid()};
|
|
auto& svstat = g_svstats[id];
|
|
svstat.gnss = id.gnss;
|
|
auto& gm = svstat.glonassMessage;
|
|
auto oldgm = gm;
|
|
int strno = gm.parse(std::basic_string<uint8_t>((uint8_t*)nmm.gloi().contents().c_str(), nmm.gloi().contents().size()));
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
if(strno == 1 && gm.n4 != 0 && gm.NT !=0) {
|
|
// uint32_t glotime = gm.getGloTime(); // this starts GLONASS time at 31st of december 1995, 00:00 UTC
|
|
// CONVERSION, possibly vital
|
|
// svstat.wn = glotime / (7*86400);
|
|
// svstat.tow = glotime % (7*86400);
|
|
// cout<<"Glonass now: "<<humanTime(glotime + 820368000) << " n4 "<<(int)gm.n4<<" NT "<<(int)gm.NT<< " wn "<<svstat.wn <<" tow " <<svstat.tow<<endl;
|
|
}
|
|
else if(strno == 2) {
|
|
if(svstat.wn() > 0)
|
|
idb.addValue(id, "glohealth", {{"Bn", gm.Bn}}, satUTCTime(id));
|
|
if(oldgm.Bn != gm.Bn) {
|
|
cout<<humanTime(id.gnss, svstat.wn(), svstat.tow())<<" n4 "<< (int)gm.n4<<" NT " << (int)gm.NT<<" GLONASS R"<<id.sv<<"@"<<id.sigid<<" health changed from "<<(int)oldgm.Bn <<" to "<< (int)gm.Bn <<endl;
|
|
|
|
}
|
|
}
|
|
else if(strno == 4) {
|
|
// svstat.aode = gm.En * 24;
|
|
if(svstat.wn() > 0) {
|
|
idb.addValue(id, "glo_taun_ns", {{"value", gm.getTaunNS()}}, satUTCTime(id));
|
|
idb.addValue(id, "ft", {{"value", gm.FT}}, satUTCTime(id));
|
|
|
|
if(oldgm.taun && oldgm.taun != gm.taun) {
|
|
if(gm.getGloTime() - oldgm.getGloTime() < 300) {
|
|
svstat.timeDisco = gm.getTaunNS() - oldgm.getTaunNS();
|
|
idb.addValue(id, "clock_jump_ns", {{"jump", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
}
|
|
}
|
|
if(gm.x && gm.y && gm.z) {
|
|
if(svstat.ephglomsg.x != gm.x &&
|
|
svstat.ephglomsg.y != gm.y &&
|
|
svstat.ephglomsg.z != gm.z) {
|
|
svstat.oldephglomsg = svstat.ephglomsg;
|
|
svstat.ephglomsg = gm;
|
|
svstat.reportNewEphemeris(id, idb);
|
|
}
|
|
}
|
|
}
|
|
else if(strno == 5) {
|
|
g_GlonassUTCOffset = gm.getUTCOffset(0).first;
|
|
g_GlonassGPSOffset = gm.getGPSOffset(0).first;
|
|
idb.addValue(id, "utcoffset", {
|
|
{"tauc", gm.tauc},
|
|
{"delta", g_GlonassUTCOffset}
|
|
},
|
|
satUTCTime(id));
|
|
|
|
}
|
|
else if(strno == 6 || strno == 8 || strno == 10 || strno == 12 || strno == 14) {
|
|
svstat.glonassAlmaEven = {nmm.localutcseconds(), gm};
|
|
}
|
|
else if(strno == 7 || strno == 9 || strno == 11 || strno == 13 || strno == 15) {
|
|
if(nmm.localutcseconds() - svstat.glonassAlmaEven.first < 4 && svstat.glonassAlmaEven.second.strtype == gm.strtype -1) {
|
|
g_glonassalma[svstat.glonassAlmaEven.second.nA] = make_pair(svstat.glonassAlmaEven.second, gm);
|
|
}
|
|
}
|
|
// cout<<"GLONASS R"<<id.second<<" str "<<strno<<endl;
|
|
}
|
|
else if(nmm.type() == NavMonMessage::SBASMessageType) {
|
|
if(doGalileoReportSpeedup)
|
|
continue; // speedup
|
|
|
|
auto& sb = g_sbas[nmm.sbm().gnsssv()];
|
|
|
|
sb.perrecv[nmm.sourceid()].last_seen = nmm.localutcseconds();
|
|
|
|
basic_string<uint8_t> sbas((uint8_t*)nmm.sbm().contents().c_str(), nmm.sbm().contents().length());
|
|
auto delta = sb.status.parse(sbas, nmm.localutcseconds());
|
|
// fast correction - clogs the database, so dropping that for now
|
|
/*
|
|
for(const auto& f : delta.first) {
|
|
idb.addValue(f.id, "sbas_fast",
|
|
{{"correction", f.correction},
|
|
{"udrei", f.udrei}}, nmm.localutcseconds(),
|
|
nmm.sbm().gnsssv(), "sbas");
|
|
}
|
|
*/
|
|
for(const auto& lt : delta.second) {
|
|
auto iter = g_svstats.find(lt.id);
|
|
if(iter == g_svstats.end())
|
|
continue;
|
|
const auto& s = *iter;
|
|
bool haveEphemeris=false;
|
|
double spaceShift=0, ephShift = 0, rangeShift =0;
|
|
if(s.second.completeIOD() && (s.second.liveIOD().getIOD() & 0xff) == lt.iod8) {
|
|
Point sat;
|
|
|
|
s.second.getCoordinates(s.second.tow(), &sat);
|
|
Point adjsat=sat;
|
|
adjsat.x += lt.dx;
|
|
adjsat.y += lt.dy;
|
|
adjsat.z += lt.dz;
|
|
Point sbasCenter;
|
|
int prn = nmm.sbm().gnsssv();
|
|
if(prn== 126 || prn == 136 || prn == 123)
|
|
sbasCenter = c_egnosCenter;
|
|
else if(prn == 138 || prn == 131 || prn == 133)
|
|
sbasCenter = c_waasCenter;
|
|
else
|
|
sbasCenter = Point{0,0,0};
|
|
|
|
double dist = Vector(sbasCenter, adjsat).length() - Vector(sbasCenter, sat).length();
|
|
spaceShift = dist;
|
|
dist -= lt.dai / 3;
|
|
ephShift = dist;
|
|
rangeShift = dist - g_svstats[lt.id].sbas[nmm.sbm().gnsssv()].fast.correction;
|
|
haveEphemeris=true;
|
|
}
|
|
idb.addValue(lt.id,"sbas_lterm",
|
|
{
|
|
{"iod8", lt.iod8},
|
|
{"toa", lt.toa},
|
|
{"iodp", lt.iodp},
|
|
{"dx", lt.dx},
|
|
{"dy", lt.dy},
|
|
{"dz", lt.dz},
|
|
{"dai", lt.dai},
|
|
{"ddx", lt.ddx},
|
|
{"ddy", lt.ddy},
|
|
{"ddz", lt.ddz},
|
|
{"ddai", lt.ddai},
|
|
{"ephemeris", 1.0*haveEphemeris},
|
|
{"space_shift", spaceShift},
|
|
{"eph_shift", ephShift},
|
|
{"range_shift", rangeShift}
|
|
}, nmm.localutcseconds(), nmm.sbm().gnsssv(), "sbas");
|
|
|
|
}
|
|
|
|
if(nmm.localutcseconds() - sb.status.d_lastDNU > 120) {
|
|
for(const auto& c : sb.status.d_fast) {
|
|
g_svstats[c.first].sbas[nmm.sbm().gnsssv()].fast = c.second;
|
|
}
|
|
for(const auto& c : sb.status.d_longterm) {
|
|
g_svstats[c.first].sbas[nmm.sbm().gnsssv()].longterm = c.second;
|
|
}
|
|
}
|
|
}
|
|
else if(nmm.type() == NavMonMessage::SARResponseType) {
|
|
SatID id;
|
|
id.gnss = 2;
|
|
id.sv = nmm.sr().gnsssv();
|
|
id.sigid = nmm.sr().sigid();
|
|
string hexid = nmm.sr().identifier();
|
|
string hexstring;
|
|
for(int n = 0; n < 15; ++n)
|
|
hexstring+=fmt::sprintf("%x", (int)getbitu((unsigned char*)hexid.c_str(), 4 + 4*n, 4));
|
|
|
|
idb.addValue(id, "galsar", {{"mtype", (int) nmm.sr().type()},
|
|
{"midentifier", hexstring},
|
|
{"mcode", nmm.sr().code()},
|
|
{"mparams", makeHexDump(nmm.sr().params())}}, satUTCTime(id), nmm.sourceid());
|
|
|
|
}
|
|
else if(nmm.type() == NavMonMessage::TimeOffsetType) {
|
|
struct gnsstimeoffset
|
|
{
|
|
double offset{0};
|
|
double accuracy{-1}; // this is how we detect 'unset'
|
|
} gps, gal, glo, bds;
|
|
for(const auto& o : nmm.to().offsets()) {
|
|
if(o.gnssid() == 0) {
|
|
gps.offset = o.offsetns();
|
|
gps.accuracy = o.tacc();
|
|
} else if(o.gnssid() == 2) {
|
|
gal.offset = o.offsetns();
|
|
gal.accuracy = o.tacc();
|
|
} else if(o.gnssid() == 3) {
|
|
bds.offset = o.offsetns();
|
|
bds.accuracy = o.tacc();
|
|
} else if(o.gnssid() == 6) {
|
|
glo.offset = o.offsetns();
|
|
glo.accuracy = o.tacc();
|
|
}
|
|
}
|
|
idb.addValueObserver(nmm.sourceid(), "timeoffset",
|
|
{{"itow", nmm.to().itow()},
|
|
{gps.accuracy >= 0 ? "gps-offset" : nullptr, gps.offset},
|
|
{gal.accuracy >= 0 ? "gal-offset" : nullptr, gal.offset},
|
|
{glo.accuracy >= 0 ? "glo-offset": nullptr, glo.offset},
|
|
{bds.accuracy >= 0 ? "bds-offset": nullptr, bds.offset},
|
|
|
|
{"gps-tacc", gps.accuracy},
|
|
{"gal-tacc", gal.accuracy},
|
|
{"glo-tacc", glo.accuracy},
|
|
{"bds-tacc", bds.accuracy},
|
|
|
|
{(gal.accuracy >= 0 && gps.accuracy >=0) ? "gal-gps-offset" : nullptr, gal.offset - gps.offset},
|
|
{(gal.accuracy >= 0 && bds.accuracy >=0) ? "gal-bds-offset" : nullptr, gal.offset - bds.offset},
|
|
{(gal.accuracy >= 0 && glo.accuracy >=0) ? "gal-glo-offset" : nullptr, gal.offset - glo.offset},
|
|
{(gps.accuracy >= 0 && bds.accuracy >=0) ? "gps-bds-offset" : nullptr, gps.offset - bds.offset},
|
|
{(gps.accuracy >= 0 && glo.accuracy >=0) ? "gps-glo-offset" : nullptr, gps.offset - glo.offset},
|
|
{(bds.accuracy >= 0 && glo.accuracy >=0) ? "bds-glo-offset" : nullptr, bds.offset - glo.offset}},
|
|
nmm.localutcseconds());
|
|
}
|
|
else {
|
|
cout<<"Unknown type "<< (int)nmm.type()<<endl;
|
|
}
|
|
|
|
}
|
|
}
|
|
catch(EofException& e)
|
|
{}
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
cerr<<"Exiting because of fatal error "<<e.what()<<endl;
|
|
}
|
|
|
|
|
|
// dedup techniques
|
|
#if 0
|
|
/*
|
|
struct DedupKey
|
|
{
|
|
SatID id;
|
|
int wn;
|
|
int tow;
|
|
basic_string<uint8_t> contents;
|
|
bool operator<(const DedupKey& rhs) const
|
|
{
|
|
return tie(id, wn, tow, contents) <
|
|
tie(rhs.id, rhs.wn, rhs.tow, rhs.contents);
|
|
}
|
|
};
|
|
|
|
static set<DedupKey> s_dedup;
|
|
DedupKey dk{id, (int)nmm.gf().gnsswn(), (int)nmm.gf().gnsstow(), inav};
|
|
if(s_dedup.insert(dk).second == false) {
|
|
// cout<<"Dedup"<<endl;
|
|
continue;
|
|
}
|
|
if(s_dedup.size() > 10000)
|
|
s_dedup.clear();
|
|
*/
|
|
// XXX conversion, may be vital
|
|
// g_svstats[id].wn = nmm.gf().gnsswn();
|
|
#endif
|