M2 step 6: Tracks, recorded to GPX in the background

`r` in the GNSS App (or `gnss track start|stop`) records a Track to
/gnss/tracks/YYYYMMDD-HHMMSS.gpx: a point every 5 s once moved 5 m
(lib/gnss/track, 7 tests: haversine, the rule, GPX text, the file name).
It keeps recording with the App closed, shows REC in the Status Bar, and
announces start and stop with a Toast. It needs a card and the time;
switching GNSS off stops it. Tracks are written like Captures: past 90 %
card usage, until full. Storage Clean-up gets a GNSS tracks category.

A Track cut short by a reset or power loss has no GPX footer; the GNSS
Service closes such files at the next boot.

Verified on the device: a recorded Track and one interrupted by a reset
both parse as GPX 1.1 on the PC.

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:46:57 +02:00
co-authored by Claude Opus 5.5
parent 463ed2dda8
commit d7952612dd
13 changed files with 335 additions and 11 deletions
+24
View File
@@ -46,6 +46,15 @@ bool GnssApp::onKey(const KeyEvent& e) {
requestRedraw();
return true;
}
if (e.key == Key::Char && (e.ch == 'r' || e.ch == 'R')) { // record a Track, or stop it
if (gnss_.tracking()) gnss_.stopTrack();
else {
refusal_ = gnss_.startTrack(millis());
refusalMs_ = millis();
}
requestRedraw();
return true;
}
return false; // Back leaves the App
}
@@ -123,6 +132,21 @@ void GnssApp::drawPosition(Canvas& c) {
row("HDOP", s.fix == FixType::None ? "-" : line);
if (s.timeValid) std::snprintf(line, sizeof line, "%02d:%02d:%02d UTC", s.hour, s.minute, s.second);
row("Time", s.timeValid ? line : "-");
// The Track line, at the bottom left.
c.setFont(&fonts::small);
if (gnss_.tracking()) {
c.setTextColor(theme::kWarning);
std::snprintf(line, sizeof line, "REC %d point%s, %s r: stop", gnss_.trackPoints(),
gnss_.trackPoints() == 1 ? "" : "s", duration(now - gnss_.trackStartMs()).c_str());
} else if (!refusal_.empty() && now - refusalMs_ < 4000) {
c.setTextColor(theme::kWarning);
std::snprintf(line, sizeof line, "%s", refusal_.c_str());
} else {
c.setTextColor(theme::kMuted);
std::snprintf(line, sizeof line, "r: record a Track");
}
c.drawString(line, 4, area.y + area.h - 9);
}
void GnssApp::drawSky(Canvas& c) {