#include #include #include #include #include "fmt/format.h" #include "fmt/printf.h" #include "CLI/CLI.hpp" #include #include #include #include #include #include #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 "navmon.hh" #include "tle.hh" #include "sp3.hh" #include "ubx.hh" #include #include #include "sbas.hh" #include "version.hh" #include "gpscnav.hh" #include "rinex.hh" #include "rtcm.hh" static char program[]="navdump"; using namespace std; extern const char* g_gitHash; map g_srcpos; vector g_sp3s; bool sp3Order(const SP3Entry& a, const SP3Entry& b) { return tie(a.gnss, a.sv, a.t) < tie(b.gnss, b.sv, b.t); } void readSP3s(string_view fname) { SP3Reader sp3(fname); SP3Entry e; while(sp3.get(e)) { g_sp3s.push_back(e); } sort(g_sp3s.begin(), g_sp3s.end(), sp3Order); } string beidouHealth(int in) { string ret; if(in == 256) { return "NO CLOCK"; } if(in==511) { return "NO SAT"; } if(in & (1<<7)) ret += "B1I abnormal "; if(in & (1<<6)) ret += "B2I abnormal "; if(in & (1<<5)) ret += "B3I abnormal "; if(in & (1<<1)) ret += "navigation abnormal "; if(ret.empty()) return "OK"; return ret; } // GALILEO ONLY!! template void doOrbitDump(int gnss, int sv, int wn, const T& oldEph, const T& newEph, int time_start, int time_end) { ofstream orbitcsv("orbit."+to_string(gnss)+"."+to_string(sv)+"."+to_string(oldEph.iodnav)+"-"+to_string(newEph.iodnav)+".csv"); orbitcsv << "timestamp x y z oldx oldy oldz\n"; orbitcsv << fixed; for(int t = time_start; t < time_end; t += 30) { Point p, oldp; getCoordinates(t, newEph, &p); getCoordinates(t, oldEph, &oldp); time_t posix = utcFromGST(wn, t); orbitcsv << posix <<" " < d_filter; }; struct FixStat { double iTow{-1}; struct SatStat { double bestrange1{-1}; // corrected for clock double bestrange5{-1}; // corrected for clock double doppler1{-1}; // corrected for clock double doppler5{-1}; // corrected for clock double radvel{-1}; double ephrange{-1}; GalileoMessage ephemeris; }; map, SatStat> sats; }; double g_rcvoffset; void emitFixState(int src, double iTow, FixStat& fs, int n) { cout<<"\nFix state for source "<t) <<" to "<< humanTime(g_sp3s.rbegin()->t)< svpairs; vector stations; bool doReceptionData{false}; bool doRFData{true}; bool doObserverPosition{false}; bool doObserverDetails{false}; bool doTimeOffsets{false}; bool doVERSION{false}; string rinexfname; string osnmafname; app.add_option("--svs", svpairs, "Listen to specified svs. '0' = gps, '2' = Galileo, '2,1' is E01"); app.add_option("--stations", stations, "Listen to specified stations."); app.add_option("--positions,-p", doObserverPosition, "Print out observer positions (or not)"); app.add_option("--rfdata,-r", doRFData, "Print out RF data (or not)"); app.add_option("--observerdetails,-o", doObserverDetails, "Print out observer detail data (or not)"); app.add_option("--timeoffsets,-t", doTimeOffsets, "Print out timeoffset data (or not)"); app.add_option("--recdata", doReceptionData, "Print out reception data (or not)"); app.add_option("--rinex", rinexfname, "If set, emit ephemerides to this filename"); app.add_option("--osnma", osnmafname, "If set, emit OSNMA CSV to this filename"); app.add_flag("--version", doVERSION, "show program version and copyright"); try { app.parse(argc, argv); } catch(const CLI::Error &e) { return app.exit(e); } if(doVERSION) { showVersion(program, g_gitHash); exit(0); } SVFilter svfilter; for(const auto& svp : svpairs) { svfilter.addFilter(svp); } set statset; for(const auto& i : stations) statset.insert(i); ofstream almanac("almanac.txt"); ofstream iodstream("iodstream.csv"); iodstream << "timestamp gnssid sv iodnav t0e age" << endl; ofstream ephcsv("eph.csv"); ephcsv<<"timestamp gnssid sv old_iod new_iod age insta_age x y z lat lon h"< rnw; if(!rinexfname.empty()) rnw = RINEXNavWriter(rinexfname); std::optional osnmacsv; if(!osnmafname.empty()) { osnmacsv = ofstream(osnmafname); (*osnmacsv)<<"wn,tow,wtype,sv,osnma\n"; } for(;;) { char bert[4]; int res = readn2(0, bert, 4); if( res != 4) { cerr<<"EOF, res = "< galEphemeris; if(nmm.type() == NavMonMessage::ReceptionDataType) { if(doReceptionData) { etstamp(); cout<<"receptiondata for "< inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size()); static map gms; static map, GalileoMessage> gmwtypes; static map oldgm4s; int sv = nmm.gi().gnsssv(); if(!svfilter.check(2, sv, nmm.gi().sigid())) continue; etstamp(); int64_t imptow = (((uint32_t)round(nmm.localutcseconds() + nmm.localutcnanoseconds()/1000000000.0) - 935280000 + 18) % (7*86400)) - 2; if(nmm.gi().sigid() != 5) { if(!(imptow % 2)) imptow--; } else if((imptow % 2)) imptow--; if(imptow != nmm.gi().gnsstow()) cout<< " !!"<< (imptow - nmm.gi().gnsstow()) <<"!! "; GalileoMessage& gm = gms[sv]; int wtype = gm.parse(inav); gm.tow = nmm.gi().gnsstow(); bool isnew = gmwtypes[{nmm.gi().gnsssv(), wtype}].tow != gm.tow; gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm; static map oldEph; cout << "gal inav wtype "<=1 && wtype <= 5) { if(nmm.gi().has_reserved1()) { cout<<" res1 "<gnss == e.gnss && bestSP3->sv == sv) { static set> haveSeen; if(!haveSeen.count({sv, bestSP3->t})) { haveSeen.insert({sv, bestSP3->t}); Point newPoint; double E=getCoordinates(gm.tow + (bestSP3->t - start), gm, &newPoint, false); Point sp3Point(bestSP3->x, bestSP3->y, bestSP3->z); Vector dist(newPoint, sp3Point); Vector nspeed; getSpeed(gm.tow + (bestSP3->t - start), gm, &nspeed); Vector speed = nspeed; nspeed.norm(); double along = nspeed.inner(dist); cout<<"\nsp3 "<<(bestSP3->t - start)<<" E"<t)<<" (" << newPoint.x/1000.0 <<", "<x/1000.0) <<", " << (bestSP3->y/1000.0) <<", " << (bestSP3->z/1000.0) << ") "<clockBias << " " << gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first<< " " << dist.length()<< " "; cout << (bestSP3->clockBias - gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first); cout << " " << gm.af0 <<" " << gm.af1; cout << endl; sp3csv <t << " 2 "<< sv <<" " << ephAge(gm.tow+(bestSP3->t - start), gm.getT0e()) <<" "<x<<" " << bestSP3->y<<" " <z <<" " << newPoint.x<<" " <clockBias <<" "; sp3csv << gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first<<" " << dist.length() <<" " << along <<" "; sp3csv << (bestSP3->clockBias - gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first) << " " << E << " " << speed.length()<emitEphemeris(sid, gm); // gm.toJSON(); cout<<" disco! "<< oldEph[sv].iodnav << " - > "<(latlonh)/M_PI<<" " << 180*get<1>(latlonh)/M_PI <<" " <(latlonh) << "\n"; oldEph[sv]=gm; } } } if(wtype == 1) { cout << " iodnav " << gm.iodnav <<" t0e "<< gm.t0e*60 <<" " << ephAge(gm.t0e*60, gm.tow); } if(wtype == 2 || wtype == 3) { cout << " iodnav " << gm.iodnav; if(wtype == 3) cout<<" sisa "<<(int)gm.sisa; } if(wtype == 1 || wtype == 2 || wtype == 3) { iodstream << nmm.localutcseconds()<<" " << nmm.gi().gnssid() <<" "<< nmm.gi().gnsssv() << " " << gm.iodnav << " " << gm.t0e*60 <<" " << ephAge(gm.t0e*60, gm.tow); if(wtype == 3) iodstream<=7 && wtype<=10) cout<<" ioda "< 31) dw = 31- dw; int delta = dw*7*86400 + gm.tow - gm.getT0g(); // NOT ephemeris age tricks cout<<" wn%64 "<< (gm.wn%64) << " dw " << dw <<" delta " << delta; } cout< cnav((uint8_t*)nmm.gc().contents().c_str(), nmm.gc().contents().size()); int sv = nmm.gc().gnsssv(); if(!svfilter.check(2, sv, nmm.gc().sigid())) continue; etstamp(); cout << "C/NAV for " << nmm.gc().gnssid()<<","< fnav((uint8_t*)nmm.gf().contents().c_str(), nmm.gf().contents().size()); int sv = nmm.gf().gnsssv(); if(!svfilter.check(2, sv, nmm.gf().sigid())) continue; etstamp(); GalileoMessage gm; gm.parseFnav(fnav); cout<<"gal F/NAV wtype "<< (int)gm.wtype << " for "<((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size())); struct GPSState gs; static map eph; static map almas; uint8_t page; static int gpswn; int frame=gs.parseGPSMessage(cond, &page); cout<<"GPS "< oldgs1s; static map oldgs2s; if(frame == 1) { gpswn = gs.wn; cout << "gpshealth = "<<(int)gs.gpshealth<<", wn "<second.t0c != gs.t0c) { auto oldOffset = getGPSAtomicOffset(gs.tow, iter->second); auto newOffset = getGPSAtomicOffset(gs.tow, gs); cout<<" Timejump: "<second) )<<" seconds, old t0c "<second.t0c; } oldgs1s[sv] = gs; } else if(frame == 2) { eph[sv].parseGPSMessage(cond, &page); // gs in frame 2 contains t0e, so legit cout << "t0e = "<gnss == e.gnss && bestSP3->sv == sv) { static set> haveSeen; if(!haveSeen.count({sv, bestSP3->t})) { haveSeen.insert({sv, bestSP3->t}); Point newPoint; double E=getCoordinates(gs.tow + (bestSP3->t - start), eph[sv], &newPoint, false); Point sp3Point(bestSP3->x, bestSP3->y, bestSP3->z); Vector dist(newPoint, sp3Point); Vector nspeed; getSpeed(gs.tow + (bestSP3->t - start), eph[sv], &nspeed); Vector speed = nspeed; nspeed.norm(); double along = nspeed.inner(dist); cout<<"\nsp3 "<<(bestSP3->t - start)<<" G"<t)<<" (" << newPoint.x/1000.0 <<", "<x/1000.0) <<", " << (bestSP3->y/1000.0) <<", " << (bestSP3->z/1000.0) << ") "<clockBias << " " << getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first<< " " << dist.length()<< " "; cout << (bestSP3->clockBias - getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first); cout << " " << gs.af0 <<" " << gs.af1; cout << endl; sp3csv <t << " 0 "<< sv <<" " << ephAge(gs.tow+(bestSP3->t - start), eph[sv].getT0e()) <<" "<x<<" " << bestSP3->y<<" " <z <<" " << newPoint.x<<" " <clockBias <<" "; sp3csv << getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first<<" " << dist.length() <<" " << along <<" "; sp3csv << (bestSP3->clockBias - getGPSAtomicOffset(gs.tow + (bestSP3->t-start), oldgs1s[sv]).first) << " " << E << " " << speed.length()<= 25 && gs.gpsalma.sv <= 32) || gs.gpsalma.sv==57 ) { // see table 20-V of the GPS ICD cout << " data-id "< ("; cout<< ed.dradial<<", "< ("; cout<< ed.dradial<<", "< states; auto& state = states[sv]; int type = parseGPSCNavMessage( std::basic_string((uint8_t*)nmm.gpsc().contents().c_str(), nmm.gpsc().contents().size()), state); SatID sid{0, (uint32_t)sv, (uint32_t)sigid}; cout << "GPS CNAV " << makeSatIDName(sid) <<" tow "< cond; try { cond = getCondensedBeidouMessage(std::basic_string((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size())); } catch(std::exception& e) { cout<<"Parsing error"< bms; auto& bm = bms[sv]; int fraid = bm.parse(cond, &pageno); cout<<"BeiDou "< msgs; if(msgs[sv].wn>= 0 && msgs[sv].t0c != bm.t0c) { auto oldOffset = msgs[sv].getAtomicOffset(bm.sow); auto newOffset = bm.getAtomicOffset(bm.sow); cout<<" Timejump: "<((uint8_t*)nmm.bid2().contents().c_str(), nmm.bid2().contents().size())); BeidouMessage bm; uint8_t pageno; int fraid = bm.parse(cond, &pageno); cout<<"BeiDou "< gms; auto& gm = gms[nmm.gloi().gnsssv()]; int strno = gm.parse(std::basic_string((uint8_t*)nmm.gloi().contents().c_str(), nmm.gloi().contents().size())); cout<<"Glonass R"< "; cout << "("<(latlonh)/M_PI <<" lat "<< 180*std::get<0>(latlonh)/M_PI <<" elev "<< std::get<2>(latlonh) << " acc "<(latlonh)/M_PI<<" "<< // 180*std::get<0>(latlonh)/M_PI<<" "<(latlonh)<<" "< fixes; if(nmm.rfd().gnssid()==2 && galEphemeris.count(nmm.rfd().gnsssv())) { // galileo const auto& eph = galEphemeris[nmm.rfd().gnsssv()]; Point sat; auto [offset, trend] = eph.getAtomicOffset(round(nmm.rfd().rcvtow())); (void)trend; static int n; double E=getCoordinates(nmm.rfd().rcvtow(), eph, &sat); double range = Vector(g_srcpos[nmm.sourceid()], sat).length(); double origrange = range; E=getCoordinates(nmm.rfd().rcvtow() - range/299792458.0, eph, &sat); range = Vector(g_srcpos[nmm.sourceid()], sat).length(); cout << " d "< sbas((uint8_t*)nmm.sbm().contents().c_str(), nmm.sbm().contents().size()); cout<<" PRN "< sbstate; if(type == 0) { sbstate[nmm.sbm().gnsssv()].parse0(sbas, nmm.localutcseconds()); } else if(type == 1) { sbstate[nmm.sbm().gnsssv()].parse1(sbas, nmm.localutcseconds()); } else if(type == 6) { auto integ = sbstate[nmm.sbm().gnsssv()].parse6(sbas, nmm.localutcseconds()); cout<<"integrity updated: "; for(const auto& i : integ) { cout<((const uint8_t*)nmm.dm().payload().c_str(), nmm.dm().payload().size())); if(res.empty()) continue; etstamp(); uint64_t maxt=0; for(const auto& sv : res) { if(sv.gnss != 2) continue; if(sv.tr > maxt) maxt = sv.tr; } double ttag = round( (ldexp(1.0*maxt, -32)/1000.0 + 0.08) / 0.1) * 0.1; double rtow = round(ldexp(1.0*maxt, -32) /1000.0); // this was the rounded tow of transmission sort(res.begin(), res.end(), [&](const auto& a, const auto& b) { double elevA=0, elevB=0; if(galEphemeris.count(a.sv)) { const auto& eph = galEphemeris[a.sv]; Point sat; getCoordinates(rtow, eph, &sat); elevA=getElevationDeg(sat, g_srcpos[nmm.sourceid()]); } if(galEphemeris.count(b.sv)) { const auto& eph = galEphemeris[b.sv]; Point sat; getCoordinates(rtow, eph, &sat); elevB=getElevationDeg(sat, g_srcpos[nmm.sourceid()]); } return elevB < elevA; }); double toffsetms=0; bool first = true; for(const auto sv : res) { if(sv.gnss != 2) continue; if(!galEphemeris.count(sv.sv)) continue; const auto& eph = galEphemeris[sv.sv]; double clockoffms = eph.getAtomicOffset(rtow).first/1000000.0; Point sat; double E=getCoordinates(rtow - clockoffms/1000.0, eph, &sat); double range = Vector(g_srcpos[nmm.sourceid()], sat).length(); getCoordinates(rtow - clockoffms/1000.0 - range/299792458.0, eph, &sat); range = Vector(g_srcpos[nmm.sourceid()], sat).length(); double trmsec = ldexp(maxt - sv.tr, -32) + clockoffms; constexpr double omegaE = 2*M_PI /86164.091 ; double rotcor = omegaE * (sat.x*g_srcpos[nmm.sourceid()].y - sat.y * g_srcpos[nmm.sourceid()].x) / 299792458.0; range += rotcor; double bgdcor = 299792458.0 *ldexp(eph.BGDE1E5b,-32); range -= bgdcor; constexpr double speedOfLightPerNS = 299792458.0 / 1000000000.0; // Δtr=F e A1/2 sin(E) constexpr double F = 1000000000.0*-4.442807309e-10; // "in ns" double dtr = F * eph.getE() * eph.getSqrtA() * sin(E); double relcor = speedOfLightPerNS * dtr; range -= relcor; double predTrmsec = range / 299792.4580; if(first) { toffsetms = trmsec - predTrmsec; first = false; cout<<"Set toffsetms to "<< toffsetms << " for tow "<