M2 step 3: GNSS Service, with standby and the clock from a Fix

The GNSS Service reads the receiver (UART 15/13, 115200, 1 KB buffer)
from its 50 ms tick and feeds the NMEA parser. With a Fix it sets the
clock (GNSS is the most trusted TimeSource) and refreshes it every 10
min. Settings gets GNSS On/Off and the coordinate format (Q64).

Off puts the receiver in standby with $PCAS12,65535, renewed hourly; On
wakes it with a hot start, $PCAS10,0. Both measured on the device: the
output stops within a second, and a command wakes it within a second.

Commands: gnss status (Fix, satellites per constellation, bytes and
sentences), gnss nmea on|off, gnss send <sentence>, gnss restart. The
probe is gone; never drive GPIO 15 (the receiver's output): the first
probe's swapped-pin attempt silenced it until a power cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-04 22:26:53 +02:00
co-authored by Claude Opus 5.5
parent c9b1ecb772
commit 988253ef02
13 changed files with 257 additions and 32 deletions
+13
View File
@@ -2,6 +2,7 @@
#include <cstdio>
#include <string>
#include <vector>
#include "nmea_parser.h"
@@ -195,6 +196,17 @@ void test_an_overlong_line_is_dropped() {
TEST_ASSERT_TRUE(p.state().positionValid);
}
void test_every_complete_line_reaches_the_echo() {
NmeaParser p;
std::vector<std::string> seen;
p.onLine = [&](const std::string& l) { seen.push_back(l); };
std::string data = "$GNVTG,,,,,,,,,N*2E\r\nbroken\r\n$GN";
p.feed(data.data(), data.size());
TEST_ASSERT_EQUAL(2, static_cast<int>(seen.size()));
TEST_ASSERT_EQUAL_STRING("$GNVTG,,,,,,,,,N*2E", seen[0].c_str());
TEST_ASSERT_EQUAL_STRING("broken", seen[1].c_str());
}
void test_constellation_names() {
TEST_ASSERT_EQUAL_STRING("GPS", constellationName(Constellation::Gps));
TEST_ASSERT_EQUAL_STRING("Galileo", constellationName(Constellation::Galileo));
@@ -218,6 +230,7 @@ int main() {
RUN_TEST(test_gsa_marks_the_satellites_used_per_system);
RUN_TEST(test_split_feeds_and_noise_are_handled);
RUN_TEST(test_an_overlong_line_is_dropped);
RUN_TEST(test_every_complete_line_reaches_the_echo);
RUN_TEST(test_constellation_names);
return UNITY_END();
}
+2
View File
@@ -26,6 +26,8 @@ void test_defaults_when_store_is_empty() {
TEST_ASSERT_TRUE(s.getBool(Setting::Sound));
TEST_ASSERT_TRUE(s.getBool(Setting::ProbeMacRaw));
TEST_ASSERT_TRUE(s.getBool(Setting::WifiEnabled));
TEST_ASSERT_TRUE(s.getBool(Setting::GnssEnabled)); // Q58: on by default
TEST_ASSERT_FALSE(s.getBool(Setting::CoordinatesDms)); // Q64: decimal degrees
TEST_ASSERT_EQUAL_STRING("EU868", s.getString(Setting::Region).c_str());
TEST_ASSERT_EQUAL_STRING("CET-1CEST,M3.5.0,M10.5.0/3", s.getString(Setting::Timezone).c_str());
}
@@ -107,6 +107,19 @@ void test_names_are_text_rows_with_their_byte_limits() {
TEST_ASSERT_EQUAL_STRING("RORO", f.menu.value(shortName).c_str());
}
void test_gnss_and_coordinate_rows_toggle() {
Fixture f;
int gnss = f.row(SettingsMenu::Row::Gnss), coords = f.row(SettingsMenu::Row::Coordinates);
TEST_ASSERT_EQUAL_STRING("On", f.menu.value(gnss).c_str());
TEST_ASSERT_EQUAL_STRING("Decimal", f.menu.value(coords).c_str());
f.menu.toggle(gnss);
f.menu.toggle(coords);
TEST_ASSERT_FALSE(f.settings.getBool(Setting::GnssEnabled));
TEST_ASSERT_TRUE(f.settings.getBool(Setting::CoordinatesDms));
TEST_ASSERT_EQUAL_STRING("Off", f.menu.value(gnss).c_str());
TEST_ASSERT_EQUAL_STRING("Deg min sec", f.menu.value(coords).c_str());
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_every_row_has_a_label);
@@ -118,5 +131,6 @@ int main() {
RUN_TEST(test_brightness_steps_with_left_right_within_bounds);
RUN_TEST(test_wifi_is_a_page);
RUN_TEST(test_names_are_text_rows_with_their_byte_limits);
RUN_TEST(test_gnss_and_coordinate_rows_toggle);
return UNITY_END();
}