#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 "CLI/CLI.hpp" #include "gpscnav.hh" #include "rtcm.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 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("*")); } 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 { // if we haven't seen galileo, we have no idea ret["last-seen"]=utcFromGST(latestWN(2, svstats), latestTow(2, svstats)); } catch(...) {} ret["gst-utc-offset-ns"] = g_GSTUTCOffset; ret["gst-gps-offset-ns"] = g_GSTGPSOffset; ret["gps-utc-offset-ns"] = g_GPSUTCOffset; ret["beidou-utc-offset-ns"] = g_BeiDouUTCOffset; ret["glonass-utc-offset-ns"] = g_GlonassUTCOffset; ret["glonass-gps-offset-ns"] = g_GlonassGPSOffset; map 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) { 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)lastCovSyncHour) { lastCovSyncHour = nmm.localutcseconds() / 1800; int tow; static int totexceeds, totcells; try { 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.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<(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: "< 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}; // XXX conversion, may be vital // g_svstats[id].wn = nmm.gi().gnsswn(); 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) { if(svstat.ephgalmsg.iodnav != svstat.galmsgTyped[1].iodnav) { svstat.oldephgalmsg = svstat.ephgalmsg; svstat.ephgalmsg = svstat.galmsgTyped[wtype]; svstat.reportNewEphemeris(id, idb); } } } // XXX conversion possibly vital // 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(); if(wtype >=1 && wtype <= 4) { // ephemeris if(wtype == 3) { idb.addValue(id, "sisa", {{"value", g_svstats[id].galmsg.sisa}}, satUTCTime(id)); } else if(wtype == 4) { idb.addValue(id, "clock", {{"offset_ns", svstat.galmsg.getAtomicOffset(svstat.tow()).first}, {"t0c", g_svstats[id].galmsg.t0c*60}, // getT0c()?? {"af0", g_svstats[id].galmsg.af0}, {"af1", g_svstats[id].galmsg.af1}, {"af2", g_svstats[id].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", {{"value", svstat.timeDisco}}, satUTCTime(id)); } } } else if(wtype == 5) { idb.addValue(id, "iono", { {"ai0", g_svstats[id].galmsg.ai0}, {"ai1", g_svstats[id].galmsg.ai1}, {"ai2", g_svstats[id].galmsg.ai2}, {"sf1", g_svstats[id].galmsg.sf1}, {"sf2", g_svstats[id].galmsg.sf2}, {"sf3", g_svstats[id].galmsg.sf3}, {"sf4", g_svstats[id].galmsg.sf4}, {"sf5", g_svstats[id].galmsg.sf5}}, satUTCTime(id)); idb.addValue(id, "galbgd", { {"BGDE1E5a", g_svstats[id].galmsg.BGDE1E5a}, {"BGDE1E5b", g_svstats[id].galmsg.BGDE1E5b}}, satUTCTime(id)); idb.addValue(id, "galhealth", { {"e1bhs", g_svstats[id].galmsg.e1bhs}, {"e5bhs", g_svstats[id].galmsg.e5bhs}, {"e5bdvs", g_svstats[id].galmsg.e5bdvs}, {"e1bdvs", g_svstats[id].galmsg.e1bdvs}}, satUTCTime(id)); } else if(wtype == 6) { // GST-UTC 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; } 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 "<= 0) { ent.second.clearPrev(); } } #endif } 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].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); 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()); 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()); 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.0*gm.t0c}, {"af0", 8.0*gm.af0}, {"af1", 8.0*gm.af1}, {"af2", 16.0*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); if(iter != g_svstats.end() && iter->second.completeIOD() && iter->second.liveIOD().getIOD() == ed.iod) 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}}, nmm.localutcseconds(), nmm.sourceid()); } } else if(rm.type == 1058 || rm.type == 1241) { for(const auto& cd : rm.d_clocks) { 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(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", {{"value", 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", {{"value", 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 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 { cout<<"Unknown type "<< (int)nmm.type()<