Files
wadamesh/test/test_gps_baud_probe.cpp
Kaj SchittecatandClaude Opus 5.5 966190e9c7 touch: the GPS start is watched for as long as the receiver runs (#778)
A receiver still finishing an earlier stop goes on sending for a moment after
the start and then falls silent. One good sentence ended the repeats, so it
stayed silent until the GPS was next restarted. The check now keeps running
and starts it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 17:02:44 +01:00

279 lines
10 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
//
// Host test for GPS Auto speed (32533c45, CAP_GPS_BAUD_FIND on the T-Deck and the M9):
// the real src/helpers/WadaNmeaLocationProvider.h, with its UART probe, against a
// simulated receiver that talks NMEA at one speed (noise at every other speed), and
// the real speed rules of touchPrefsGetGpsBaud / touchPrefsSetGpsBaudFound
// (src/helpers/esp32/GpsBaudPrefs.h) over a fake NVS that counts writes.
//
// c++ -std=c++17 -Wall -Wextra -DESP32 -I src -I test/gps_probe_fakes test/test_gps_baud_probe.cpp \
// -o /tmp/test_gps_baud_probe && /tmp/test_gps_baud_probe
#include <assert.h>
#include <stdio.h>
#include "helpers/WadaNmeaLocationProvider.h"
#include "helpers/esp32/GpsBaudPrefs.h"
uint32_t g_test_ms = 0;
// ---- the prefs the provider calls, over a fake NVS (TouchPrefsStore.cpp's wrappers) ----
static struct {
uint32_t chosen = 0; // s_cfg.gps_baud (settings blob)
bool have_found = false; // isKey("gps_bd_fd")
uint32_t found = 0;
int found_writes = 0; // prefsPutUInt("gps_bd_fd")
int chosen_writes = 0; // touchPrefsSetGpsBaud (cfgFlush)
} nvs;
uint32_t touchPrefsGetGpsBaud(uint32_t fallback) {
if (nvs.chosen != 0) return nvs.chosen;
return GpsBaudPrefs::effective(0, nvs.have_found ? nvs.found : 0, fallback);
}
bool touchPrefsGpsBaudChosen() { return nvs.chosen != 0; }
void touchPrefsSetGpsBaudFound(uint32_t baud) {
if (!GpsBaudPrefs::sane(baud)) return;
if (!GpsBaudPrefs::foundNeedsWrite(baud, nvs.have_found, nvs.have_found ? nvs.found : 0)) return;
nvs.have_found = true; nvs.found = baud; ++nvs.found_writes;
}
bool touchPrefsSetGpsBaud(uint32_t baud) { nvs.chosen = baud; ++nvs.chosen_writes; return true; }
static void resetNvs() { nvs = {}; nvs.chosen = 0; }
// A boot: the core opens Serial1 at touchPrefsGetGpsBaud(GPS_BAUD_RATE) (initBasicGPS),
// then the provider begins. The receiver keeps talking at `true_baud`.
struct Board {
HardwareSerial uart;
mesh::RTCClock clock;
WadaNmeaLocationProvider* gps = nullptr;
uint32_t default_baud;
Board(uint32_t true_baud, uint32_t def, bool keep_choice = true) : default_baud(def) {
uart.true_baud = true_baud;
uart.begin(touchPrefsGetGpsBaud(def));
gps = new WadaNmeaLocationProvider(uart, &clock, -1, -1, 18, 17, def, keep_choice);
gps->begin();
}
~Board() { delete gps; }
// Run the loop task for `ms`, a pass every 10 ms. Returns when the UART has carried
// a valid sentence at the receiver's speed and stayed there, or at the end.
void run(uint32_t ms) {
const uint32_t end = g_test_ms + ms;
while ((int32_t)(g_test_ms - end) < 0) { gps->loop(); g_test_ms += 10; }
}
};
int main() {
g_test_ms = 100000;
// ---- T-Deck (default 38400), a 9600 receiver, nothing picked or found ----
{
resetNvs();
Board b(9600, 38400);
assert(b.uart.baud == 38400); // the first start opens at the default
b.run(60000);
assert(b.uart.baud == 9600); // the probe found it...
assert(b.uart.begun_at.size() == 2 && b.uart.begun_at[1] == 9600); // ...on the first try after 38400
assert(nvs.have_found && nvs.found == 9600 && nvs.found_writes == 1); // stored once
assert(nvs.chosen == 0 && nvs.chosen_writes == 0); // never as the user's choice
const int begins = b.uart.begins;
b.run(120000); // two more minutes of valid NMEA
assert(b.uart.begins == begins && nvs.found_writes == 1); // locked: no reopen, no write
// the GPS saver rests it and wakes it: still no write, no probing
b.gps->stop(); b.run(5000); b.gps->begin(); b.run(30000);
assert(b.uart.baud == 9600 && nvs.found_writes == 1);
assert(b.uart.begins == begins);
}
// ---- the next start opens at the found 9600 and locks at once, writing nothing ----
{
Board b(9600, 38400);
assert(b.uart.baud == 9600);
b.run(60000);
assert(b.uart.begins == 1 && nvs.found_writes == 1);
}
// ---- a 38400 receiver, M9-style default 115200: found, stored once ----
{
resetNvs();
Board b(38400, 115200);
b.run(90000);
assert(b.uart.baud == 38400);
assert(nvs.found == 38400 && nvs.found_writes == 1);
// probe order after the default: 9600, 19200, 38400, at 9 s then every 5 s
assert(b.uart.begun_at.size() == 4);
assert(b.uart.begun_at[1] == 9600 && b.uart.begun_at[2] == 19200 && b.uart.begun_at[3] == 38400);
}
// ---- a 38400 receiver on the T-Deck default 38400: locks, nothing stored ----
{
resetNvs();
Board b(38400, 38400);
b.run(60000);
assert(b.uart.begins == 1 && b.uart.baud == 38400);
assert(nvs.found_writes == 0 && !nvs.have_found);
}
// ---- a picked speed is used as it is: no probing, nothing stored ----
{
resetNvs();
nvs.chosen = 115200; // the user picked 115200; the receiver talks 9600
Board b(9600, 38400);
assert(b.uart.baud == 115200);
b.run(120000);
assert(b.uart.begins == 1 && b.uart.baud == 115200);
assert(nvs.found_writes == 0 && nvs.chosen == 115200 && nvs.chosen_writes == 0);
}
// ---- ...and a found speed from before does not override the pick ----
{
resetNvs();
nvs.have_found = true; nvs.found = 9600; nvs.chosen = 57600;
Board b(9600, 38400);
assert(b.uart.baud == 57600);
b.run(60000);
assert(b.uart.begins == 1 && nvs.found == 9600 && nvs.found_writes == 0);
}
// ---- a swapped receiver: found 9600 stored, the new one talks 38400 ----
{
resetNvs();
nvs.have_found = true; nvs.found = 9600;
Board b(38400, 38400);
assert(b.uart.baud == 9600); // opens at the old found speed
b.run(60000);
assert(b.uart.baud == 38400 && nvs.found == 38400 && nvs.found_writes == 1);
}
// ---- no receiver at all: the probe keeps cycling, never stores anything ----
{
resetNvs();
Board b(0, 38400);
b.run(10 * 60000);
assert(nvs.found_writes == 0 && !nvs.have_found);
assert(b.uart.begins > 50); // every 5 s after the first 9 s
for (uint32_t s : b.uart.begun_at)
assert(s == 9600 || s == 19200 || s == 38400 || s == 57600 || s == 115200);
}
// ---- the GPS saver stops it mid-probe (receiver still silent), then it talks ----
// begin() takes the stored speed as the UART's without reopening it, so the probe
// skips that speed on its first pass; it must still find it on the next one.
{
resetNvs();
Board b(0, 38400); // asleep: nothing on the line
b.run(30000); // the probe has moved the UART off 38400
assert(b.uart.baud != 38400);
b.gps->stop(); b.run(2000);
b.uart.true_baud = 38400; // awake, talking at the default
b.gps->begin();
b.run(120000);
assert(b.uart.baud == 38400);
assert(nvs.found_writes == 0); // the default needs no stored speed
}
// ---- noise only (a receiver at a speed not in the list): nothing locks on noise ----
{
resetNvs();
Board b(4800, 38400);
b.run(10 * 60000);
assert(nvs.found_writes == 0);
}
// ---- CAP_GPS_BAUD_AUTO boards (keep_choice false): the found speed becomes the setting ----
{
resetNvs();
Board b(9600, 38400, false);
b.run(60000);
assert(b.uart.baud == 9600 && nvs.chosen == 9600 && nvs.chosen_writes == 1);
assert(nvs.found_writes == 0);
}
// ---- #778: a GNSS start lost right after the stop: the receiver must not stay silent ----
{
resetNvs();
Board b(38400, 38400);
b.run(5000);
b.gps->stop();
assert(b.uart.stopped);
b.uart.drop_starts = 2; // this start and the first repeat are lost
b.gps->begin();
assert(b.uart.stopped);
const uint32_t n = b.gps->validSentences();
b.run(10000);
assert(!b.uart.stopped);
assert(b.gps->validSentences() > n);
assert(b.gps->msSinceValidSentence() < 2000);
// ...and with sentences flowing the repeats stop.
const int heard = b.uart.starts_heard;
b.run(120000);
assert(b.uart.starts_heard == heard);
}
// ---- a receiver that sends on for a moment after the start, then stops: still recovered ----
{
resetNvs();
Board b(38400, 38400);
b.run(5000);
b.uart.stopped = true; // the earlier stop lands after sentences flowed
const uint32_t n = b.gps->validSentences();
b.run(40000);
assert(!b.uart.stopped);
assert(b.gps->validSentences() > n);
}
// ---- a start that goes out with stale sentences still in the UART is still repeated ----
{
resetNvs();
Board b(38400, 38400);
b.run(5000);
b.gps->stop();
b.uart.drop_starts = 1;
b.run(3); // the tail of what it sent, unread
b.gps->begin();
b.run(5000);
assert(!b.uart.stopped);
}
// ---- a receiver lost for longer: the slow repeats still bring it back ----
{
resetNvs();
nvs.chosen = 38400; // no probe: only the repeats send a start
Board b(38400, 38400);
b.run(5000);
b.gps->stop();
b.uart.drop_starts = 7;
b.gps->begin();
b.run(30000);
assert(b.uart.stopped);
b.run(70000);
assert(!b.uart.stopped);
}
// ---- Auto, stopped at a speed we have not tried yet: the start goes out on each candidate ----
{
resetNvs();
Board b(9600, 38400);
b.uart.stopped = true;
b.run(60000);
assert(!b.uart.stopped && b.uart.baud == 9600);
assert(nvs.found == 9600 && nvs.found_writes == 1);
}
// ---- a board with no receiver gets no reopen from the repeats ----
{
resetNvs();
nvs.chosen = 38400;
Board b(0, 38400);
b.run(5 * 60000);
assert(b.uart.begins == 1);
assert(b.gps->validSentences() == 0 && b.gps->msSinceValidSentence() == UINT32_MAX);
}
// ---- the rules themselves ----
assert(GpsBaudPrefs::effective(0, 0, 38400) == 38400);
assert(GpsBaudPrefs::effective(0, 9600, 38400) == 9600);
assert(GpsBaudPrefs::effective(0, 1234, 38400) == 38400); // insane found: ignored
assert(GpsBaudPrefs::effective(0, 0xFFFFFFFFu, 38400) == 38400);
assert(GpsBaudPrefs::effective(115200, 9600, 38400) == 115200); // a choice wins
assert(GpsBaudPrefs::foundNeedsWrite(9600, false, 0));
assert(!GpsBaudPrefs::foundNeedsWrite(9600, true, 9600));
assert(GpsBaudPrefs::foundNeedsWrite(38400, true, 9600));
assert(!GpsBaudPrefs::foundNeedsWrite(0, false, 0));
assert(!GpsBaudPrefs::foundNeedsWrite(1000000, false, 0));
printf("test_gps_baud_probe: ok\n");
return 0;
}