#include #include #include #include #include "fmt/format.h" #include "fmt/printf.h" #include #include #include #include #include #include #include #include "ext/powerblog/h2o-pp.hh" #include "minicurl.hh" #include #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 #include "navmon.hh" #include #include "navparse.hh" #include #include "influxpush.hh" #include "sbas.hh" #include #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 g_srcpos; struct SBASAndReceiverStatus { SBASState status; struct PerRecv { time_t last_seen{0}; }; map perrecv; }; typedef map sbas_t; sbas_t g_sbas; GetterSetter g_sbaskeeper; map g_beidoualma; map> g_glonassalma; map g_galileoalma; map g_gpsalma; GetterSetter> g_beidoualmakeeper; GetterSetter>> g_glonassalmakeeper; GetterSetter> g_galileoalmakeeper; GetterSetter> 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< g_statskeeper; int latestWN(int gnssid, const svstats_t& stats) { map 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 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 getHzCorrection(time_t now, int src, unsigned int gnssid, unsigned int sigid, const svstats_t& svstats) { std::optional allHzCorr; double alltot=0; int allcount=0; // cout<<"getHzCorrection called for src "<second.deltaHzTime < 60) { // cout<<" Found entry for SV "<first<<", deltaHz: "<second.deltaHz<< " age " << now - iter->second.deltaHzTime<<" db "<second.db<second.deltaHz; allcount++; } } if(allcount > 3) { allHzCorr = alltot/allcount; // cout<<"Returning "<<*allHzCorr< options{"ok", "out of service", "will be out of service", "test"}; if(bhs >= (int)options.size()) { cerr<<"Asked for humanBHS "<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 receivers; time_t now; try { now=getSatelliteUTC(g_svstats); } catch(...) { return; } map svcount, sigcount; map 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> 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 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 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<(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<(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: "< siggnsscount, svgnsscount; map svcount, sigcount; map 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 sats; auto galileoalma = g_galileoalmakeeper.get(); auto gpsalma = g_gpsalmakeeper.get(); auto beidoualma = g_beidoualmakeeper.get(); auto svstats = g_statskeeper.get(); // cout<<"pseudoTow "<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"< 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<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() < (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"< 0 || (nmm.localutcseconds() - pr.second.t < 120))) { // cerr<<"Doing it -> "<< s.second.galmsg.ai0 <<" " <(obs)<<" meters, long "<(obs)<<", lat "<(obs)<(satdegs)<<" meters, long "<(satdegs)<<", lat "<(satdegs)<< " -> elevation "< 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< ["<< humanBhs(gm.e5bhs)<<", "<< humanBhs(gm.e1bhs)<<", "<< (int)gm.e5bdvs <<", " << (int)gm.e1bdvs<<"], lastseen "< 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 "< 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< ["<< humanBhs(gm.e5ahs)<<", "<< humanBhs(gm.e1bhs)<<", "<< (int)gm.e5advs <<", " << (int)gm.e1bdvs<<"], lastseen "< 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 "<=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"< 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 "<((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size())); for(const auto& tss : ret) { SatID id{static_cast(tss.gnss), static_cast(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 "< 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<((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 "< [" << humanUra(gm.ura)<<"] "<<(int)gm.ura<<", lastseen "< [" << (int)gm.gpshealth <<"], lastseen "< ["<< (int)gm.gpshealth<<"] , lastseen "<=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, unsigned int> lastT0e; pair 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*)nmm.gpsc().contents().c_str(), nmm.gpsc().contents().size()), gcns); // cout<<"Got a message from "<((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<=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: "<((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 "<((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: "< 0) idb.addValue(id, "glohealth", {{"Bn", gm.Bn}}, satUTCTime(id)); if(oldgm.Bn != gm.Bn) { cout< 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"< 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()< contents; bool operator<(const DedupKey& rhs) const { return tie(id, wn, tow, contents) < tie(rhs.id, rhs.wn, rhs.tow, rhs.contents); } }; static set s_dedup; DedupKey dk{id, (int)nmm.gf().gnsswn(), (int)nmm.gf().gnsstow(), inav}; if(s_dedup.insert(dk).second == false) { // cout<<"Dedup"< 10000) s_dedup.clear(); */ // XXX conversion, may be vital // g_svstats[id].wn = nmm.gf().gnsswn(); #endif