#define _LARGEFILE64_SOURCE #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 "githash.h" bool doDEBUG{false}; bool doLOGFILE{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}; 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 ComboAddress& 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.sin4.sin_port != 0) { thread t(&NMMSender::sendTCPThread, this, d.get()); t.detach(); } } } void sendTCPThread(Destination* d) { for(;;) { try { Socket s(d->dst.sin4.sin_family, SOCK_STREAM); SocketCommunicator sc(s); sc.setTimeout(3); sc.connect(d->dst); if (doDEBUG) { cerr<dst.toStringWithPort()< 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(std::exception& e) { if (doDEBUG) { cerr<dst.toStringWithPort()<<" had error: "< mut(d->mut); if (doDEBUG) { cerr<queue.size()<<" messages queued for "<dst.toStringWithPort()<dst.toStringWithPort()<<" had error"; } { std::lock_guard mut(d->mut); if (doDEBUG) { cerr<<"There are now "<queue.size()<<" messages queued for "<dst.toStringWithPort()<> 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.sin4.sin_port) { std::lock_guard l(mut); queue.push_back(msg); } else writen2(fd, msg.c_str(), msg.size()); } 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({ubxClass, ubxType, 0, 0, 0, 0, 0, 0}); if(port > 6) throw std::runtime_error("Port number out of range (>6)"); 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"); app.add_option("--port,-p", portName, "Device or file to read serial from")->required(); 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"); app.add_flag("--debug", doDEBUG, "Display debug information"); app.add_flag("--logfile", doLOGFILE, "Create logfile"); try { app.parse(argc, argv); } catch(const CLI::Error &e) { return app.exit(e); } g_baudval = getBaudrate(baudrate); if(!(doGPS || doGalileo || doGlonass || doBeidou)) { cerr<<"Enable at least one of --gps, --galileo, --glonass, --beidou"< msg; if(doReset) { 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; } 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(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); extern const char* g_gitHash; 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 (doDEBUG) { cerr<