#include #include #include #include #include "fmt/format.h" #include "fmt/printf.h" #include #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" using namespace std; struct EofException{}; uint8_t getUint8() { int c; c = fgetc(stdin); if(c == -1) throw EofException(); return (uint8_t) c; } uint16_t getUint16() { uint16_t ret; int res = fread(&ret, 1, sizeof(ret), stdin); if(res != sizeof(ret)) throw EofException(); // ret = ntohs(ret); return ret; } Point g_ourpos(3922.505 * 1000, 290.116 * 1000, 5004.189 * 1000); int g_tow, g_wn, g_dtLS{18}; /* inav schedule: 1) Find plausible start time of next cycle Current cycle: TOW - (TOW%30) Next cycle: TOW - (TOW%30) + 30 t n w 0 1: 2 wn % 30 == 0 2 2: 4 wn % 30 == 2 4 3: 6 WN/TOW 4 -> set startTow, startTowFresh 6 4: 7/9 8 5: 8/10 10 6: 0 TOW 12 7: 0 WN/TOW 14 8: 0 WN/TOW 16 9: 0 WN/TOW 18 10: 0 WN/TOW 20 11: 1 22 12: 3 24 13: 5 WN/TOW 26 14: 0 WN/TOW 28 15: 0 WN/TOW */ string humanSisa(uint8_t sisa) { unsigned int sval = sisa; if(sisa < 50) return std::to_string(sval)+" cm"; if(sisa < 75) return std::to_string(50 + 2* (sval-50))+" cm"; if(sisa < 100) return std::to_string(100 + 4*(sval-75))+" cm"; if(sisa < 125) return std::to_string(200 + 16*(sval-100))+" cm"; if(sisa < 255) return "SPARE"; return "NO SIS AVAILABLE"; } struct SVIOD { std::bitset<32> words; uint32_t t0e; uint32_t e, sqrtA; int32_t m0, omega0, i0, omega, idot, omegadot, deltan; int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0}; uint16_t t0c; // clock epoch int32_t af0, af1; int8_t af2; uint8_t sisa; uint32_t wn{0}, tow{0}; bool complete() const { return words[1] && words[2] && words[3] && words[4]; } void addWord(std::basic_string_view page); }; void SVIOD::addWord(std::basic_string_view page) { uint8_t wtype = getbitu(&page[0], 0, 6); words[wtype]=true; if(wtype == 1) { t0e = getbitu(&page[0], 16, 14); m0 = getbits(&page[0], 30, 32); e = getbitu(&page[0], 62, 32); sqrtA = getbitu(&page[0], 94, 32); } else if(wtype == 2) { omega0 = getbits(&page[0], 16, 32); i0 = getbits(&page[0], 48, 32); omega = getbits(&page[0], 80, 32); idot = getbits(&page[0], 112, 14); } else if(wtype == 3) { omegadot = getbits(&page[0], 16, 24); deltan = getbits(&page[0], 40, 16); cuc = getbits(&page[0], 56, 16); cus = getbits(&page[0], 72, 16); crc = getbits(&page[0], 88, 16); crs = getbits(&page[0], 104, 16); sisa = getbitu(&page[0], 120, 8); } else if(wtype == 4) { cic = getbits(&page[0], 22, 16); cis = getbits(&page[0], 38, 16); t0c = getbitu(&page[0], 54, 14); af0 = getbits(&page[0], 68, 31); af1 = getbits(&page[0], 99, 21); af2 = getbits(&page[0], 120, 6); /* cout<<(int) t0c << " " <<(int) af0 <<" " <<(int) af1 <<" " <<(int) af2< iods; void addWord(std::basic_string_view page); bool completeIOD() const; uint16_t getIOD() const; SVIOD liveIOD() const; pair prevIOD{-1, SVIOD()}; void clearPrev() { prevIOD.first = -1; } }; bool SVStat::completeIOD() const { for(const auto& iod : iods) if(iod.second.complete()) return true; return false; } uint16_t SVStat::getIOD() const { for(const auto& iod : iods) if(iod.second.complete()) return iod.first; throw std::runtime_error("Asked for unknown IOD"); } SVIOD SVStat::liveIOD() const { if(auto iter = iods.find(getIOD()); iter != iods.end()) return iter->second; throw std::runtime_error("Asked for unknown IOD"); } void SVStat::addWord(std::basic_string_view page) { uint8_t wtype = getbitu(&page[0], 0, 6); if(wtype == 0) { if(getbitu(&page[0], 6,2) == 2) { g_wn = wn = getbitu(&page[0], 96, 12); g_tow = tow = getbitu(&page[0], 108, 20); } } else if(wtype >=1 && wtype <= 4) { // ephemeris uint16_t iod = getbitu(&page[0], 6, 10); iods[iod].addWord(page); if(iods[iod].complete()) { for(const auto& i : iods) { if(i.first != iod && i.second.complete()) prevIOD=i; } SVIOD latest = iods[iod]; iods.clear(); iods[iod] = latest; } } else if(wtype==5) { // disturbance, health, time ai0 = getbitu(&page[0], 6, 11); ai1 = getbits(&page[0], 17, 11); // ai1 & 2 are signed, 0 not ai2 = getbits(&page[0], 28, 14); sf1 = getbitu(&page[0], 42, 1); sf2 = getbitu(&page[0], 43, 1); sf3 = getbitu(&page[0], 44, 1); sf4 = getbitu(&page[0], 45, 1); sf5 = getbitu(&page[0], 46, 1); BGDE1E5a = getbits(&page[0], 47, 10); BGDE1E5b = getbits(&page[0], 57, 10); e5bhs = getbitu(&page[0], 67, 2); e1bhs = getbitu(&page[0], 69, 2); e5bdvs = getbitu(&page[0], 71, 1); e1bdvs = getbitu(&page[0], 72, 1); g_wn = wn = getbitu(&page[0], 73, 12); g_tow = tow = getbitu(&page[0], 85, 20); } else if(wtype == 6) { a0 = getbits(&page[0], 6, 32); a1 = getbits(&page[0], 38, 24); dtLS = getbits(&page[0], 62, 8); t0t = getbitu(&page[0], 70, 8); wn0t = getbitu(&page[0], 78, 8); wnLSF = getbitu(&page[0], 86, 8); dn = getbitu(&page[0], 94, 3); dtLSF = getbits(&page[0], 97, 8); // cout<<(int) dtLS << " " <<(int) wnLSF<<" " <<(int) dn <<" " <<(int) dtLSF<x = xprime * cos(Omega) - yprime * cos(i) * sin(Omega); p->y = xprime * sin(Omega) + yprime * cos(i) * cos(Omega); p->z = yprime * sin(i); if(!quiet) { Point core(0.0, .0, .0); Vector radius(core, *p); cout << radius.length() << " calculated r "< g_svstats; uint64_t nanoTime(int wn, int tow) { return 1000000000ULL*(935280000 + wn * 7*86400 + tow - g_dtLS); // Leap!! } uint64_t utcFromGST(int wn, int tow) { return (935280000 + wn * 7*86400 + tow - g_dtLS); // Leap!! } double utcFromGST(int wn, double tow) { return (935280000 + wn * 7*86400 + tow - g_dtLS); // Leap!! } struct InfluxPusher { explicit InfluxPusher(std::string_view dbname) : d_dbname(dbname) { } void addValue( const pair& ent, string_view name, auto value) { d_buffer+= string(name) +",sv=" +to_string(ent.first)+" value="+to_string(value)+ " "+to_string(nanoTime(ent.second.wn, ent.second.tow))+"\n"; checkSend(); } // note: this version trusts the system-wide g_wn, g_tow ovre the local one void addValue(int sv, string_view name, auto value) { d_buffer+= string(name) +",sv=" +to_string(sv) + " value="+to_string(value)+" "+ to_string(nanoTime(g_wn, g_tow))+"\n"; checkSend(); } void checkSend() { if(d_buffer.size() > 1000000 || (time(0) - d_lastsent) > 10) { string buffer; buffer.swap(d_buffer); // thread t([buffer,this]() { doSend(buffer); // }); // t.detach(); d_lastsent=time(0); } } void doSend(std::string buffer) { MiniCurl mc; MiniCurl::MiniCurlHeaders mch; if(!buffer.empty()) { mc.postURL("http://127.0.0.1:8086/write?db="+d_dbname, buffer, mch); } } ~InfluxPusher() { doSend(d_buffer); } std::string d_buffer; time_t d_lastsent{0}; string d_dbname; }; /* 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 wn, int tow) { time_t t = utcFromGST(wn, tow); struct tm tm; gmtime_r(&t, &tm); char buffer[80]; strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", &tm); return buffer; } /* | t0e tow | - > tow - t0e, <3.5 days, so ok | t0e tow | -> tow - t0e > 3.5 days, so 7*86400 - tow + t0e | tow t0e | -> 7*86400 - tow + t0e > 3.5 days, so tow - t0e (negative age) | tow t0e | -> 7*86400 - tow + t0e < 3.5 days, ok */ int ephAge(int tow, int t0e) { unsigned int diff; unsigned int halfweek = 0.5*7*86400; if(t0e < tow) { diff = tow - t0e; if(diff < halfweek) return diff; else return (7*86400 - tow) + t0e; } else { // "t0e in future" diff = 7*86400 - t0e + tow; if(diff < halfweek) return diff; else return tow - t0e; // in the future, negative age } } int main(int argc, char** argv) try { signal(SIGPIPE, SIG_IGN); InfluxPusher idb(argc > 3 ? argv[3] : "galileo"); MiniCurl::init(); H2OWebserver h2s("galmon"); h2s.addHandler("/global", [](auto handler, auto req) { nlohmann::json ret = nlohmann::json::object(); ret["leap-seconds"] = g_dtLS; map utcstats, gpsgststats; for(const auto& s: g_svstats) { // XXX this might run BEFORE we have gps/utc stats! int dw = (uint8_t)g_wn - s.second.wn0t; int age = dw * 7 * 86400 + g_tow - s.second.t0t * 3600; utcstats[age]=s.first; uint8_t wn0g = s.second.wn0t; int dwg = (((uint8_t)g_wn)&(1+2+4+8+16+32)) - wn0g; age = dwg*7*86400 + g_tow - s.second.t0g * 3600; gpsgststats[age]=s.first; } if(utcstats.empty()) { ret["utc-offset-ns"]=nullptr; } else { int sv = utcstats.begin()->second; // freshest SV long shift = g_svstats[sv].a0 * (1LL<<20) + g_svstats[sv].a1 * utcstats.begin()->first; // in 2^-50 seconds units ret["utc-offset-ns"] = 1.073741824*ldexp(shift, -20); ret["leap-second-planned"] = (g_svstats[sv].dtLSF != g_svstats[sv].dtLS); } if(gpsgststats.empty()) { ret["gps-offset-ns"]=nullptr; } else { int sv = gpsgststats.begin()->second; // freshest SV long shift = g_svstats[sv].a0g * (1U<<16) + g_svstats[sv].a1g * utcstats.begin()->first; // in 2^-51 seconds units ret["gps-offset-ns"] = 1.073741824*ldexp(shift, -21); } return ret; }); h2s.addHandler("/svs", [](auto handler, auto req) { nlohmann::json ret = nlohmann::json::object(); for(const auto& s: g_svstats) if(s.second.completeIOD()) { nlohmann::json item = nlohmann::json::object(); item["iod"]=s.second.getIOD(); item["sisa"]=humanSisa(s.second.liveIOD().sisa); item["a0"]=s.second.a0; item["a1"]=s.second.a1; item["dtLS"]=s.second.dtLS; item["a0g"]=s.second.a0g; item["a1g"]=s.second.a1g; item["e5bdvs"]=s.second.e5bdvs; item["e1bdvs"]=s.second.e1bdvs; item["e5bhs"]=s.second.e5bhs; item["e1bhs"]=s.second.e1bhs; item["elev"]=s.second.el; item["db"]=s.second.db; item["eph-age-m"] = ephAge(g_tow, 60*s.second.liveIOD().t0e)/60.0; item["last-seen-s"] = s.second.tow ? (7*86400*(g_wn - s.second.wn) + (int)g_tow - (int)s.second.tow) : -1; item["af0"] = s.second.liveIOD().af0; item["af1"] = s.second.liveIOD().af1; item["af2"] = (int)s.second.liveIOD().af2; item["t0c"] = s.second.liveIOD().t0c; Point our = g_ourpos; Point p; Point core; getCoordinates(g_wn, g_tow, s.second.liveIOD(), &p); Vector core2us(core, our); Vector dx(our, p); // = x-ourx, dy = y-oury, dz = z-ourz; /* https://gis.stackexchange.com/questions/58923/calculating-view-angle to calculate elevation: Cos(elevation) = (x*dx + y*dy + z*dz) / Sqrt((x^2+y^2+z^2)*(dx^2+dy^2+dz^2)) Obtain its principal inverse cosine. Subtract this from 90 degrees if you want the angle of view relative to a nominal horizon. This is the "elevation." NOTE! x = you on the ground! */ double elev = acos ( core2us.inner(dx) / (core2us.length() * dx.length())); double deg = 180.0* (elev/M_PI); item["calc-elev"] = 90 - deg; cout< (" << p.x << ", "<< p.y <<", "<< p.z<<") "< 2 ? argv[2] : "./html/"); int port = argc > 1 ? atoi(argv[1]) : 29599; std::thread ws([&h2s, port]() { auto actx = h2s.addContext(); h2s.addListener(ComboAddress("::", port), actx); cout<<"Listening on port "<< port < strs; boost::split(strs,line,boost::is_any_of(",")); for(unsigned int n=4; n + 4 < strs.size(); n += 4) { int sv = atoi(strs[n].c_str()); if(sv < 37) { g_svstats[sv].el = atoi(strs[n+1].c_str()); g_svstats[sv].azi = atoi(strs[n+2].c_str()); if(g_svstats[sv].el >= 0) g_svstats[sv].db = atoi(strs[n+3].c_str()); else g_svstats[sv].db = -1; idb.addValue(sv, "db", g_svstats[sv].db); idb.addValue(sv, "elev", g_svstats[sv].el); idb.addValue(sv, "azi", g_svstats[sv].azi); } } } line.clear(); } continue; } c = getUint8(); if(c != 0x62) { ungetc(c, stdin); // might be 0xb5 continue; } if(lastDisplay != time(0)) { lastDisplay = time(0); if(g_wn && g_tow) { cout<<"UTC from satellite: "< msg; msg.reserve(msgLen); for(int n=0; n < msgLen; ++n) msg.append(1, getUint8()); uint16_t ubxChecksum = getUint16(); if(ubxChecksum != calcUbxChecksum(ubxClass, ubxType, msg)) { cout< inav = getInavFromSFRBXMsg(msg); // cout<<"inav for "<=1 && wtype <= 4) { // ephemeris uint16_t iod = getbitu(&inav[0], 6, 10); if(wtype == 1 && g_tow) { } else if(wtype == 3) { idb.addValue(sv, "sisa", g_svstats[sv].iods[iod].sisa); } else if(wtype == 4) { idb.addValue(sv, "af0", g_svstats[sv].iods[iod].af0); idb.addValue(sv, "af1", g_svstats[sv].iods[iod].af1); idb.addValue(sv, "af2", g_svstats[sv].iods[iod].af2); idb.addValue(sv, "t0c", g_svstats[sv].iods[iod].t0c); double age = ephAge(g_tow, g_svstats[sv].iods[iod].t0c * 60); /* cout<<"Atomic age "<= 0) { time_t t = utcFromGST(g_wn, g_tow); sisacsv << t <<" " << ent.first << " " << (unsigned int) ent.second.liveIOD().sisa << endl; // cout << t <<" " << ent.first << " " << (unsigned int) ent.second.liveIOD().sisa << "\n"; double clockage = ephAge(g_tow, ent.second.liveIOD().t0c * 60); double offset = 1.0*ent.second.liveIOD().af0/(1LL<<34) + clockage * ent.second.liveIOD().af1/(1LL<<46); clockcsv << t << " " << ent.first<<" " << ent.second.liveIOD().af0 << " " << ent.second.liveIOD().af1 <<" " << (int)ent.second.liveIOD().af2 <<" " << 935280000 + g_wn *7*86400 + ent.second.liveIOD().t0c * 60 << " " << clockage << " " << offset<= 0) g_svstats[sv].db = (int)msg[10+12*n]; else g_svstats[sv].db = -1; idb.addValue(sv, "db", g_svstats[sv].db); idb.addValue(sv, "elev", g_svstats[sv].el); idb.addValue(sv, "azi", g_svstats[sv].azi); } } } else if (ubxClass == 4) { // info fmt::printf("Log level %d: %.*s\n", (int)ubxType, msg.size(), (char*)msg.c_str()); } else if (ubxClass == 6 && ubxType == 0) { // port config fmt::printf("Port config: "); for(const auto& c : msg) fmt::printf("0x%02x,", (int)c); cout<<"\n"; uint8_t outmask=msg[14]; if(outmask & 1) cout<<" out UBX"<