2265 lines
85 KiB
C++
2265 lines
85 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 "CLI/CLI.hpp"
|
|
#include "gpscnav.hh"
|
|
#include "version.hh"
|
|
|
|
static char program[]="navparse";
|
|
|
|
using namespace std;
|
|
|
|
extern const char* g_gitHash;
|
|
|
|
struct ObserverPosition
|
|
{
|
|
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, ObserverPosition> g_srcpos;
|
|
|
|
|
|
struct SBASStatus
|
|
{
|
|
time_t last_seen{0};
|
|
time_t last_type_0{0};
|
|
struct PerRecv
|
|
{
|
|
time_t last_seen{0};
|
|
};
|
|
map<int, PerRecv> perrecv;
|
|
|
|
};
|
|
typedef map<int, SBASStatus> 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;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void SVIOD::addGalileoWord(std::basic_string_view<uint8_t> page)
|
|
{
|
|
uint8_t wtype = getbitu(&page[0], 0, 6);
|
|
words[wtype]=true;
|
|
gnssid = 2;
|
|
if(wtype == 1) {
|
|
t0e = getbitu(&page[0], 16, 14) * 60; // WE SCALE THIS FOR THE USER!
|
|
m0 = getbits(&page[0], 30, 32);
|
|
e = getbitu(&page[0], 62, 32);
|
|
sqrtA = getbitu(&page[0], 94, 32);
|
|
}
|
|
else if(wtype == 2) {
|
|
omega0 = getbits(&page[0], 16, 32);
|
|
i0 = getbits(&page[0], 48, 32);
|
|
omega = getbits(&page[0], 80, 32);
|
|
idot = getbits(&page[0], 112, 14);
|
|
}
|
|
else if(wtype == 3) {
|
|
omegadot = getbits(&page[0], 16, 24);
|
|
deltan = getbits(&page[0], 40, 16);
|
|
cuc = getbits(&page[0], 56, 16);
|
|
cus = getbits(&page[0], 72, 16);
|
|
crc = getbits(&page[0], 88, 16);
|
|
crs = getbits(&page[0], 104, 16);
|
|
sisa = getbitu(&page[0], 120, 8);
|
|
}
|
|
else if(wtype == 4) {
|
|
cic = getbits(&page[0], 22, 16);
|
|
cis = getbits(&page[0], 38, 16);
|
|
|
|
t0c = getbitu(&page[0], 54, 14);
|
|
af0 = getbits(&page[0], 68, 31);
|
|
af1 = getbits(&page[0], 99, 21);
|
|
af2 = getbits(&page[0], 120, 6);
|
|
/*
|
|
cout<<(int) t0c << " " <<(int) af0 <<" " <<(int) af1 <<" " <<(int) af2<<endl;
|
|
cout<<(int) t0c*60 << " " << (((double) af0) / (1ULL<<34))*1000000 <<" usec " << (((double) af1)/(1ULL << 46))*1000000000000 <<" ps/s"<<endl;
|
|
*/
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SVStat::completeIOD() const
|
|
{
|
|
for(const auto& iod : iods)
|
|
if(iod.second.complete())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint16_t SVStat::getIOD() const
|
|
{
|
|
for(const auto& iod : iods)
|
|
if(iod.second.complete())
|
|
return iod.first;
|
|
throw std::runtime_error("Asked for live IOD number, don't have one yet");
|
|
}
|
|
|
|
SVIOD SVStat::liveIOD() const
|
|
{
|
|
if(auto iter = iods.find(getIOD()); iter != iods.end())
|
|
return iter->second;
|
|
throw std::runtime_error("Asked for live IOD, don't have one yet");
|
|
}
|
|
|
|
void SVStat::checkCompleteAndClean(int iod)
|
|
{
|
|
if(iods[iod].complete()) {
|
|
for(const auto& i : iods) {
|
|
if(i.first != iod && i.second.complete())
|
|
prevIOD=i;
|
|
}
|
|
SVIOD latest = iods[iod];
|
|
|
|
decltype(iods) newiods; // XXX race condition here,
|
|
newiods[iod]=latest;
|
|
iods.swap(newiods); // try to keep it brief
|
|
}
|
|
}
|
|
|
|
void SVStat::addGalileoWord(std::basic_string_view<uint8_t> page)
|
|
{
|
|
uint8_t wtype = getbitu(&page[0], 0, 6);
|
|
if(wtype == 0) {
|
|
if(getbitu(&page[0], 6,2) == 2) {
|
|
wn = getbitu(&page[0], 96, 12);
|
|
if(tow != getbitu(&page[0], 108, 20)) {
|
|
cerr<<"wtype "<<wtype<<", was about to mis-set TOW, " <<tow<< " " <<getbitu(&page[0], 108, 20) <<endl;
|
|
}
|
|
|
|
}
|
|
}
|
|
else if(wtype >=1 && wtype <= 4) { // ephemeris
|
|
uint16_t iod = getbitu(&page[0], 6, 10);
|
|
iods[iod].addGalileoWord(page);
|
|
checkCompleteAndClean(iod);
|
|
}
|
|
else if(wtype==5) { // disturbance, health, time
|
|
ai0 = getbitu(&page[0], 6, 11);
|
|
ai1 = getbits(&page[0], 17, 11); // ai1 & 2 are signed, 0 not
|
|
ai2 = getbits(&page[0], 28, 14);
|
|
|
|
sf1 = getbitu(&page[0], 42, 1);
|
|
sf2 = getbitu(&page[0], 43, 1);
|
|
sf3 = getbitu(&page[0], 44, 1);
|
|
sf4 = getbitu(&page[0], 45, 1);
|
|
sf5 = getbitu(&page[0], 46, 1);
|
|
BGDE1E5a = getbits(&page[0], 47, 10);
|
|
BGDE1E5b = getbits(&page[0], 57, 10);
|
|
|
|
e5bhs = getbitu(&page[0], 67, 2);
|
|
e1bhs = getbitu(&page[0], 69, 2);
|
|
e5bdvs = getbitu(&page[0], 71, 1);
|
|
e1bdvs = getbitu(&page[0], 72, 1);
|
|
|
|
|
|
wn = getbitu(&page[0], 73, 12);
|
|
if(tow != getbitu(&page[0], 85, 20))
|
|
{
|
|
cerr<<"wtype "<<wtype<<", was about to mis-set TOW"<<endl;
|
|
}
|
|
}
|
|
else if(wtype == 6) {
|
|
|
|
a0 = getbits(&page[0], 6, 32);
|
|
a1 = getbits(&page[0], 38, 24);
|
|
dtLS = getbits(&page[0], 62, 8);
|
|
t0t = getbitu(&page[0], 70, 8) * 3600; // WE SCALE THIS FOR THE USER
|
|
wn0t = getbitu(&page[0], 78, 8);
|
|
wnLSF = getbitu(&page[0], 86, 8);
|
|
dn = getbitu(&page[0], 94, 3);
|
|
dtLSF = getbits(&page[0], 97, 8);
|
|
|
|
// cout<<(int) dtLS << " " <<(int) wnLSF<<" " <<(int) dn <<" " <<(int) dtLSF<<endl;
|
|
if(tow != getbitu(&page[0], 105, 20)) {
|
|
cerr<<"wtype "<<wtype<<", was about to mis-set TOW"<<endl;
|
|
}
|
|
}
|
|
else if(wtype == 10) { // GSTT GPS
|
|
a0g = getbits(&page[0], 86, 16);
|
|
a1g = getbits(&page[0], 102, 12);
|
|
t0g = getbitu(&page[0], 114, 8);
|
|
wn0g = getbitu(&page[0], 122, 6);
|
|
}
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
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");
|
|
return stats.find(ages.rbegin()->second)->second.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);
|
|
}
|
|
|
|
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("*"));
|
|
|
|
}
|
|
|
|
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");
|
|
|
|
app.add_flag("--version", doVERSION, "show program version and copyright");
|
|
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 {
|
|
ret["last-seen"]=utcFromGST(latestWN(2, svstats), latestTow(2, svstats));
|
|
}
|
|
catch(...)
|
|
{}
|
|
|
|
map<int, SatID> utcstats, gpsgststats, gpsutcstats;
|
|
|
|
for(const auto& s: svstats) {
|
|
if(!s.second.wn) // this will suck in 20 years
|
|
continue;
|
|
|
|
//Galileo-UTC offset: 3.22 ns, Galileo-GPS offset: 7.67 ns, 18 leap seconds
|
|
|
|
|
|
if(s.first.gnss == 0) { // GPS
|
|
int dw = (uint8_t)s.second.wn - s.second.wn0t;
|
|
int age = dw * 7 * 86400 + s.second.tow - s.second.t0t; // t0t is PRESCALED // XXX changed this,
|
|
|
|
gpsutcstats[age]=s.first;
|
|
continue;
|
|
}
|
|
|
|
int dw = (uint8_t)s.second.wn - s.second.wn0t;
|
|
int age = dw * 7 * 86400 + s.second.tow - s.second.t0t; // t0t is pre-scaled
|
|
utcstats[age]=s.first;
|
|
|
|
uint8_t wn0g = s.second.wn0t;
|
|
int dwg = (((uint8_t)s.second.wn)&(1+2+4+8+16+32)) - wn0g;
|
|
age = dwg*7*86400 + s.second.tow - s.second.t0g * 3600;
|
|
gpsgststats[age]=s.first;
|
|
|
|
}
|
|
if(utcstats.empty()) {
|
|
ret["utc-offset-ns"]=nullptr;
|
|
}
|
|
else {
|
|
auto satid = utcstats.begin()->second; // freshest SV
|
|
long shift = svstats[{2,satid.sv,satid.sigid}].a0 * (1LL<<20) + svstats[{2,satid.sv,satid.sigid}].a1 * utcstats.begin()->first; // in 2^-50 seconds units
|
|
ret["utc-offset-ns"] = 1.073741824*ldexp(1.0*shift, -20);
|
|
ret["leap-second-planned"] = (svstats[satid].dtLSF != svstats[satid].dtLS);
|
|
}
|
|
|
|
if(gpsgststats.empty()) {
|
|
ret["gps-offset-ns"]=nullptr;
|
|
}
|
|
else {
|
|
auto satid = gpsgststats.begin()->second; // freshest SV
|
|
long shift = svstats[{2,satid.sv, satid.sigid}].a0g * (1L<<16) + svstats[{2,satid.sv, satid.sigid}].a1g * gpsgststats.begin()->first; // in 2^-51 seconds units
|
|
|
|
ret["gps-offset-ns"] = 1.073741824*ldexp(shift, -21);
|
|
}
|
|
|
|
if(gpsutcstats.empty()) {
|
|
ret["gps-utc-offset-ns"]=nullptr;
|
|
}
|
|
else {
|
|
auto satid = gpsutcstats.begin()->second; // freshest SV
|
|
long shift = svstats[{0,satid.sv,satid.sigid}].a0 * (1LL<<20) + svstats[{0,satid.sv,satid.sigid}].a1 * gpsutcstats.begin()->first; // In 2^-50 seconds units
|
|
|
|
ret["gps-utc-offset-ns"] = 1.073741824*ldexp(shift, -20);
|
|
}
|
|
|
|
|
|
|
|
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.liveIOD().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(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;
|
|
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);
|
|
|
|
getCoordinates(latestTow(sv.first.gnss, svstats), sv.second.liveIOD(), & sat);
|
|
}
|
|
if(sv.first.gnss == 3 && sv.second.oldBeidouMessage.sow >= 0 && sv.second.oldBeidouMessage.sqrtA != 0) {
|
|
getCoordinates(latestTow(sv.first.gnss, svstats), sv.second.oldBeidouMessage, &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;
|
|
ret["a0"] = s.a0;
|
|
ret["a1"] = s.a1;
|
|
ret["a0g"] = s.a0g;
|
|
ret["a1g"] = s.a1g;
|
|
if(id.gnss == 2) {
|
|
ret["sf1"] = s.sf1;
|
|
ret["sf2"] = s.sf2;
|
|
ret["sf3"] = s.sf3;
|
|
ret["sf4"] = s.sf4;
|
|
ret["sf5"] = s.sf5;
|
|
ret["BGDE1E5a"] = s.BGDE1E5a;
|
|
ret["BGDE1E5b"] = s.BGDE1E5b;
|
|
ret["e5bdvs"]=s.e5bdvs;
|
|
ret["e1bdvs"]=s.e1bdvs;
|
|
ret["e5bhs"]=s.e5bhs;
|
|
ret["e1bhs"]=s.e1bhs;
|
|
}
|
|
ret["ai0"] = s.ai0;
|
|
ret["ai1"] = s.ai1;
|
|
ret["ai2"] = s.ai2;
|
|
ret["wn"] = s.wn;
|
|
ret["tow"] = s.tow;
|
|
ret["dtLS"] = s.dtLS;
|
|
ret["dtLSF"] = s.dtLSF;
|
|
ret["wnLSF"] = s.wnLSF;
|
|
ret["dn"] = s.dn;
|
|
|
|
|
|
if(id.gnss == 3 && svstats[id].oldBeidouMessage.sow >= 0 && svstats[id].oldBeidouMessage.sqrtA != 0) {
|
|
const auto& iod = svstats[id].oldBeidouMessage;
|
|
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;
|
|
getCoordinates(s.tow, iod, &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].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();
|
|
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.af0;
|
|
ret["af1"] = iod.af1;
|
|
ret["af2"] = iod.af2;
|
|
Point p;
|
|
getCoordinates(s.tow, iod, &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].liveIOD().sisa == 255) {
|
|
continue;
|
|
}
|
|
if(svstats[id].e1bhs || svstats[id].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].ura == 16) {
|
|
// cout<<"Skipping G"<<id.sv<<" because of URA"<<endl;
|
|
continue;
|
|
}
|
|
if(svstats[id].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].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.last_seen;
|
|
item["last-seen-s"] = time(0) - s.second.last_seen;
|
|
|
|
item["last-type-0"] = s.second.last_type_0;
|
|
item["last-type-0-s"] = time(0) - s.second.last_type_0;
|
|
|
|
if(s.second.last_seen - s.second.last_type_0 < 120)
|
|
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 oldBeidouMessage for sow >=0 as 'completeIOD'?
|
|
|
|
if(s.first.gnss == 3) { // beidou
|
|
item["sisa"]=humanUra(s.second.ura);
|
|
item["sisa-m"]=numUra(s.second.ura);
|
|
if(s.second.t0eMSB >= 0 && s.second.t0eLSB >=0)
|
|
item["eph-age-m"] = ephAge(s.second.tow, 8.0*((s.second.t0eMSB<<15) + s.second.t0eLSB))/60.0;
|
|
|
|
if(s.second.tleMatch.distance >=0) {
|
|
item["best-tle"] = s.second.tleMatch.name;
|
|
item["best-tle-dist"] = s.second.tleMatch.distance /1000.0;
|
|
item["best-tle-norad"] = s.second.tleMatch.norad;
|
|
item["best-tle-int-desig"] = s.second.tleMatch.internat;
|
|
}
|
|
Point p;
|
|
if(s.second.oldBeidouMessage.sqrtA != 0) {
|
|
getCoordinates(s.second.tow, s.second.oldBeidouMessage, &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.aode;
|
|
item["iod"] = s.second.glonassMessage.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;
|
|
if(s.second.tleMatch.distance >=0) {
|
|
item["best-tle"] = s.second.tleMatch.name;
|
|
item["best-tle-dist"] = truncPrec(s.second.tleMatch.distance /1000.0, 1);
|
|
item["best-tle-norad"] = s.second.tleMatch.norad;
|
|
item["best-tle-int-desig"] = s.second.tleMatch.internat;
|
|
}
|
|
|
|
}
|
|
if(s.second.completeIOD()) {
|
|
item["iod"]=s.second.getIOD();
|
|
if(s.first.gnss == 0 || s.first.gnss == 3) {
|
|
item["sisa"]=humanUra(s.second.ura);
|
|
item["sisa-m"]=truncPrec(numUra(s.second.ura),3);
|
|
// cout<<"Asked to convert "<<s.second.ura<<" for sv "<<s.first.first<<","<<s.first.second<<endl;
|
|
}
|
|
else {
|
|
item["sisa"]=humanSisa(s.second.liveIOD().sisa);
|
|
item["sisa-m"]=numSisa(s.second.liveIOD().sisa);
|
|
}
|
|
item["eph-age-m"] = ephAge(s.second.tow, s.second.liveIOD().t0e)/60.0;
|
|
item["af0"] = s.second.liveIOD().af0;
|
|
item["af1"] = s.second.liveIOD().af1;
|
|
item["af2"] = (int)s.second.liveIOD().af2;
|
|
item["t0c"] = s.second.liveIOD().t0c;
|
|
|
|
|
|
Point p;
|
|
Point core;
|
|
|
|
// this should actually use local time!
|
|
getCoordinates(s.second.tow, s.second.liveIOD(), &p);
|
|
auto match = g_tles.getBestMatch(
|
|
s.first.gnss ?
|
|
utcFromGST((int)s.second.wn, (int)s.second.tow) : utcFromGPS(s.second.wn, s.second.tow),
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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);
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
item["t0t"] = s.second.t0t;
|
|
item["wn0t"] = s.second.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.oldBeidouMessage.getUTCOffset(s.second.oldBeidouMessage.sow);
|
|
item["delta-utc"] = fmt::sprintf("%.1f %+.1f/d", deltaUTC.first, deltaUTC.second * 86400);
|
|
|
|
auto deltaGPS = s.second.oldBeidouMessage.getGPSOffset(s.second.oldBeidouMessage.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;
|
|
}
|
|
|
|
|
|
|
|
item["dtLS"]=s.second.dtLS;
|
|
|
|
if(s.first.gnss == 3) { // beidou
|
|
item["a0g"]=s.second.a0g;
|
|
item["a1g"]=s.second.a1g;
|
|
if(s.second.aode >= 0)
|
|
item["aode"]=s.second.aode;
|
|
if(s.second.aodc >= 0)
|
|
item["aodc"]=s.second.aodc;
|
|
|
|
}
|
|
|
|
|
|
if(s.first.gnss == 2) { // galileo
|
|
item["a0g"]=s.second.a0g;
|
|
item["a1g"]=s.second.a1g;
|
|
item["t0g"]=s.second.t0g;
|
|
item["wn0g"]=s.second.wn0g;
|
|
|
|
item["health"] =
|
|
humanBhs(s.second.e1bhs) +"/" +
|
|
humanBhs(s.second.e5bhs) +"/" +
|
|
(s.second.e1bdvs ? "NG" : "val") +"/"+
|
|
(s.second.e5bdvs ? "NG" : "val");
|
|
item["e5bdvs"]=s.second.e5bdvs;
|
|
item["e1bdvs"]=s.second.e1bdvs;
|
|
item["e5bhs"]=s.second.e5bhs;
|
|
item["e1bhs"]=s.second.e1bhs;
|
|
item["healthissue"]=0;
|
|
if(s.second.e1bhs == 2 || s.second.e5bhs == 2)
|
|
item["healthissue"] = 1;
|
|
if(s.second.e1bhs == 3 || s.second.e5bhs == 3)
|
|
item["healthissue"] = 1;
|
|
if(s.second.e1bdvs || s.second.e5bdvs || s.second.e1bhs == 1 || s.second.e5bhs == 1)
|
|
item["healthissue"] = 2;
|
|
|
|
}
|
|
else if(s.first.gnss == 0 || s.first.gnss == 3 || s.first.gnss == 6) {// gps or beidou or GLONASS
|
|
item["health"]= s.second.gpshealth ? ("NOT OK: "+to_string(s.second.gpshealth)) : string("OK");
|
|
item["healthissue"]= 2* !!s.second.gpshealth;
|
|
}
|
|
|
|
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.first.gnss == 0 || s.first.gnss == 2) && s.second.completeIOD())
|
|
getCoordinates(latestTow(s.first.gnss, svstats), s.second.liveIOD(), & sat);
|
|
if(s.first.gnss == 3 && s.second.oldBeidouMessage.sow >= 0 && s.second.oldBeidouMessage.sqrtA != 0) {
|
|
getCoordinates(latestTow(s.first.gnss, svstats), s.second.oldBeidouMessage, &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.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];
|
|
if(fread(bert, 1, 6, stdin) != 6 || 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;
|
|
nmm.ParseFromString(string(buffer, len));
|
|
/*
|
|
static time_t lastCovSyncHour;
|
|
if(nmm.localutcseconds() / 1800 > (unsigned int)lastCovSyncHour) {
|
|
lastCovSyncHour = nmm.localutcseconds() / 1800;
|
|
int tow;
|
|
static int totexceeds, totcells;
|
|
try {
|
|
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.liveIOD().sisa == 255) {
|
|
continue;
|
|
}
|
|
if(svstat.e1bhs || svstat.e1bdvs)
|
|
continue;
|
|
sats.push_back(sat);
|
|
}
|
|
|
|
auto cov = emitCoverage(sats);
|
|
int exceeds=0, cells=0;
|
|
for(const auto& latvect : cov) {
|
|
for(const auto& longpair : latvect.second) {
|
|
cells++;
|
|
if(get<4>(longpair) >= 6.0)
|
|
exceeds++;
|
|
// else
|
|
//cout<<get<6>(longpair) << endl;
|
|
}
|
|
}
|
|
totexceeds += exceeds;
|
|
totcells += cells;
|
|
fmt::printf("At %s, %.2f%% (%d) of %d cells exceeded PDOP 6 for 5 degrees horizon (%d sats), running %.2f%%\n", humanTime(nmm.localutcseconds()), 100.0*exceeds/cells, exceeds, cells, sats.size(), 100.0*totexceeds/totcells);
|
|
}
|
|
catch(std::exception&e) {
|
|
cout<<"Error with coverage: "<<e.what()<<endl;
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
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;
|
|
// XXX this needs to be unified somehow
|
|
if(id.gnss == 3 && g_svstats[id].oldBeidouMessage.sow >= 0 && g_svstats[id].oldBeidouMessage.sqrtA != 0) {
|
|
getCoordinates(g_svstats[id].tow, g_svstats[id].oldBeidouMessage, &sat);
|
|
}
|
|
else if(g_svstats[id].completeIOD()) {
|
|
getCoordinates(g_svstats[id].tow, g_svstats[id].liveIOD(), &sat);
|
|
}
|
|
|
|
if(sat.x != 0 && g_srcpos[nmm.sourceid()].pos.x != 0) {
|
|
if(!(random() % 4))
|
|
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};
|
|
g_svstats[id].wn = nmm.gi().gnsswn();
|
|
auto& svstat = g_svstats[id];
|
|
auto oldgm = svstat.galmsg;
|
|
|
|
auto& gm = svstat.galmsg;
|
|
unsigned int wtype = gm.parse(inav);
|
|
|
|
|
|
if(wtype == 1 || wtype == 2 || wtype == 3) {
|
|
idb.addValue(id, "ephemeris", {{"iod-live", svstat.galmsg.iodnav},
|
|
{"eph-age", ephAge(gm.tow, gm.getT0e())}}, satUTCTime(id));
|
|
}
|
|
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;
|
|
g_svstats[id].tow = nmm.gi().gnsstow();
|
|
|
|
// g_svstats[id].perrecv[nmm.sourceid()].wn = nmm.gi().gnsswn();
|
|
// g_svstats[id].perrecv[nmm.sourceid()].tow = nmm.gi().gnsstow();
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
g_svstats[id].addGalileoWord(inav);
|
|
if(g_svstats[id].e1bhs || g_svstats[id].e5bhs || g_svstats[id].e1bdvs || g_svstats[id].e5bdvs) {
|
|
// if(sv != 18 && sv != 14)
|
|
// cout << "sv "<<sv<<" health: " << g_svstats[id].e1bhs <<" " << g_svstats[id].e5bhs <<" " << g_svstats[id].e1bdvs <<" "<< g_svstats[id].e5bdvs <<endl;
|
|
}
|
|
|
|
if(wtype >=1 && wtype <= 4) { // ephemeris
|
|
uint16_t iod = getbitu(&inav[0], 6, 10);
|
|
if(wtype == 3) {
|
|
idb.addValue(id, "sisa", {{"value", g_svstats[id].iods[iod].sisa}}, satUTCTime(id));
|
|
}
|
|
else if(wtype == 4) {
|
|
idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow).first},
|
|
{"t0c", g_svstats[id].iods[iod].t0c*60},
|
|
{"af0", g_svstats[id].iods[iod].af0},
|
|
{"af1", g_svstats[id].iods[iod].af1},
|
|
{"af2", g_svstats[id].iods[iod].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", {{"value", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
}
|
|
}
|
|
else if(wtype == 5) {
|
|
idb.addValue(id, "iono", {
|
|
{"ai0", g_svstats[id].ai0},
|
|
{"ai1", g_svstats[id].ai1},
|
|
{"ai2", g_svstats[id].ai2},
|
|
{"sf1", g_svstats[id].sf1},
|
|
{"sf2", g_svstats[id].sf2},
|
|
{"sf3", g_svstats[id].sf3},
|
|
{"sf4", g_svstats[id].sf4},
|
|
{"sf5", g_svstats[id].sf5}}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "galbgd", {
|
|
{"BGDE1E5a", g_svstats[id].BGDE1E5a},
|
|
{"BGDE1E5b", g_svstats[id].BGDE1E5b}}, satUTCTime(id));
|
|
|
|
|
|
idb.addValue(id, "galhealth", {
|
|
{"e1bhs", g_svstats[id].e1bhs},
|
|
{"e5bhs", g_svstats[id].e5bhs},
|
|
{"e5bdvs", g_svstats[id].e5bdvs},
|
|
{"e1bdvs", g_svstats[id].e1bdvs}}, satUTCTime(id));
|
|
}
|
|
else if(wtype == 6) { // GST-UTC
|
|
int dw = (uint8_t)g_svstats[id].wn - g_svstats[id].wn0t;
|
|
int age = dw * 7 * 86400 + g_svstats[id].tow - g_svstats[id].t0t; // t0t is PRESCALED
|
|
|
|
long shift = g_svstats[id].a0 * (1LL<<20) + g_svstats[id].a1 * age; // in 2^-50 seconds units
|
|
idb.addValue(id, "utcoffset", {
|
|
{"a0", g_svstats[id].a0},
|
|
{"a1", g_svstats[id].a1},
|
|
{"t0t", g_svstats[id].t0t},
|
|
{"delta", 1.073741824*ldexp(1.0*shift, -20)}
|
|
},
|
|
satUTCTime(id));
|
|
|
|
g_dtLS = g_svstats[id].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;
|
|
}
|
|
|
|
uint8_t wn0g = g_svstats[id].wn0t;
|
|
int dwg = (((uint8_t)g_svstats[id].wn)&(1+2+4+8+16+32)) - wn0g;
|
|
int age = dwg*7*86400 + g_svstats[id].tow - g_svstats[id].t0g * 3600;
|
|
|
|
long shift = g_svstats[id].a0g * (1L<<16) + g_svstats[id].a1g * age; // in 2^-51 seconds units
|
|
idb.addValue(id, "gpsoffset", {{"a0g", g_svstats[id].a0g},
|
|
{"a1g", g_svstats[id].a1g},
|
|
{"t0g", g_svstats[id].t0g},
|
|
{"delta", 1.073741824*ldexp(shift, -21)}
|
|
}, satUTCTime(id));
|
|
|
|
}
|
|
|
|
for(auto& ent : g_svstats) {
|
|
// fmt::printf("%2d\t", ent.first);
|
|
id=ent.first;
|
|
if(ent.second.completeIOD() && ent.second.prevIOD.first >= 0) {
|
|
|
|
int ephage = ephAge(ent.second.tow, ent.second.prevIOD.second.t0e);
|
|
if(ent.second.liveIOD().sisa != ent.second.prevIOD.second.sisa) {
|
|
|
|
cout<<humanTime(id.gnss, ent.second.wn, ent.second.tow)<<" gnssid "<<ent.first.gnss<<" sv "<<ent.first.sv<<"@"<<ent.first.sigid<<" changed sisa from "<<(unsigned int) ent.second.prevIOD.second.sisa<<" ("<<
|
|
humanSisa(ent.second.prevIOD.second.sisa)<<") to " << (unsigned int)ent.second.liveIOD().sisa << " ("<<
|
|
humanSisa(ent.second.liveIOD().sisa)<<"), lastseen = "<< (ephage/3600.0) <<"h"<<endl;
|
|
}
|
|
|
|
Point p, oldp;
|
|
getCoordinates(ent.second.tow, ent.second.liveIOD(), &p);
|
|
getCoordinates(ent.second.tow, ent.second.prevIOD.second, &oldp);
|
|
|
|
if(ent.second.prevIOD.second.t0e < ent.second.liveIOD().t0e) {
|
|
double hours = (1.0*(ent.second.liveIOD().t0e - ent.second.prevIOD.second.t0e)/3600.0); // XXX week jump
|
|
double disco = Vector(p, oldp).length();
|
|
// cout<<id.first<<","<<id.second<<" discontinuity after "<< hours<<" hours: "<< disco <<endl;
|
|
|
|
if(hours < 4) {
|
|
g_svstats[id].latestDisco= disco;
|
|
g_svstats[id].latestDiscoAge= hours*3600;
|
|
}
|
|
else
|
|
g_svstats[id].latestDisco= -1;
|
|
|
|
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", 1.0*ent.second.getIOD()},
|
|
{"iod", 1.0*ent.second.prevIOD.first}}, satUTCTime(id));
|
|
}
|
|
}
|
|
ent.second.clearPrev();
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
|
|
|
|
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].oldBeidouMessage.sow >= 0 && g_svstats[id].oldBeidouMessage.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].oldBeidouMessage, 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].oldBeidouMessage, &sat);
|
|
|
|
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()) {
|
|
double freqMHZ = 1575.42;
|
|
if(id.gnss == 2 && id.sigid == 5) // this is exactly the beidou b2i freq?
|
|
freqMHZ = 1207.140;
|
|
|
|
auto res = doDoppler(nmm.rfd().rcvtow(), g_srcpos[nmm.sourceid()].pos, g_svstats[id].liveIOD(),freqMHZ * 1000000);
|
|
|
|
Point sat;
|
|
getCoordinates(nmm.rfd().rcvtow(), g_svstats[id].liveIOD(), &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());
|
|
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) {
|
|
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 = doDoppler(tsat, g_srcpos[nmm.sourceid()].pos, g_svstats[id].liveIOD(),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;
|
|
getCoordinates(tsat, g_svstats[id].liveIOD(), &sat);
|
|
|
|
// idb.addValue(id, "delta_hz_cor", tss.dopplerHz - res.preddop - *corr, nmm.sourceid());
|
|
|
|
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 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];
|
|
auto oldsvstat = svstat;
|
|
uint8_t page;
|
|
int frame=parseGPSMessage(cond, svstat, &page);
|
|
if(frame == 1) {
|
|
|
|
idb.addValue(id, "clock", {{"offset_ns", getGPSAtomicOffset(svstat.tow, svstat).first},
|
|
{"t0c", 16.0*svstat.t0c},
|
|
{"af0", 8.0*svstat.af0},
|
|
{"af1", 8.0*svstat.af1},
|
|
{"af2", 16.0*svstat.af2}}, satUTCTime(id));
|
|
|
|
// cout<<"Got ura "<<svstat.ura<<" for sv "<<id.first<<","<<id.second<<endl;
|
|
idb.addValue(id, "gpsura", {{"value", svstat.ura}}, satUTCTime(id));
|
|
|
|
if(oldsvstat.af0 && oldsvstat.ura != svstat.ura) { // XX find better way to check
|
|
cout<<humanTime(id.gnss, svstat.wn, svstat.tow)<<" src "<<nmm.sourceid()<<" wn "<<svstat.wn <<" GPS "<<id.sv <<"@"<<id.sigid<<" change in URA ["<< humanUra(oldsvstat.ura) <<"] -> [" << humanUra(svstat.ura)<<"] "<<(int)svstat.ura<<", lastseen "<<ephAge(oldsvstat.tow, svstat.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
|
|
if(oldsvstat.af0 && oldsvstat.gpshealth != svstat.gpshealth) { // XX find better way to check
|
|
cout<<humanTime(id.gnss, svstat.wn, svstat.tow)<<" src " <<nmm.sourceid()<<" wn "<<svstat.wn <<" GPS "<<id.sv <<"@"<<id.sigid<<" change in health ["<< (int)oldsvstat.gpshealth <<"] -> [" << (int)svstat.gpshealth <<"], lastseen "<<ephAge(oldsvstat.tow, svstat.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
|
|
|
|
// double age = ephAge(g_svstats[id].tow, g_svstats[id].t0c * 16);
|
|
|
|
if(oldsvstat.af0 && oldsvstat.t0c != svstat.t0c) {
|
|
auto oldOffset = getGPSAtomicOffset(svstat.tow, oldsvstat);
|
|
auto newOffset = getGPSAtomicOffset(svstat.tow, svstat);
|
|
svstat.timeDisco = oldOffset.first - newOffset.first;
|
|
if(fabs(svstat.timeDisco) < 10000)
|
|
idb.addValue(id, "clock_jump_ns", {{"value", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
}
|
|
else if(frame==2) {
|
|
if(oldsvstat.gpshealth != svstat.gpshealth) {
|
|
cout<<humanTime(id.gnss, svstat.wn, svstat.tow)<<" src "<<nmm.sourceid()<<" GPS "<<id.sv <<"@"<<id.sigid<<" change in health: ["<< (int)oldsvstat.gpshealth<<"] -> ["<< (int)svstat.gpshealth<<"] , lastseen "<<ephAge(oldsvstat.tow, svstat.tow)/3600.0 <<" hours"<<endl;
|
|
}
|
|
idb.addValue(id, "gpshealth", {{"value", g_svstats[id].gpshealth}}, satUTCTime(id));
|
|
}
|
|
else if(frame==4 && page==18) {
|
|
int dw = (uint8_t)g_svstats[id].wn - g_svstats[id].wn0t;
|
|
int age = dw * 7 * 86400 + g_svstats[id].tow - g_svstats[id].t0t; // t0t is PRESCALED
|
|
long shift = g_svstats[id].a0 * (1LL<<20) + g_svstats[id].a1 * age; // in 2^-50 seconds units
|
|
|
|
idb.addValue(id, "utcoffset", {
|
|
{"a0", g_svstats[id].a0},
|
|
{"a1", g_svstats[id].a1},
|
|
{"delta", 1.073741824*ldexp(1.0*shift, -20)}
|
|
},
|
|
satUTCTime(id));
|
|
}
|
|
else if((frame == 5 && page <= 24) || (frame==4 && page >=25 && page<=32)) {
|
|
g_gpsalma[svstat.gpsalma.sv] = svstat.gpsalma;
|
|
}
|
|
g_svstats[id].perrecv[nmm.sourceid()].t = nmm.localutcseconds();
|
|
g_svstats[id].tow = nmm.gpsi().gnsstow();
|
|
g_svstats[id].wn = nmm.gpsi().gnsswn();
|
|
if(g_svstats[id].wn < 512)
|
|
g_svstats[id].wn += 2048;
|
|
}
|
|
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];
|
|
|
|
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;
|
|
svstat.tow = nmm.gpsc().gnsstow();
|
|
svstat.wn = nmm.gpsc().gnsswn();
|
|
|
|
}
|
|
else if(nmm.type()== NavMonMessage::BeidouInavTypeD1) {
|
|
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];
|
|
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.beidouMessage;
|
|
auto oldbm = bm;
|
|
int fraid=bm.parse(cond, &pageno);
|
|
svstat.tow = nmm.bid1().gnsstow();
|
|
svstat.wn = nmm.bid1().gnsswn();
|
|
if(fraid == 1) {
|
|
svstat.ura = bm.urai;
|
|
svstat.gpshealth = bm.sath1;
|
|
svstat.af0 = bm.a0;
|
|
svstat.af1 = bm.a1;
|
|
svstat.af2 = bm.a2;
|
|
svstat.aode = bm.aode;
|
|
svstat.aodc = bm.aodc;
|
|
|
|
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", {{"value", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
svstat.lastBeidouMessage1 = bm;
|
|
}
|
|
if(fraid == 2) {
|
|
svstat.lastBeidouMessage2 = bm;
|
|
svstat.t0eMSB = bm.t0eMSB;
|
|
}
|
|
if(fraid == 3) {
|
|
svstat.t0eLSB = bm.t0eLSB;
|
|
Point oldpoint, newpoint;
|
|
if(bm.sow - svstat.lastBeidouMessage2.sow == 6 && svstat.oldBeidouMessage.sow >= 0) {
|
|
getCoordinates(svstat.tow, bm, &newpoint);
|
|
|
|
if(fabs(svstat.lastTLELookupX - newpoint.x) > 300000) {
|
|
// cout<<"fraid 3 lookup, delta " << fabs(svstat.lastTLELookupX - newpoint.x) << endl;
|
|
auto match = g_tles.getBestMatch(nanoTime(3, svstat.wn, svstat.tow)/1000000000.0, newpoint.x, newpoint.y, newpoint.z);
|
|
svstat.tleMatch = match;
|
|
svstat.lastTLELookupX = newpoint.x;
|
|
}
|
|
|
|
if(svstat.oldBeidouMessage.getT0e() != svstat.beidouMessage.getT0e()) {
|
|
getCoordinates(svstat.tow, svstat.oldBeidouMessage, &oldpoint);
|
|
Vector jump(oldpoint ,newpoint);
|
|
/* cout<<fmt::sprintf("Discontinuity C%02d (%f,%f,%f) -> (%f, %f, %f), jump: %f, seconds: %f\n",
|
|
id.second, oldpoint.x, oldpoint.y, oldpoint.z,
|
|
newpoint.x, newpoint.y, newpoint.z, jump.length(), (double)bm.getT0e() - svstat.oldBeidouMessage.getT0e());
|
|
*/
|
|
double hours = (bm.getT0e() - svstat.oldBeidouMessage.getT0e())/3600;
|
|
if(hours < 4) {
|
|
svstat.latestDisco = jump.length();
|
|
}
|
|
else
|
|
svstat.latestDisco = -1;
|
|
|
|
if(hours < 24) {
|
|
idb.addValue(id, "eph-disco",
|
|
{{"meters", jump.length()},
|
|
{"seconds", hours*3600.0},
|
|
{"oldx", oldpoint.x}, {"oldy", oldpoint.y}, {"oldz", oldpoint.z},
|
|
{"x", newpoint.x}, {"y", newpoint.y}, {"z", newpoint.z},
|
|
{"iod", 0},
|
|
{"oldiod", 0}}, nanoTime(id.gnss, bm.wn, bm.sow)/1000000000.0);
|
|
}
|
|
}
|
|
|
|
}
|
|
if(bm.sqrtA) // only copy in if complete
|
|
svstat.oldBeidouMessage = bm;
|
|
}
|
|
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.oldBeidouMessage.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;
|
|
// 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) {
|
|
SatID id{nmm.gloi().gnssid(), nmm.gloi().gnsssv(), nmm.gloi().sigid()};
|
|
auto& svstat = g_svstats[id];
|
|
|
|
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
|
|
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;
|
|
|
|
}
|
|
svstat.gpshealth = gm.Bn;
|
|
}
|
|
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", {{"value", svstat.timeDisco}}, satUTCTime(id));
|
|
}
|
|
}
|
|
}
|
|
if(gm.x && gm.y && gm.z) {
|
|
time_t t0e = getGlonassT0e(nmm.localutcseconds(), gm.Tb);
|
|
|
|
if(svstat.lastTLELookupX != gm.getX()) {
|
|
auto match = g_tles.getBestMatch(t0e, gm.getX(), gm.getY(), gm.getZ());
|
|
svstat.tleMatch = match;
|
|
svstat.lastTLELookupX = gm.getX();
|
|
}
|
|
}
|
|
}
|
|
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) {
|
|
auto& sb = g_sbas[nmm.sbm().gnsssv()];
|
|
sb.last_seen = nmm.localutcseconds();
|
|
sb.perrecv[nmm.sourceid()].last_seen = nmm.localutcseconds();
|
|
|
|
basic_string<uint8_t> sbas((uint8_t*)nmm.sbm().contents().c_str(), nmm.sbm().contents().length());
|
|
int type = getbitu(&sbas[0], 8, 6);
|
|
if(type == 0) { // the don't use message
|
|
sb.last_type_0 = 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;
|
|
}
|