Skip to content

GPS: one fix per receiver epoch from standard sentences; Unicore INSPVAX - #300

Merged
chriskinal merged 2 commits into
developfrom
feat/gps-epoch-assembler
Oct 9, 2026
Merged

chriskinal merged 2 commits into
developfrom
feat/gps-epoch-assembler

Conversation

@chriskinal

Copy link
Copy Markdown
Collaborator

Phase 2 of Plans/GPS_RECEIVER_SENTENCES_PLAN.md, on top of #298's ingest. Receivers that spread a fix over standard sentences now work, and the UM981's fused INS log does too.

Epoch assembler (Services/Gps/NmeaEpochAssembler)

Takes GGA/GNS, VTG, HPR, HDT and THS and emits one fix per receiver epoch:

  • Epoch identity. Sentences carrying a UTC (GGA, GNS, HPR) belong to the epoch with that UTC; those without (VTG, HDT, THS) attach to the open one; a repeated kind starts a new epoch. Members printed before the position sentence belong to its epoch.
  • Learning. The per-epoch set is learned from three alike epochs; until then each epoch is closed by the next one's start (one epoch of lag). Once learned, the fix is emitted the moment the last member arrives: no timer, no lag.
  • A lost sentence is marked absent on that fix (heading invalid, roll 0, speed 0, nothing carried over from the previous epoch) and counted as incomplete. A changed set (receiver reconfigured, a member added or removed) is relearned.
  • Mapping. GGA/GNS give position, fix, satellites, HDOP and the correction age $KSXT lacks (GNS mode letters map to the GGA scale). VTG gives speed; its track is only used through fix-to-fix. HPR/HDT/THS from the receiver are the dual heading (HPR heading when fixed or float, its pitch as the roll only when fixed); the same sentences from an IMU talker (IN, HE) go to the IMU slot like $PANDA's. Roll goes through the AHRS calibration.

Unicore logs

# (long header) and % (short header) logs are framed with their CRC-32; INSPVAXA / INSPVAA decode as one fix: azimuth as the heading (no IMU fusion on the host), speed from the north and east velocities, INS roll and pitch, fix quality from the position type and none while the INS is inactive. Checked against the UM981 manual's example line and the CRC it prints.

One finding on the way: the v26 firmware's INSPVAXA decoder reads the velocities one field early (it skips the undulation), so it computes speed from undulation and north velocity. Worth a fix there; the app follows the manual.

Also

  • The parser's frame minimum no longer rejects short sentences such as $GPHDT,275.5,T.
  • The sentence monitor keeps a slot per type and counts members apart from fixes and refusals; the status bar shows the learned family (GGA+VTG+HPR); the System Data card counts incomplete epochs; INSPVAX leaves HDOP and age unreported like $KSXT.
  • Docs/GPS_RECEIVERS.md: transport, the bridge contract, what each family yields, the card's rows, setup for the Bynav/UM982, the UM981 and an AiO.

Tests

NmeaEpochAssemblerTests (18: warm-up lag, learning, emission on the last member, dropped member, speed not carried over, reconfigured smaller and larger, roll and heading quality rules, AHRS calibration, IMU talker, HDT/THS, GNS modes, UTC and repeat-kind boundaries, members before the position sentence, position-less epochs dropped, zero allocation), UnicoreLogTests (10: the manual's CRC and line, short header, INSPVAA, inactive INS, position types, calibration, unknown logs and bad frames, zero allocation), chunking determinism through AutoSteerService for a three-sentence receiver. Services 1501 / ViewModels 308 / Models 167 pass; desktop builds.

Bench: your T1-FD switched to GPGGA 0.1 + GPVTG 0.1 + GPHPR 0.1 should show GGA+VTG+HPR in the status bar after three epochs, with the age now populated; a UM981 printing INSPVAXA 0.1 through the passthrough should show INSPVAX. Both still without captures, so the first real run is the test.

🤖 Generated with Claude Code

Phase 2 of Plans/GPS_RECEIVER_SENTENCES_PLAN.md.

- NmeaEpochAssembler takes GGA/GNS, VTG, HPR, HDT and THS and emits one
  fix per receiver epoch. Sentences with a UTC belong to that epoch;
  those without attach to the open one. The receiver's per-epoch set is
  learned from three alike epochs (one epoch of lag until then); once
  learned the fix is emitted on the set's last member, with no timer. A
  member that never comes is marked absent (heading invalid, roll 0,
  speed 0, nothing carried over) and counted as incomplete; a changed
  set is relearned. A heading from an IMU talker (IN, HE) goes to the
  IMU slot, as $PANDA's does; HPR's pitch is the roll, taken when fixed.
- The parser dispatches $<talker><type> sentences to the assembler and
  frames Unicore / NovAtel '#' and '%' logs with their CRC-32; INSPVAXA
  and INSPVAA (UM981) decode as one fused fix, checked against the
  manual's example line and its printed CRC. The frame minimum no longer
  rejects short sentences such as $GPHDT,h,T.
- The monitor keeps a slot per sentence type and counts members apart
  from fixes and refusals; the status bar shows the learned family
  ("GGA+VTG+HPR"); the System Data card counts incomplete epochs; KSXT
  and INSPVAX leave HDOP and age unreported.
- Docs/GPS_RECEIVERS.md: transport, bridge contract, families, card
  rows, per-receiver setup.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first real fixture: 10 s from Chris's T1-FD behind the AiO v26
passthrough (one sentence per datagram, 100 epochs, RTK float while
the position reconverged after the output change, heading fixed), with
two of a stranger's binary datagrams that the capture tool now logs.
Replayed through the ingest path: the family is learned, every epoch
gives one fix with the receiver's own values, nothing is incomplete,
dropped or refused, and the same fixes come out whether the datagrams
are replayed one per line, one per epoch, or cut every 37 bytes.

NmeaCapture reads the capture format; fixtures under Fixtures/nmea are
copied beside the test assembly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@chriskinal

Copy link
Copy Markdown
Collaborator Author

Bench-confirmed and now covered by a real fixture. The T1-FD behind the AiO passthrough, switched to GPGGA 0.1 + GPVTG 0.1 + GPHPR 0.1, showed GGA+VTG+HPR at 10 Hz with the correction age populated, dual heading and roll from HPR, 0 incomplete epochs, 0 refused. The 10 s capture is in Tests/AgOpenWeb.Services.Tests/Fixtures/nmea/um982-gga-vtg-hpr.txt and CaptureReplayTests replays it through the ingest path: 100 epochs → 100 fixes with the receiver's own values, and the same fixes whether replayed one line per datagram, one epoch per datagram, or cut every 37 bytes (joined 200+ times).

@chriskinal
chriskinal merged commit 15efa2e into develop Oct 9, 2026
4 checks passed
@chriskinal
chriskinal deleted the feat/gps-epoch-assembler branch October 9, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant