Compare commits

...

3 Commits

Author SHA1 Message Date
onur 9ced0da0b2 Normalize zero-based firmware satellite indices
Ubuntu build / build-and-test (ubuntu-22.04) (push) Has been cancelled
Ubuntu build / build-and-test (ubuntu-24.04) (push) Has been cancelled
2026-07-24 12:06:09 +03:00
onur 73589204f5 Verify satellite propagation is timezone invariant 2026-07-24 11:58:24 +03:00
onur 6ffa76139c Show RINEX status time in local timezone 2026-07-24 11:50:48 +03:00
6 changed files with 65 additions and 18 deletions
+4 -3
View File
@@ -223,9 +223,10 @@ without logging out.
previous two UTC days if today's stream file is unavailable. Current and next-minute satellite
positions are propagated from the cached ephemeris, then smoothly interpolated at 10 FPS.
- The 3D Earth tab includes a `RINEX Guncelle` button for an immediate manual refresh. Its status
line shows the UTC time of the last successful download or cache load.
- Complete UART channel snapshots such as `tracked satellites=5 prns=[...]` replace the previous
connected-PRN set, so satellites that were lost no longer remain green.
line shows the system-local time of the last successful download or cache load.
- Complete UART channel snapshots such as `tracked satellites=5 prn_indices=[...]` replace the
previous connected-PRN set, so satellites that were lost no longer remain green. Firmware
channel indices are zero-based and are normalized for display (`0` is GPS PRN `G01`).
- RINEX text is parsed once per download and retained as compact ephemeris records. The static
globe is cached between camera changes, reducing CPU and allocation load during orbit animation.
- The globe uses an embedded Natural Earth coastline mask with terrain-like land colouring,
+22 -8
View File
@@ -234,10 +234,11 @@ QSet<QString> LocationParser::parseGpsPrns(const QString &line)
QSet<QString> result;
const auto addPrn = [&result](QString value, bool zeroBased = false) {
value = value.trimmed().toUpper();
if (value.startsWith(QLatin1Char('G'))) value.remove(0, 1);
const bool explicitGpsPrn = value.startsWith(QLatin1Char('G'));
if (explicitGpsPrn) value.remove(0, 1);
bool ok = false;
int number = value.toInt(&ok);
if (ok && zeroBased) ++number;
if (ok && zeroBased && !explicitGpsPrn) ++number;
if (ok && number >= 1 && number <= 63) {
result.insert(QStringLiteral("G%1").arg(number, 2, 10, QLatin1Char('0')));
}
@@ -249,18 +250,31 @@ QSet<QString> LocationParser::parseGpsPrns(const QString &line)
auto gpsMatches = gpsToken.globalMatch(line);
while (gpsMatches.hasNext()) addPrn(gpsMatches.next().captured(QStringLiteral("prn")));
static const QRegularExpression labeledList(
QStringLiteral(R"((?<label>\bPRNS?\b|\bSVS?\b|\bSVNS?\b|\bSVIDS?\b)\s*(?:=|:|->)?\s*[\[(]?\s*(?<list>(?:G?\d{1,2})(?:\s*[,;/| ]\s*G?\d{1,2})*)\s*[\])]?)"),
// Current firmware versions called their zero-based hardware channel values
// "prns" in complete tracking snapshots. Keep accepting those messages while
// newer firmware uses the unambiguous "prn_indices" label.
static const QRegularExpression legacyTrackingIndices(
QStringLiteral(R"(\btracked\s+satellites\s*=\s*\d+\s+prns?\s*=)"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression number(QStringLiteral(R"(G?(\d{1,2}))"),
const bool legacyZeroBasedPrns = legacyTrackingIndices.match(line).hasMatch();
static const QRegularExpression labeledList(
QStringLiteral(R"((?<label>\bPRN[_ -]?INDICES\b|\bPRNS?\b|\bSVS?\b|\bSVNS?\b|\bSVIDS?\b)\s*(?:=|:|->)?\s*[\[(]?\s*(?<list>(?:G?\d{1,2})(?:\s*[,;/| ]\s*G?\d{1,2})*)\s*[\])]?)"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression number(QStringLiteral(R"(G?\d{1,2})"),
QRegularExpression::CaseInsensitiveOption);
auto listMatches = labeledList.globalMatch(line);
while (listMatches.hasNext()) {
const QRegularExpressionMatch listMatch = listMatches.next();
const bool zeroBased = listMatch.captured(QStringLiteral("label")).startsWith(
QStringLiteral("SVN"), Qt::CaseInsensitive);
QString label = listMatch.captured(QStringLiteral("label")).toUpper();
label.remove(QLatin1Char('_'));
label.remove(QLatin1Char('-'));
label.remove(QLatin1Char(' '));
const bool zeroBased = label.startsWith(QStringLiteral("SVN"))
|| label == QStringLiteral("PRNINDICES")
|| (legacyZeroBasedPrns && label.startsWith(QStringLiteral("PRN")));
auto values = number.globalMatch(listMatch.captured(QStringLiteral("list")));
while (values.hasNext()) addPrn(values.next().captured(1), zeroBased);
while (values.hasNext()) addPrn(values.next().captured(0), zeroBased);
}
return result;
}
+1 -1
View File
@@ -520,7 +520,7 @@ void MainWindow::handleSerialLine(const QString &line)
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression trackingSnapshotRegex(
QStringLiteral(
R"(\btracked\s+satellites\s*=\s*\d+\s+(?:prns?|svns?)\s*=\s*\[[^\]]*\])"),
R"(\btracked\s+satellites\s*=\s*\d+\s+(?:prn[_ -]?indices|prns?|svns?)\s*=\s*\[[^\]]*\])"),
QRegularExpression::CaseInsensitiveOption);
const bool solveStarted = solveStartRegex.match(line.trimmed()).hasMatch();
const bool trackingSnapshot = trackingSnapshotRegex.match(line).hasMatch();
+6 -4
View File
@@ -158,10 +158,11 @@ void RinexNavigation::handleDownload(const QByteArray &compressed, const QDate &
m_ephemerides = std::move(ephemerides);
setLoading(false);
publishCurrentMotion();
emit statusChanged(QStringLiteral("%1 GPS uydusu · RINEX %2 · %3 UTC")
emit statusChanged(QStringLiteral("%1 GPS uydusu · RINEX %2 · %3")
.arg(positions.size())
.arg(date.toString(QStringLiteral("dd.MM.yyyy")))
.arg(now.time().toString(QStringLiteral("HH:mm:ss"))), false);
.arg(now.toLocalTime().toString(
QStringLiteral("HH:mm:ss t"))), false);
}
bool RinexNavigation::loadCache()
@@ -176,9 +177,10 @@ bool RinexNavigation::loadCache()
if (positions.isEmpty()) return false;
m_ephemerides = std::move(ephemerides);
publishCurrentMotion();
emit statusChanged(QStringLiteral("%1 GPS uydusu · onbellekteki RINEX · %2 UTC")
emit statusChanged(QStringLiteral("%1 GPS uydusu · onbellekteki RINEX · %2")
.arg(positions.size())
.arg(now.time().toString(QStringLiteral("HH:mm:ss"))), true);
.arg(now.toLocalTime().toString(
QStringLiteral("HH:mm:ss t"))), true);
return true;
}
+2 -2
View File
@@ -89,13 +89,13 @@ int main(int argc, char **argv)
assert(sideSplitter != nullptr);
assert(sideSplitter->orientation() == Qt::Vertical);
sendLine(window, QStringLiteral("[TRK] tracked satellites=2 prns=[3,8]"));
sendLine(window, QStringLiteral("[TRK] tracked satellites=2 prn_indices=[2,7]"));
assert(map != nullptr);
auto *globe = window.findChild<GlobeWidget *>();
assert(globe != nullptr);
assert(globe->observedPrns()
== QSet<QString>({QStringLiteral("G03"), QStringLiteral("G08")}));
sendLine(window, QStringLiteral("[TRK] tracked satellites=1 prns=[14]"));
sendLine(window, QStringLiteral("[TRK] tracked satellites=1 prn_indices=[13]"));
assert(globe->observedPrns() == QSet<QString>({QStringLiteral("G14")}));
assert(QMetaObject::invokeMethod(map, "referenceLocationSelected", Qt::DirectConnection,
+30
View File
@@ -186,6 +186,25 @@ int main(int argc, char **argv)
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G32")}));
}
{
const QSet<QString> prns = LocationParser::parseGpsPrns(
QStringLiteral("[TRK] tracked satellites=3 prn_indices=[0,7,31]"));
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08"),
QStringLiteral("G32")}));
}
{
const QSet<QString> prns = LocationParser::parseGpsPrns(
QStringLiteral("[TRK] tracked satellites=2 prns=[0,7]"));
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08")}));
}
{
const QSet<QString> prns = LocationParser::parseGpsPrns(
QStringLiteral("[TRK] tracked satellites=2 prn_indices=[0,G08]"));
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08")}));
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral(
"[OK] [NAV] DOP GDOP=7.0907891622 PDOP=5.6333862175 "
@@ -257,6 +276,17 @@ int main(int argc, char **argv)
assert(closeTo(satellites[0].x, semiMajorAxis * std::cos(node), 0.1));
assert(closeTo(satellites[0].y, semiMajorAxis * std::sin(node), 0.1));
assert(closeTo(satellites[0].z, 0.0, 0.1));
// A local wall-clock representation must not move the satellites.
// 11:48 at UTC+03 and 08:48 UTC represent the same physical instant.
const auto localTimeSatellites = RinexNavigation::parseGpsNavigation(
syntheticGpsNavigation(gpsLabel, gpsWeek, toe),
matchingUtc.toOffsetFromUtc(3 * 60 * 60), &error);
assert(error.isEmpty());
assert(localTimeSatellites.size() == satellites.size());
assert(closeTo(localTimeSatellites[0].x, satellites[0].x, 0.001));
assert(closeTo(localTimeSatellites[0].y, satellites[0].y, 0.001));
assert(closeTo(localTimeSatellites[0].z, satellites[0].z, 0.001));
}
const QString rinexPath = qEnvironmentVariable("LOCATION_PLOTTER_TEST_RINEX");