#define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ubx.hh" #include "navmon.hh" #include #include #include #include "fmt/format.h" #include "fmt/printf.h" #include "bits.hh" #include "galileo.hh" #include #include "navmon.pb.h" #include "gps.hh" #include "glonass.hh" #include "beidou.hh" #include "CLI/CLI.hpp" #include #include #include "comboaddress.hh" #include "swrappers.hh" #include "sclasses.hh" #include "version.hh" static char program[]="ubxtool"; bool doDEBUG{false}; bool doLOGFILE{false}; bool doVERSION{false}; struct timespec g_gnssutc; uint16_t g_galwn; using namespace std; uint16_t g_srcid{0}; int g_fixtype{-1}; double g_speed{-1}; extern const char* g_gitHash; static int getBaudrate(int baud) { if(baud==115200) return B115200; else if(baud==57600) return B57600; else if(baud==38400) return B38400; else if(baud==19200) return B19200; else if(baud==9600) return B9600; else throw std::runtime_error("Unknown baudrate "+to_string(baud)); } static int g_baudval; size_t writen2(int fd, const void *buf, size_t count) { const char *ptr = (char*)buf; const char *eptr = ptr + count; ssize_t res; while(ptr != eptr) { res = ::write(fd, ptr, eptr - ptr); if(res < 0) { throw runtime_error("failed in writen2: "+string(strerror(errno))); } else if (res == 0) throw EofException(); ptr += (size_t) res; } return count; } /* inav schedule: 1) Find plausible start time of next cycle Current cycle: TOW - (TOW%30) Next cycle: TOW - (TOW%30) + 30 E1: t n w 0 1: 2 tow % 30 == 1 2 2: 4 tow % 30 == 3 4 3: 6 TOW 5 -> set startTow, startTowFresh 6 4: 7/9 7 8 5: 8/10 9 10 6: 0 WN/TOW 11 12 7: 0 WN/TOW 13 14 8: 0 WN/TOW 15 16 9: 0 WN/TOW 17 18 10: 0 WN/TOW 19 20 11: 1 21 22 12: 3 23 24 13: 5 WN/TOW 25 26 14: 0 WN/TOW 27 28 15: 0 WN/TOW 29 E5b-1: t n w 0 1: 1 (2/2) tow % 30 == 0 2 2: 3 tow % 30 == 2 4 3: 5 WN/TOW 4 -> set startTow, startTowFresh 6 4: 7/9 8 5: 8/10 10 6: 0 WN/TOW 12 7: 0 WN/TOW 14 8: 0 WN/TOW 16 9: 0 WN/TOW 18 10: 0 WN/TOW 20 11: 2 22 12: 4 24 13: 6 TOW 26 14: 0 WN/TOW 28 15: 1 (1/2) WN/TOW */ class UBXMessage { public: struct BadChecksum{}; explicit UBXMessage(basic_string_view src) { d_raw = src; if(d_raw.size() < 6) throw std::runtime_error("Partial UBX message"); uint16_t csum = calcUbxChecksum(getClass(), getType(), d_raw.substr(6, d_raw.size()-8)); if(csum != d_raw.at(d_raw.size()-2) + 256*d_raw.at(d_raw.size()-1)) throw BadChecksum(); } uint8_t getClass() const { return d_raw.at(2); } uint8_t getType() const { return d_raw.at(3); } std::basic_string getPayload() const { return d_raw.substr(6, d_raw.size()-8); } std::basic_string d_raw; }; bool g_fromFile{false}; #if !defined(O_LARGEFILE) #define O_LARGEFILE 0 #endif std::pair getUBXMessage(int fd, double* timeout) { static int logfile=0; if (doLOGFILE) { if(!logfile && !g_fromFile) { logfile = open("./logfile", O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE, 0600); if(!logfile) throw std::runtime_error("Failed to open logfile for writing"); } } uint8_t marker[2]={0}; for(;;) { marker[0] = marker[1]; int res = readn2Timeout(fd, marker+1, 1, timeout); if(res < 0) throw EofException(); // if (doDEBUG) { cerr< msg; msg.append(marker, 2); // 0,1 uint8_t b[4]; readn2Timeout(fd, b, 4, timeout); msg.append(b, 4); // class, type, len1, len2 uint16_t len = b[2] + 256*b[3]; // if (doDEBUG) { cerr< msg, uint8_t ubxClass, uint8_t ubxType) { for(int n=3; n; --n) { writen2(fd, &msg[0], msg.size()); try { return waitForUBX(fd, seconds, ubxClass, ubxType); } catch(...) { if(n==1) throw; cerr<<"Retransmit"< msg, uint8_t ubxClass, uint8_t ubxType) { for(int n=3; n; --n) { writen2(fd, &msg[0], msg.size()); try { return waitForUBXAckNack(fd, seconds, ubxClass, ubxType); } catch(...) { if(n==1) throw; cerr<<"Retransmit"< queue; std::mutex mut; void emitNMM(const NavMonMessage& nmm); }; public: void addDestination(int fd) { auto d = std::make_unique(); d->fd = fd; d_dests.push_back(std::move(d)); } void addDestination(const std::string& dest) { auto d = std::make_unique(); d->dst = dest; d_dests.push_back(std::move(d)); } void launch() { for(auto& d : d_dests) { if(!d->dst.empty()) { thread t(&NMMSender::sendTCPThread, this, d.get()); t.detach(); } } } void sendTCPThread(Destination* d) { struct NameError{}; for(;;) { ComboAddress chosen; try { vector addrs; for(;;) { addrs=resolveName(d->dst, true, true); if(!addrs.empty()) break; cerr<dst<<", sleeping and trying again later"<dst<<" on "< mut(d->mut); if(!d->queue.empty()) { msg = d->queue.front(); } } if(!msg.empty()) { sc.writen(msg); std::lock_guard mut(d->mut); d->queue.pop_front(); } else usleep(100000); } } } catch(NameError&) { { std::lock_guard mut(d->mut); if (doDEBUG) { cerr<queue.size()<<" messages queued for "<dst<dst<<" via "< mut(d->mut); if (doDEBUG) { cerr<queue.size()<<" messages queued for "<dst<dst <<" via "< mut(d->mut); if (doDEBUG) { cerr<<"There are now "<queue.size()<<" messages queued for "<dst<<" via "<> d_dests; }; void NMMSender::emitNMM(const NavMonMessage& nmm) { for(auto& d : d_dests) { d->emitNMM(nmm); } } void NMMSender::Destination::emitNMM(const NavMonMessage& nmm) { string out; nmm.SerializeToString(& out); string msg("bert"); uint16_t len = htons(out.size()); msg.append((char*)&len, 2); msg.append(out); if(!dst.empty()) { std::lock_guard l(mut); queue.push_back(msg); } else writen2(fd, msg.c_str(), msg.size()); } bool version9 = false; void enableUBXMessageOnPort(int fd, uint8_t ubxClass, uint8_t ubxType, uint8_t port, uint8_t rate=1) { for(int n=0 ; n < 5; ++n) { try { basic_string payload; if(version9) { payload= basic_string({ubxClass, ubxType, rate}); } else { if(port > 6) throw std::runtime_error("Port number out of range (>6)"); payload.assign({ubxClass, ubxType, 0, 0, 0, 0, 0, 0}); payload[2+ port]=rate; } auto msg = buildUbxMessage(0x06, 0x01, payload); if(sendAndWaitForUBXAckNack(fd, 2, msg, 0x06, 0x01)) return; else throw std::runtime_error("Got NACK enabling UBX message "+to_string((int)ubxClass)+" "+to_string((int)ubxType)); } catch(TimeoutError& te) { if (doDEBUG) { cerr< destinations; string portName; int ubxport=3; int baudrate=115200; unsigned int fuzzPositionMeters=0; string owner; string remark; app.add_option("--destination,-d", destinations, "Send output to this IPv4/v6 address"); app.add_flag("--wait", doWait, "Wait a bit, do not try to read init messages"); app.add_flag("--reset", doReset, "Reset UBX device"); app.add_flag("--beidou,-c", doBeidou, "Enable BeiDou reception"); app.add_flag("--gps,-g", doGPS, "Enable GPS reception"); app.add_flag("--glonass,-r", doGlonass, "Enable Glonass reception"); app.add_flag("--galileo,-e", doGalileo, "Enable Galileo reception"); app.add_flag("--sbas,-s", doSBAS, "Enable SBAS (EGNOS/WAAS/etc) reception"); app.add_option("--rtscts", doRTSCTS, "Set hardware handshaking"); app.add_flag("--stdout", doSTDOUT, "Emit output to stdout"); auto pn = app.add_option("--port,-p", portName, "Device or file to read serial from"); app.add_option("--station", g_srcid, "Station id"); app.add_option("--ubxport,-u", ubxport, "UBX port to enable messages on (usb=3)"); app.add_option("--baud,-b", baudrate, "Baudrate for serial connection"); app.add_flag("--keep-nmea,-k", doKeepNMEA, "Don't disable NMEA"); app.add_flag("--fake-fix", doFakeFix, "Inject locally generated fake fix data"); app.add_option("--fuzz-position,-f", fuzzPositionMeters, "Fuzz position by this many meters"); app.add_option("--owner,-o", owner, "Name/handle/nick of owner/operator"); app.add_option("--remark", remark, "Remark for this station"); int surveyMinSeconds = 0; int surveyMinCM = 0; bool doSurveyReset=false; app.add_option("--survey-min-seconds", surveyMinSeconds, "Survey minimally this amount of seconds"); app.add_option("--survey-min-cm", surveyMinCM, "Survey until accuracy is better (lower) than this setting"); app.add_flag("--survey-reset", doSurveyReset, "Reset the Surveyed-in state"); app.add_flag("--debug", doDEBUG, "Display debug information"); app.add_flag("--logfile", doLOGFILE, "Create logfile"); 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); } if(! *pn) { cerr<<"you must provide the --port"< msg; if(doReset) { if (doDEBUG) { cerr< msg; if(version9) { cmd = 0x8a; msg = buildUbxMessage(0x06, cmd, {0x00, 0x01, 0x00, 0x00, 0x01,0x00,0x03,0x20, 1, // survey in mode // min survey time: 0x10,0x00,0x03,0x40, ptrSeconds[0], ptrSeconds[1], ptrSeconds[2], ptrSeconds[3], 0x11,0x00,0x03,0x40, ptrCent[0], ptrCent[1], ptrCent[2], ptrCent[3] }); } else { minCentimetersVal /= 10; cmd = 0x3d; msg = buildUbxMessage(0x06, cmd, { 1,0,0,0, // survey-in, res, flag1, flag2 0,0,0,0, // x 0,0,0,0, // y 0,0,0,0, // z 0,0,0,0, // fixed position accuracy ptrSeconds[0], ptrSeconds[1], ptrSeconds[2], ptrSeconds[3], ptrCent[0], ptrCent[1], ptrCent[2], ptrCent[3] }); } cerr<>8) & 0xFF), (unsigned char)((baudrate>>16) & 0xFF), (unsigned char)((baudrate>>24) & 0xFF), 0x03,0x00,0x01,0x00, 0x00,0x00,0x00,0x00 }); } else { msg = buildUbxMessage(0x06, 0x00, {(unsigned char)(ubxport),0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00}); } if(sendAndWaitForUBXAckNack(fd, 10, msg, 0x06, 0x00)) { // disable NMEA if (doDEBUG) { cerr<set_x(3924698.1158); nmm.mutable_op()->set_y(301124.8036); nmm.mutable_op()->set_z(5001904.9952); nmm.mutable_op()->set_acc(3.14); ns.emitNMM( nmm); } } if(msg.getClass() == 0x02 && msg.getType() == 0x15) { // RAWX, the doppler stuff // if (doDEBUG) { cerr<set_gnssid(gnssid); nmm.mutable_rfd()->set_gnsssv(sv); nmm.mutable_rfd()->set_sigid(sigid); nmm.mutable_rfd()->set_rcvtow(rcvTow); nmm.mutable_rfd()->set_rcvwn(rcvWn); nmm.mutable_rfd()->set_doppler(doppler); nmm.mutable_rfd()->set_carrierphase(cpMes); nmm.mutable_rfd()->set_pseudorange(prMes); nmm.mutable_rfd()->set_prstd(ldexp(0.01, prStddev)); nmm.mutable_rfd()->set_dostd(ldexp(0.002, doStddev)); nmm.mutable_rfd()->set_cpstd(cpStddev*0.4); nmm.mutable_rfd()->set_locktimems(locktimems); nmm.mutable_rfd()->set_cno(cno); nmm.mutable_rfd()->set_prvalid(trkStat & 1); nmm.mutable_rfd()->set_cpvalid(trkStat & 2); nmm.mutable_rfd()->set_halfcycvalid(trkStat & 4); nmm.mutable_rfd()->set_subhalfcyc(trkStat & 8); nmm.mutable_rfd()->set_clkreset(clkReset); ns.emitNMM( nmm); } } else if(msg.getClass() == 0x01 && msg.getType() == 0x01) { // POSECF struct pos { uint32_t iTOW; int32_t ecefX; int32_t ecefY; int32_t ecefZ; uint32_t pAcc; }; pos p; memcpy(&p, payload.c_str(), sizeof(pos)); if(fuzzPositionMeters) { p.ecefX -= (p.ecefX % (fuzzPositionMeters*100)); p.ecefY -= (p.ecefY % (fuzzPositionMeters*100)); p.ecefZ -= (p.ecefZ % (fuzzPositionMeters*100)); } /* if (doDEBUG) { cerr<set_x(p.ecefX /100.0); nmm.mutable_op()->set_y(p.ecefY /100.0); nmm.mutable_op()->set_z(p.ecefZ /100.0); nmm.mutable_op()->set_acc(p.pAcc /100.0); if(g_speed >= 0.0) nmm.mutable_op()->set_groundspeed(g_speed); ns.emitNMM( nmm); } else if(msg.getClass() == 2 && msg.getType() == 0x13) { // SFRBX // order: 2, 4, 6, 7/9, 8/10, 0, 0, 0, 0, 0, 1, 3, 5, 0, 0 // * * * * * * * // tow try { pair id = make_pair(payload[0], payload[1]); int sigid = payload[2]; static set> svseen; static time_t lastStat; svseen.insert({id.first, id.second, payload[2]}); if(time(0)- lastStat > 30) { cerr<(s)<<","<(s)<<"@"<(s)<<" "; } cerr<set_gnsswn(wn); // XXX this sucks nmm.mutable_gpsi()->set_sigid(sigid); nmm.mutable_gpsi()->set_gnsstow(tow); // "with 6 second increments" -- needs to be adjusted nmm.mutable_gpsi()->set_gnssid(id.first); nmm.mutable_gpsi()->set_gnsssv(id.second); nmm.mutable_gpsi()->set_contents(string((char*)gpsframe.c_str(), gpsframe.size())); ns.emitNMM( nmm); continue; } if(id.first == 0 && sigid) { // new GPS auto cnav = getGPSFromSFRBXMsg(payload); static int wn, tow; int type = getbitu(&cnav[0], 14, 6); tow = 6 * getbitu(&cnav[0], 20, 17) - 12; if(type == 10) { wn = getbitu(&cnav[0], 38, 13); } if(!wn) continue; // can't file this yet NavMonMessage nmm; nmm.set_type(NavMonMessage::GPSCnavType); nmm.set_localutcseconds(g_gnssutc.tv_sec); nmm.set_localutcnanoseconds(g_gnssutc.tv_nsec); nmm.set_sourceid(g_srcid); nmm.mutable_gpsc()->set_gnsswn(wn); // XXX this sucks nmm.mutable_gpsc()->set_sigid(sigid); nmm.mutable_gpsc()->set_gnsstow(tow); // "with 6 second increments" -- needs to be adjusted nmm.mutable_gpsc()->set_gnssid(id.first); nmm.mutable_gpsc()->set_gnsssv(id.second); nmm.mutable_gpsc()->set_contents(string((char*)cnav.c_str(), cnav.size())); ns.emitNMM( nmm); } else if(id.first ==2) { // GALILEO auto inav = getInavFromSFRBXMsg(payload); unsigned int wtype = getbitu(&inav[0], 0, 6); uint32_t satTOW; int msgTOW{0}; if(getTOWFromInav(inav, &satTOW, &g_galwn)) { // 0, 6, 5 // if (doDEBUG) { cerr<set_gnsswn(g_galwn); nmm.mutable_gi()->set_gnsstow(msgTOW); nmm.mutable_gi()->set_gnssid(id.first); nmm.mutable_gi()->set_gnsssv(id.second); nmm.mutable_gi()->set_sigid(sigid); nmm.mutable_gi()->set_contents((const char*)&inav[0], inav.size()); ns.emitNMM( nmm); } else if(id.first==3) { auto gstr = getGlonassFromSFRBXMsg(payload); auto cond = getCondensedBeidouMessage(gstr); static map bms; auto& bm = bms[id.second]; uint8_t pageno; bm.parse(cond, &pageno); if(bm.wn < 0) { if (doDEBUG) { cerr< 5) { // this **HARDCODES** that C01,02,03,04,05 emit D2 messages! nmm.set_type(NavMonMessage::BeidouInavTypeD1); nmm.mutable_bid1()->set_gnsswn(bm.wn); // only sent in word 1!! nmm.mutable_bid1()->set_gnsstow(bm.sow); nmm.mutable_bid1()->set_gnssid(id.first); nmm.mutable_bid1()->set_gnsssv(id.second); nmm.mutable_bid1()->set_sigid(sigid); nmm.mutable_bid1()->set_contents(string((char*)gstr.c_str(), gstr.size())); ns.emitNMM( nmm); } else { // not sending this: we can't even get the week number right! /* nmm.set_type(NavMonMessage::BeidouInavTypeD2); nmm.mutable_bid2()->set_gnsswn(bm.wn); nmm.mutable_bid2()->set_gnsstow(bm.sow); nmm.mutable_bid2()->set_gnssid(id.first); nmm.mutable_bid2()->set_gnsssv(id.second); nmm.mutable_bid2()->set_sigid(sigid); nmm.mutable_bid2()->set_contents(string((char*)gstr.c_str(), gstr.size())); */ } continue; } else if(id.first==6) { // if (doDEBUG) { cerr< gms; GlonassMessage& gm = gms[id.second]; int strno = gm.parse(gstr); */ if(id.second != 255) { NavMonMessage nmm; nmm.set_localutcseconds(g_gnssutc.tv_sec); nmm.set_localutcnanoseconds(g_gnssutc.tv_nsec); nmm.set_sourceid(g_srcid); nmm.set_type(NavMonMessage::GlonassInavType); nmm.mutable_gloi()->set_freq(payload[3]); nmm.mutable_gloi()->set_gnssid(id.first); nmm.mutable_gloi()->set_gnsssv(id.second); nmm.mutable_gloi()->set_sigid(sigid); nmm.mutable_gloi()->set_contents(string((char*)gstr.c_str(), gstr.size())); ns.emitNMM( nmm); } } else if(id.first == 1) {// SBAS /* if (doDEBUG) { cerr<set_gnssid(id.first); nmm.mutable_sbm()->set_gnsssv(id.second); nmm.mutable_sbm()->set_contents(string((char*)sbas.c_str(), sbas.size())); ns.emitNMM( nmm); } else ; // if (doDEBUG) { cerr<set_gnssid(gnssid); nmm.mutable_rd()->set_gnsssv(sv); nmm.mutable_rd()->set_db(db); nmm.mutable_rd()->set_el(el); nmm.mutable_rd()->set_azi(azi); nmm.mutable_rd()->set_prres(*((int16_t*)(payload.c_str()+ 14 +12*n)) *0.1); uint32_t status; memcpy(&status, &payload[16+12*n], 4); nmm.mutable_rd()->set_qi(status & 7); nmm.mutable_rd()->set_used(status & 8); /* if (doDEBUG) { cerr<> 8)&7); cerr<<" eph-avail " << !!(status & (1<<11)); cerr<<" alm-avail " << !!(status & (1<<12)); cerr<set_gnssid(gnssid); nmm.mutable_rd()->set_gnsssv(sv); nmm.mutable_rd()->set_db(db); nmm.mutable_rd()->set_prres(*((int16_t*)(payload.c_str()+ 12 +16*n)) *0.1); // ENDIANISM nmm.mutable_rd()->set_sigid(sigid); nmm.mutable_rd()->set_el(0); nmm.mutable_rd()->set_azi(0); nmm.mutable_rd()->set_qi(qi); nmm.mutable_rd()->set_used(sigflags & 8); ns.emitNMM( nmm); } } else if(msg.getClass() == 1 && msg.getType() == 0x30) { // UBX-NAV-SVINFO } else if(msg.getClass() == 1 && msg.getType() == 0x22) { // UBX-NAV-CLOCK struct NavClock { uint32_t iTowMS; int32_t clkBNS; int32_t clkDNS; uint32_t tAcc; uint32_t fAcc; } nc; memcpy(&nc, &payload[0], sizeof(nc)); // cerr<<"Clock offset "<< nc.clkBNS<<" nanoseconds, drift "<< nc.clkDNS<<" nanoseconds/second, accuracy " << nc.tAcc<<" ns, frequency accuracy "<set_vendor("Ublox"); nmm.mutable_od()->set_hwversion(hwversion); nmm.mutable_od()->set_swversion(swversion); nmm.mutable_od()->set_serialno(serialno); nmm.mutable_od()->set_modules(mods); nmm.mutable_od()->set_clockoffsetns(nc.clkBNS); nmm.mutable_od()->set_clockoffsetdriftns(nc.clkDNS); nmm.mutable_od()->set_clockaccuracyns(nc.tAcc); nmm.mutable_od()->set_freqaccuracyps(nc.fAcc); nmm.mutable_od()->set_owner(owner); nmm.mutable_od()->set_remark(remark); nmm.mutable_od()->set_recvgithash(g_gitHash); nmm.mutable_od()->set_uptime(time(0) - starttime); ns.emitNMM( nmm); } else if(msg.getClass() == 0x02 && msg.getType() == 0x14) { // UBX-RXM-MEASX // if (doDEBUG) { cerr<set_gnssid(2); // Galileo only for now nmm.mutable_sr()->set_gnsssv(sv); nmm.mutable_sr()->set_sigid(0); // we should fill this in later nmm.mutable_sr()->set_type(payload[1]); nmm.mutable_sr()->set_identifier(string((char*)payload.c_str()+4, 8)); nmm.mutable_sr()->set_code(payload[12]); nmm.mutable_sr()->set_params(string((char*)payload.c_str()+13, payload.size()-14)); string hexstring; for(int n = 0; n < 15; ++n) hexstring+=fmt::sprintf("%x", (int)getbitu(payload.c_str(), 36 + 4*n, 4)); if (doDEBUG) { cerr<set_type(0); nmm.mutable_dm()->set_payload(string((char*)&payload[0], payload.size())); ns.emitNMM( nmm); } else if(msg.getClass() == 0x01 && msg.getType() == 0x3B) { // UBX-NAV-SVIN struct NavSin { uint8_t ver; uint8_t res[3]; uint32_t iTow; uint32_t dur; int32_t meanXCM, meanYCM, meanZCM; int8_t meanXHP, meanYHP, meanZHP; uint8_t res2; int32_t meanAcc; int32_t obs; int8_t valid; int8_t active; uint8_t res3[2]; } __attribute__((packed)); NavSin NS; static NavSin lastNS; if(payload.size() != sizeof(NS)) { cerr<<"Wrong NAV-SVIN message size, skipping"<(latlonh)<<" lon "<< get<1>(latlonh) << " h " << get<2>(latlonh) << endl; } lastNS = NS; } else if(msg.getClass() == 0x0d && msg.getType() == 0x04) { // UBX-TIM-SVIN struct TimSin { uint32_t dur; int32_t meanXCM, meanYCM, meanZCM; uint32_t meanVar; uint32_t obs; int8_t valid; int8_t active; uint8_t res3[2]; } __attribute__((packed)); TimSin TS; static TimSin lastTS; if(payload.size() != sizeof(TS)) { cerr<<"Wrong NAV-SVIN message size, skipping"<set_noiseperms(mhw.noisePerMS); nmm.mutable_ujs()->set_agccnt(mhw.agcCnt); nmm.mutable_ujs()->set_flags(mhw.flags); nmm.mutable_ujs()->set_jamind(mhw.jamInd); ns.emitNMM(nmm); } else if (doDEBUG) { cerr<