touch: GPS page says what it is actually doing, not just "acquiring..."

The GPS settings page showed "GPS: acquiring..." and nothing else until a fix
landed. That single string covers two completely different situations: a receiver
happily tracking satellites that needs another minute, and a receiver that is not
talking at all. They look identical to the user, so there is no way to tell a
working device from a broken one by looking at it. That ambiguity is what turned a
healthy T-Display P4 GPS into a long investigation.

While acquiring, the page now shows:
  - how long it has been searching, so a stuck receiver is obvious from elapsed
    time alone rather than from staring at an unchanging word;
  - whether the receiver has decoded satellite TIME. That is the useful tell: a
    GPS decodes time/date off the air well before it has enough satellites to
    solve a position, so valid time with no fix is positive proof the antenna and
    the UART are fine and it is genuinely mid-acquisition. The wording then points
    at sky view, which is the actual remedy;
  - and when there is no satellite data at all, it says so plainly instead of
    implying progress.

On a fix it now also shows altitude alongside the satellite count and position,
and the count is only printed when non-zero (it is satellites USED, so a bare
"0 sats" read as a fault).

Not included, and worth being clear about: satellites IN VIEW and per-satellite
SNR are NOT reachable from this repo. The core's MicroNMEALocationProvider keeps
its MicroNMEA object private and LocationProvider exposes only fix / sats-used /
lat / lon / alt / timestamp, so a full sky view needs a monorepo core change plus
a core-* tag bump. The boot probe added in bdc0c7f prints that data over serial in
the meantime.

Builds clean on V4, T-Deck and the P4; flashed and boot-verified on the P4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-07-31 09:53:52 +02:00
co-authored by Claude Opus 4.8
parent bdc0c7ffb6
commit 5c9f9cffef
2 changed files with 68 additions and 9 deletions
+61 -9
View File
@@ -2237,18 +2237,51 @@ static bool settingsModalIsOpen() { return g_set_modal.root != nullptr; }
// Compact one-line GPS status for the Device settings panel + control center.
// TR("GPS: off") / "GPS: searching · N sats" / "GPS: fix · N sats <lat>, <lon>".
// Stamped when acquisition starts so the page can show how long it has been searching; a stuck
// receiver is then obvious from the elapsed time alone. Cleared on fix and when GPS is off.
static uint32_t s_gps_acq_since = 0;
static const char* gpsStatusStr() {
static char s[72];
if (!g_lv.task || !g_lv.task->getGPSState()) { snprintf(s, sizeof s, TR("GPS: off")); return s; }
// satellitesCount() is satellites USED IN THE FIX (0 until a lock), so during
// cold acquisition it stays 0 — show "acquiring…" rather than a misleading
// "0 sats". On lock, show the sat count + the (auto-populated) coordinates.
if (!g_lv.task->getGpsFix()) { snprintf(s, sizeof s, TR("GPS: acquiring...")); return s; }
static char s[200];
if (!g_lv.task || !g_lv.task->getGPSState()) {
s_gps_acq_since = 0;
snprintf(s, sizeof s, TR("GPS: off"));
return s;
}
// satellitesCount() is satellites USED IN THE FIX, so it stays 0 through a cold acquisition and
// a bare "0 sats" would read as broken. The old line therefore said only "acquiring...", which
// is just as unhelpful: it cannot distinguish a receiver happily tracking satellites that needs
// another minute from one that is not talking at all. Both looked identical to the user, and
// that ambiguity is exactly what cost a long P4 investigation.
//
// The tell is TIME. A receiver decodes satellite time/date well before it has enough satellites
// to solve a position, so "no fix but valid GPS time" is positive proof that the antenna and the
// link are fine and it is genuinely mid-acquisition.
if (!g_lv.task->getGpsFix()) {
const uint32_t now = millis();
if (!s_gps_acq_since) s_gps_acq_since = now;
const uint32_t secs = (now - s_gps_acq_since) / 1000u;
const bool has_time = g_lv.task->getGpsTime() != 0;
int n = snprintf(s, sizeof s, TR("GPS: searching"));
if (n < (int)sizeof s) {
if (secs < 600) n += snprintf(s + n, sizeof s - n, " %lum%02lus", (unsigned long)(secs / 60), (unsigned long)(secs % 60));
else n += snprintf(s + n, sizeof s - n, " %lum", (unsigned long)(secs / 60));
}
if (n < (int)sizeof s) {
snprintf(s + n, sizeof s - n, "\n%s", has_time
? TR("Receiver OK, satellite time decoded. Waiting for enough satellites - needs a clear view of the sky.")
: TR("No satellite data yet. Outdoors with a clear view of the sky it should appear within a few minutes."));
}
return s;
}
s_gps_acq_since = 0;
const int sats = g_lv.task->getGpsSats();
const int alt = g_lv.task->getGpsAltitude();
int n = snprintf(s, sizeof s, TR("GPS: fix"));
if (sats >= 0 && n < (int)sizeof s) n += snprintf(s + n, sizeof s - n, " · %d sats", sats);
if (sats > 0 && n < (int)sizeof s) n += snprintf(s + n, sizeof s - n, " · %d sats", sats);
if (alt && n < (int)sizeof s) n += snprintf(s + n, sizeof s - n, " · %d m", alt);
if (n < (int)sizeof s)
snprintf(s + n, sizeof s - n, " %.5f, %.5f",
snprintf(s + n, sizeof s - n, "\n%.5f, %.5f",
g_lv.task->getNodeLat(), g_lv.task->getNodeLon());
return s;
}
@@ -11498,7 +11531,8 @@ static void buildDeviceSettings(int sec) {
lv_obj_set_style_text_font(g_set_modal.gps_status, &g_font_12, LV_PART_MAIN);
lv_obj_set_pos(g_set_modal.gps_status, 4, y);
lv_label_set_text(g_set_modal.gps_status, gpsStatusStr());
y += SC(22);
lv_obj_update_layout(g_set_modal.gps_status);
y += LV_MAX(SC(22), lv_obj_get_height(g_set_modal.gps_status) + SC(6)); // status is 2 lines now
// (Expansion Kit moved to the Sensors page — see the DSEC_SENSORS block below.)
@@ -45223,6 +45257,24 @@ int UITask::getGpsSats() {
return lp ? (int)lp->satellitesCount() : -1;
}
uint32_t UITask::getGpsTime() {
if (!_sensors) return 0;
LocationProvider* lp = _sensors->getLocationProvider();
if (!lp) return 0;
const long t = lp->getTimestamp();
// The provider builds this from the NMEA date/time fields, which read as year 0 (epoch far in
// the past) until the receiver has actually decoded them off the air. Anything plausibly recent
// means real satellite time.
return (t > 1700000000L) ? (uint32_t)t : 0;
}
int UITask::getGpsAltitude() {
if (!_sensors) return 0;
LocationProvider* lp = _sensors->getLocationProvider();
if (!lp || !lp->isValid()) return 0;
return (int)(lp->getAltitude() / 1000); // provider reports millimetres
}
// Keep the node's advertised location synced to GPS once we have a fix, and
// persist it occasionally so a fix survives reboot ("had a fix at least once").
// Called every UITask::loop(). No-op on boards without GPS (provider == NULL).
+7
View File
@@ -459,6 +459,13 @@ public:
bool getGpsFix();
/** Satellites currently in view, or -1 if unknown / no GPS hardware. */
int getGpsSats();
/** UTC epoch the GPS itself has decoded, or 0 if it has none yet. A receiver decodes TIME from
* the satellite stream well before it can solve a POSITION, so a valid time with no fix is
* positive proof the module is alive and tracking, not dead. That is the distinction the GPS
* page could not previously show, and it is what "acquiring..." was hiding. */
uint32_t getGpsTime();
/** Altitude in metres from the last fix (0 when there is no fix). */
int getGpsAltitude();
/** True once a valid fix has been seen this session. */
bool getGpsHadFix() const { return _gps_had_fix; }
double getNodeLat() const { return _sensors ? _sensors->node_lat : 0.0; }