pull/14/head
bert hubert 2019-10-01 15:03:30 +02:00
parent 019c8b628d
commit 76070765c4
6 changed files with 257 additions and 49 deletions

View File

@ -49,7 +49,7 @@ input[type="checkbox"] {
</style>
<body>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
Last update: <span id="freshness"></span>. More information about this Galileo/GPS/BeiDou/Glonass open source monitor can be found <a href="https://github.com/ahupowerdns/galmon/blob/master/README.md#galmon">here</a> and <a href="https://ds9a.nl/articles/posts/gps-gnss-how-do-they-work/">here</a>. Live map <a href="geo">here!</a>. Contact <a href="https://ds9a.nl/">me</a> if you want access to the Grafana dashboard.<br/>
<center>
<hr/>
<input type="checkbox" id="GalE1" onclick="updateSats();"> <label for="GalE1">Galileo E1</label> &nbsp;&nbsp;
@ -70,28 +70,38 @@ input[type="checkbox"] {
Stale:<br/>
<table id="svsstale"></table>
<p>
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States, Tonga, Brazil, Singapore, Austria ans Uruguay.
This table shows live output from four Galileo/GPS/BeiDou/GLONASS receivers hosted in Nootdorp, The Netherlands and California, United States, Tonga, Brazil, Singapore, Austria, India and Uruguay.
It is very much a work in progress, and will not be available at all times. Extremely rough code is on
<a href="https://github.com/ahuPowerDNS/galmon">GitHub</a>.
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
<p>
Some technical detail behind this setup can be found in <a href="https://ds9a.nl/articles/posts/galileo-notes/">this post</a>.
</p>
<p>
For updates, follow <a href="https://twitter.com/GalileoSats">@GalileoSats</a> on Twitter, or join us on our IRC channel (chat) via the
<a href="https://webchat.oftc.net/?channels=galileo">web gateway</a>.
The meaning of the fields is as follows:
</p>
<p>
The meaning of the fields is explained in <a href="https://ds9a.nl/articles/posts/gps-gnss-how-do-they-work/">this document</a> and can be summarised as follows:
</p>
<table>
<tr valign="top"><td>sv</td><td>Satellite Vehicle, an identifier for a Galileo satellite. Not the actual name of the satellite, other satellites could take over this number in case of failures</td></tr>
<tr valign="top"><td>iod</td><td>Issue of Data. Satellites periodically get sent updates on their orbit &amp; other details, each update has a new IOD number. It is coincidence that all SVs currently receive the same IOD numbers, this is by no means guaranteed. Currently however, if an SV has a lower IOD, it has not received new updates recently.</td></tr>
<tr valign="top"><td>ephagem</td><td>Age of ephemeris in minutes. Denotes how old the current set of orbit data is. Could be very old if SV is out of sight (see below). An acceptable limit is 4 hours (240 minutes).</td></tr>
<tr valign="top"><td>latest-disco</td><td>"jump" of the orbit prediction at the latest ephemeris change. Centimeters are good.</td></tr>
<tr valign="top"><td>time-disco</td><td>"jump" of the atomic clock at the latest ephemeris change.</td></tr>
<tr valign="top"><td>sisa</td><td>Signal In Space Accuracy, how well the position of an SV is known.</td></tr>
<tr valign="top"><td>e1bhs, e1bdvs, e5bhs, e5bdvs</td><td>Health flags for E1 (common) and E5 (uncommon) frequencies.</td></tr>
<tr valign="top"><td>a0, a1</td><td>Offset of the Galileo Standard Time to UTC. a0 is (more or less) the offset in nanoseconds, a1 is a measure of the rate of change</td></tr>
<tr valign="top"><td>elev</td><td>Elevation of an SV over my horizon (90 is straight up), reported by receiver</td></tr>
<tr valign="top"><td>calc-elev</td><td>Elevation of an SV over or under my horizon (90 is straight up), calculated by this website</td></tr>
<tr valign="top"><td>db</td><td>A measure of signal to noise ratio (in unknown units, 40 is good)</td></tr>
<tr valign="top"><td>lastseens</td><td>Number of seconds since we've last received from this SV. A satellite can be out of sight for a long time</td></tr>
<tr valign="top"><td>health</td><td>If a satellite considers itself healthy.</td></tr>
<tr valign="top"><td>delta-UTC</td><td>Offset of the GNSS Time to UTC, plus trend</td></tr>
<tr valign="top"><td>delta-GPS</td><td>Offset of the GNSS Time to GPS, plus trend</td></tr>
<tr valign="top"><td>alma-dist</td><td>Distance between precise satellite position and almanac summary position</td></tr>
<tr valign="top"><td>tle-dist</td><td>Distance between precise satellite position and TLE position</td></tr>
<tr valign="top"><td>best-tle</td><td>From TLE database, closest satellite to reported position</td></tr>
<tr valign="top"><td>prres</td><td>Pseudorange residual: measure of how far away the satellite <b>appears</b> to be from where it should be, according to a receiver. Meters.</td></tr>
<tr valign="top"><td>delta-Hz</td><td>Difference between calculated (expected) Doppler shift and measured Doppler shift. Measure of orbit correctness. Hz.</td></tr>
<tr valign="top"><td>elev</td><td>Elevation of an SV over or under my horizon (90 is straight up), calculated by this website</td></tr>
<tr valign="top"><td>db</td><td>A measure of signal to noise ratio (40+ is good)</td></tr>
<tr valign="top"><td>lastseens</td><td>Time since we've last received from this SV.</td></tr>
</table>
</p>
<p>

View File

@ -4,6 +4,7 @@
#include <arpa/inet.h>
#include "fmt/format.h"
#include "fmt/printf.h"
#include "CLI/CLI.hpp"
#include <fstream>
#include <map>
#include <bitset>
@ -22,12 +23,33 @@
#include "galileo.hh"
#include "navmon.hh"
#include "tle.hh"
#include "sp3.hh"
#include <unistd.h>
using namespace std;
Point g_ourpos;
vector<SP3Entry> 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;
@ -82,9 +104,55 @@ void doOrbitDump(int gnss, int sv, int wn, const T& oldEph, const T& newEph, int
}
struct SVFilter
{
struct SatID
{
int gnss, sv, sigid;
bool operator<(const SatID& rhs) const
{
return std::tie(gnss, sv, sigid) < std::tie(rhs.gnss, rhs.sv, rhs.sigid);
}
};
void addFilter(string_view str)
{
SatID satid{0,0,-1};
satid.gnss = atoi(&str[0]);
auto pos= str.find(',');
if( pos != string::npos)
satid.sv = atoi(&str[pos+1]);
pos = str.find(',', pos+1);
if(pos != string::npos)
satid.sigid = atoi(&str[pos+1]);
d_filter.insert(satid);
}
bool check(int gnss, int sv, int sigid=-1)
{
if(d_filter.empty())
return true;
if(d_filter.count({gnss,0,-1})) // gnss match
return true;
if(d_filter.count({gnss,sv,-1})) // gnss, sv match
return true;
if(d_filter.count({gnss,sv,sigid})) // gnss, sv match, sigid
return true;
return false;
}
set<SatID> d_filter;
};
int main(int argc, char** argv)
try
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
CLI::App app("navdump");
TLERepo tles;
// tles.parseFile("active.txt");
tles.parseFile("galileo.txt");
@ -92,10 +160,26 @@ try
tles.parseFile("gps-ops.txt");
tles.parseFile("beidou.txt");
bool skipGPS{false};
bool skipBeidou{false};
bool skipGalileo{false};
bool skipGlonass{false};
readSP3s("all.sp3");
if(!g_sp3s.empty()) {
// sort(g_sp3s.begin(), g_sp3s.end(), [](const auto& a, const auto&b) { return a.t < b.t; });
cout<<"Have "<<g_sp3s.size()<<" sp3 entries"; //, from "<<humanTime(g_sp3s.begin()->t) <<" to "<< humanTime(g_sp3s.rbegin()->t)<<endl;
}
vector<string> svpairs;
bool doReceptionData{false};
bool doRFData{false};
bool doObserverPosition{false};
app.add_option("--svs", svpairs, "Listen to specified svs. '0' = gps, '2' = Galileo, '2,1' is E01");
try {
app.parse(argc, argv);
} catch(const CLI::Error &e) {
return app.exit(e);
}
SVFilter svfilter;
for(const auto& svp : svpairs) {
svfilter.addFilter(svp);
}
ofstream almanac("almanac.txt");
ofstream iodstream("iodstream.csv");
@ -103,6 +187,11 @@ try
ofstream csv("delta.csv");
csv <<"timestamp gnssid sv tow tle_distance alma_distance utc_dist x y z vx vy vz rad inclination e iod"<<endl;
ofstream sp3csv;
sp3csv.open ("sp3.csv", std::ofstream::out | std::ofstream::app);
sp3csv<<"timestamp gnssid sv ephAge sp3X sp3Y sp3Z ephX ephY ephZ sp3Clock ephClock distance clockDelta"<<endl;
for(;;) {
char bert[4];
@ -130,27 +219,51 @@ try
// if(nmm.type() == NavMonMessage::ReceptionDataType)
// continue;
auto etstamp = [&nmm]() {
cout<<humanTime(nmm.localutcseconds(), nmm.localutcnanoseconds())<<" src "<< nmm.sourceid()<<" ";
uint32_t imptow = ((uint32_t)round(nmm.localutcseconds() + nmm.localutcnanoseconds()/1000000000.0) - 935280000 + 18) % (7*86400);
if(!(imptow % 2))
imptow--;
cout<< "imptow "<<imptow-2<<" ";
};
cout<<humanTime(nmm.localutcseconds())<<" "<<nmm.localutcnanoseconds()<<" ";
cout<<"src "<<nmm.sourceid()<< " ";
if(nmm.type() == NavMonMessage::ReceptionDataType) {
cout<<"receptiondata for "<<nmm.rd().gnssid()<<","<<nmm.rd().gnsssv()<<","<< (nmm.rd().has_sigid() ? nmm.rd().sigid() : 0) <<" db "<<nmm.rd().db()<<" ele "<<nmm.rd().el() <<" azi "<<nmm.rd().azi()<<" prRes "<<nmm.rd().prres() << endl;
if(doReceptionData) {
etstamp();
cout<<"receptiondata for "<<nmm.rd().gnssid()<<","<<nmm.rd().gnsssv()<<","<< (nmm.rd().has_sigid() ? nmm.rd().sigid() : 0) <<" db "<<nmm.rd().db()<<" ele "<<nmm.rd().el() <<" azi "<<nmm.rd().azi()<<" prRes "<<nmm.rd().prres() << endl;
}
}
else if(nmm.type() == NavMonMessage::GalileoInavType) {
if(skipGalileo)
continue;
basic_string<uint8_t> inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
static map<int, GalileoMessage> gms;
static map<pair<int, int>, GalileoMessage> gmwtypes;
static map<int, GalileoMessage> 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();
gmwtypes[{nmm.gi().gnsssv(), wtype}] = gm;
static map<int,GalileoMessage> oldEph;
cout << "gal inav for "<<nmm.gi().gnssid()<<","<<nmm.gi().gnsssv()<<","<<nmm.gi().sigid()<<" tow "<< nmm.gi().gnsstow()<<" wtype "<< wtype<<" ";
cout << "gal inav wtype "<<wtype<<" for "<<nmm.gi().gnssid()<<","<<nmm.gi().gnsssv()<<","<<nmm.gi().sigid()<<" pbwn "<<nmm.gi().gnsswn()<<" pbtow "<< nmm.gi().gnsstow();
static uint32_t tow;
if(wtype == 4) {
// 2^-34 2^-46
@ -168,6 +281,34 @@ try
gmwtypes[{sv,2}].iodnav == gmwtypes[{sv,3}].iodnav &&
gmwtypes[{sv,3}].iodnav == gmwtypes[{sv,4}].iodnav) {
cout <<" have complete ephemeris at " << gm.iodnav;
int start = utcFromGST(gm.wn, gm.tow);
SP3Entry e{2, sv, start};
auto bestSP3 = lower_bound(g_sp3s.begin(), g_sp3s.end(), e, sp3Order);
if(bestSP3 != g_sp3s.end() && bestSP3->gnss == e.gnss && bestSP3->sv == sv) {
static set<pair<int,int>> haveSeen;
if(!haveSeen.count({sv, bestSP3->t})) {
haveSeen.insert({sv, bestSP3->t});
Point newPoint;
getCoordinates(gm.tow + (bestSP3->t - start), gm, &newPoint);
Point sp3Point(bestSP3->x, bestSP3->y, bestSP3->z);
Vector dist(newPoint, sp3Point);
cout<<"\nsp3 "<<(bestSP3->t - start)<<" E"<<sv<<" "<<humanTime(bestSP3->t)<<" (" << newPoint.x/1000.0 <<", "<<newPoint.y/1000.0<<", "<<newPoint.z/1000.0<< ") (" <<
(bestSP3->x/1000.0) <<", " << (bestSP3->y/1000.0) <<", " << (bestSP3->z/1000.0) << ") "<<bestSP3->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 <<std::fixed<< bestSP3->t << " 2 "<< sv <<" " << ephAge(gm.tow+(bestSP3->t - start), gm.getT0e()) <<" "<<bestSP3->x<<" " << bestSP3->y<<" " <<bestSP3->z <<" " << newPoint.x<<" " <<newPoint.y <<" " <<newPoint.z << " " <<bestSP3->clockBias <<" ";
sp3csv << gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first<<" " << dist.length() <<" ";
sp3csv << (bestSP3->clockBias - gm.getAtomicOffset(gm.tow + (bestSP3->t-start)).first) << endl;
}
}
if(!oldEph[sv].sqrtA)
oldEph[sv] = gm;
else if(oldEph[sv].iodnav != gm.iodnav) {
@ -181,6 +322,8 @@ try
auto newAtomic = gm.getAtomicOffset(gm.tow);
cout<<" clock-jump "<<oldAtomic.first - newAtomic.first<<" ns ";
doOrbitDump(2, sv, gm.wn, oldEph[sv], gm, gm.tow - 3*3600, gm.tow + 3*3600);
oldEph[sv]=gm;
}
}
@ -190,6 +333,8 @@ try
}
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);
@ -198,8 +343,19 @@ try
else
iodstream<<"\n";
}
if(wtype == 0 || wtype == 5 || wtype == 6)
tow = gm.tow;
if(wtype == 0 || wtype == 5 || wtype == 6) {
if(wtype != 0 || gm.sparetime == 2) {
cout << " tow "<< gm.tow;
tow = gm.tow;
}
}
if(wtype == 5) {
cout <<" e1bhs "<< (int) gm.e1bhs << " e5bhs "<< (int) gm.e5bhs << " e1bdvs "<< (int) gm.e1bdvs << " e5bdvs "<< (int) gm.e5bdvs<< " wn "<<gm.wn <<" ai0 "<< gm.ai0 <<" ai1 " << gm.ai1 <<" ai2 " <<gm.ai2 << " BGDE1E5a " << gm.BGDE1E5a <<" BGDE1E5b " <<gm.BGDE1E5b;
// wn = gm.wn;
}
if(wtype == 6) {
cout<<" a0 " << gm.a0 <<" a1 " << gm.a1 <<" t0t "<<gm.t0t << " dtLS "<<(int)gm.dtLS;
}
// if(wtype < 7)
// gm = GalileoMessage{};
@ -242,12 +398,13 @@ try
cout<<endl;
}
else if(nmm.type() == NavMonMessage::GPSInavType) {
if(skipGPS) {
cout<<endl;
continue;
}
int sv = nmm.gpsi().gnsssv();
if(!svfilter.check(0, sv))
continue;
etstamp();
auto cond = getCondensedGPSMessage(std::basic_string<uint8_t>((uint8_t*)nmm.gpsi().contents().c_str(), nmm.gpsi().contents().size()));
struct GPSState gs;
static map<int, GPSState> eph;
@ -329,12 +486,14 @@ try
cout<<"\n";
}
else if(nmm.type() == NavMonMessage::BeidouInavTypeD1) {
if(skipBeidou) {
cout<<endl;
continue;
}
int sv = nmm.bid1().gnsssv();
if(!svfilter.check(3, sv))
continue;
etstamp();
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid1().contents().c_str(), nmm.bid1().contents().size()));
uint8_t pageno;
static map<int, BeidouMessage> bms;
@ -403,6 +562,11 @@ try
}
else if(nmm.type() == NavMonMessage::BeidouInavTypeD2) {
int sv = nmm.bid2().gnsssv();
if(!svfilter.check(3, sv))
continue;
etstamp();
auto cond = getCondensedBeidouMessage(std::basic_string<uint8_t>((uint8_t*)nmm.bid2().contents().c_str(), nmm.bid2().contents().size()));
BeidouMessage bm;
uint8_t pageno;
@ -412,11 +576,9 @@ try
}
else if(nmm.type() == NavMonMessage::GlonassInavType) {
if(skipGlonass) {
cout<<endl;
if(!svfilter.check(6, nmm.gloi().gnsssv()))
continue;
}
etstamp();
static map<int, GlonassMessage> gms;
auto& gm = gms[nmm.gloi().gnsssv()];
@ -456,16 +618,23 @@ try
cout<<endl;
}
else if(nmm.type() == NavMonMessage::ObserverPositionType) {
if(!doObserverPosition)
continue;
etstamp();
auto lonlat = getLongLat(nmm.op().x(), nmm.op().y(), nmm.op().z());
cout<<std::fixed<<"ECEF "<<nmm.op().x()<<", "<<nmm.op().y()<<", "<<nmm.op().z()<< " lon "<< 180*lonlat.first/M_PI << " lat "<<
180*lonlat.second/M_PI<< " acc "<<nmm.op().acc()<<" m "<<endl;
g_ourpos = Point(nmm.op().x(), nmm.op().y(), nmm.op().z());
}
else if(nmm.type() == NavMonMessage::RFDataType) {
if(!doRFData)
continue;
etstamp();
cout<<"RFdata for "<<nmm.rfd().gnssid()<<","<<nmm.rfd().gnsssv()<<","<<(nmm.rfd().has_sigid() ? nmm.rfd().sigid() : 0) <<endl;
}
else {
etstamp();
cout<<"Unknown type "<< (int)nmm.type()<<endl;
}
}

View File

@ -4,6 +4,9 @@
#include <string>
#include <string.h>
#include <stdexcept>
#include "fmt/format.h"
#include "fmt/printf.h"
using namespace std;
@ -32,6 +35,18 @@ std::string humanTime(time_t t)
gmtime_r(&t, &tm);
char buffer[80];
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", &tm);
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S %z", &tm);
return buffer;
}
std::string humanTime(time_t t, uint32_t nanoseconds)
{
struct tm tm={0};
gmtime_r(&t, &tm);
char buffer[80];
std::string fmt = "%a, %d %b %Y %H:%M:"+fmt::sprintf("%06.04f", tm.tm_sec + nanoseconds/1000000000.0) +" %z";
strftime(buffer, sizeof(buffer), fmt.c_str(), &tm);
return buffer;
}

View File

@ -3,7 +3,19 @@
#include <unistd.h>
#include <time.h>
#include <string>
#include <tuple>
struct EofException{};
size_t readn2(int fd, void* buffer, size_t len);
std::string humanTime(time_t t);
std::string humanTime(time_t t, uint32_t nanoseconds);
struct SatID
{
uint32_t gnss{255}; // these could all be 'int16_t' but leads to howling numbers of warnings with protobuf
uint32_t sv{0};
uint32_t sigid{0};
bool operator<(const SatID& rhs) const
{
return std::tie(gnss, sv, sigid) < std::tie(rhs.gnss, rhs.sv, rhs.sigid);
}
};

View File

@ -408,16 +408,6 @@ void SVStat::addGalileoWord(std::basic_string_view<uint8_t> page)
}
}
struct SatID
{
uint32_t gnss{255}; // these could all be 'int16_t' but leads to howling numbers of warnings with protobuf
uint32_t sv{0};
uint32_t sigid{0};
bool operator<(const SatID& rhs) const
{
return tie(gnss, sv, sigid) < tie(rhs.gnss, rhs.sv, rhs.sigid);
}
};
typedef std::map<SatID, SVStat> svstats_t;
svstats_t g_svstats;
@ -667,6 +657,10 @@ std::optional<double> getHzCorrection(time_t now, int src, unsigned int gnssid,
std::string humanBhs(int bhs)
{
static vector<string> options{"ok", "out of service", "will be out of service", "test"};
if(bhs >= options.size()) {
cerr<<"Asked for humanBHS "<<bhs<<endl;
return "??";
}
return options.at(bhs);
}
int main(int argc, char** argv)
@ -1131,6 +1125,8 @@ try
h2s.addHandler("/svs.json", [](auto handler, auto 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"));
for(const auto& s: svstats) {
nlohmann::json item = nlohmann::json::object();

View File

@ -900,6 +900,12 @@ int main(int argc, char** argv)
else if(wtype==3) {
msgTOW = curCycleTOW + 23;
}
else { // dummy
if(id.second != 20) // known broken XXX
cerr<<"galileo E"<<id.second<<" what kind of wtype is this: "<<wtype<<endl;
continue;
}
}
}
NavMonMessage nmm;