Skip to content

feat(gpsl2c): add GPS L2C (CNAV) decoder#65

Merged
siebc merged 1 commit into
masterfrom
sc/gps-l2
Jul 1, 2026
Merged

feat(gpsl2c): add GPS L2C (CNAV) decoder#65
siebc merged 1 commit into
masterfrom
sc/gps-l2

Conversation

@giove-a

@giove-a giove-a commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Adds a GPS L2C civil-navigation (CNAV) decoder per IS-GPS-200N §30, on the
v2 soft-symbol API. GPS L2C broadcasts the same 300-bit CNAV message as GPS
L5I (IS-GPS-200N §30 ≡ IS-GPS-705J §20.3.3) — identical preamble, rate-1/2 K=7
continuous FEC (G1=0o171, G2=0o133), CRC-24Q, per-message-type layouts, π, and
TOW × 6. The 25 bps → 50 sps / 12-second framing is time-domain only, so the
symbol-domain decoder is unchanged (600-symbol message, 616-symbol sync window).
Cross-checked against gnss-sdr and pocketsdr, both of which drive L2C and L5
through one CNAV decoder.

What's included

  • Shared GPS CNAV core extracted into src/gps/cnav.jl (FEC, window-Viterbi
    sync, CRC gate, all parse_mt*, and the data container). src/gps/l5.jl and
    the new src/gps/l2c.jl are thin signal layers.
  • GPSL2CMDecoderState(prn) + GNSSDecoderState(::GPSL2CM, prn) dispatch.
    The only decode difference from L5I is is_sat_healthy, which reports the
    L2 health bit (MT10 bit 53) vs L5's bit 54 — dispatched on a parametric
    GPSCNAVConstants{S} (aliased GPSL5IConstants / GPSL2CMConstants).
  • Signal-neutral type names: the shared CNAV container/records are now
    GPSCNAVData, GPSCNAVReducedAlmanac, GPSCNAVMidiAlmanac,
    GPSCNAV{Clock,Ephemeris}DifferentialCorrection, GPSCNAVIntegritySupportMessage.
    The former GPSL5I* names remain as exported deprecated aliases
    (Base.@deprecate_binding), to be removed at the next major version — so this
    is non-breaking for downstream users.
  • Shared primitives relocated: UInt320 (packed-word type) and
    _merge_keyed moved from gps/l1ca.jl / gps/l1c_d.jl into src/gnss.jl, so
    the CNAV core no longer depends on sibling signal files.

Dependency

Requires GNSSSignals ≥ 2.6.0 (adds the GPSL2CM signal type);
[compat] GNSSSignals = "2.6".

Tests

  • Synthetic round-trip suite (shared CNAV transmit-chain helpers in
    test/cnav_test_utils.jl, reused by the L5I and L2C tests).
  • Spirent-derived golden fixture (test/data/gps_l2c_prn25_nav_bits.bin,
    PRN 25, one full 24-message broadcast cycle) whose decoded parameters match
    Spirent's own reference decode across every message type (MT10/11, 30–37,
    12/13/14/31/34/37, 15/36).
  • Full suite: 2446/2446 pass; docs build clean (doctests included).

Notes for merge

Please merge squash or rebase (not a merge commit) to keep history linear
for release CI; semantic-release will minor-bump 3.1.0 → 3.2.0 from the
feat(gpsl2c) commit.

GPS L2C broadcasts the same 300-bit CNAV message as GPS L5I (IS-GPS-200N
§30 ≡ IS-GPS-705J §20.3.3): identical preamble, rate-1/2 K=7 continuous
FEC, CRC-24Q, per-message-type layouts, π, and TOW×6. The 25 bps→50 sps /
12-second framing is time-domain only and leaves the symbol-domain decoder
unchanged (600-symbol message, 616-symbol sync window). Cross-checked
against gnss-sdr and pocketsdr, which both reuse one CNAV decoder for the
two signals.

Extract the shared CNAV core (FEC, window-Viterbi sync, CRC gate, all
parse_mt*, and the data container) into src/gps/cnav.jl; l5.jl and the new
l2c.jl are thin signal layers. The only decode difference is that
is_sat_healthy reports the L2 health bit (MT10 bit 53) for L2C vs the L5 bit
(54), dispatched on the constants type — one parametric GPSCNAVConstants{S}
aliased as GPSL5IConstants / GPSL2CMConstants.

The shared CNAV data container and its records are named signal-neutrally
(GPSCNAVData, GPSCNAVReducedAlmanac, GPSCNAVMidiAlmanac, GPSCNAVClock/
EphemerisDifferentialCorrection, GPSCNAVIntegritySupportMessage); the former
GPSL5I* names remain as exported deprecated aliases, to be removed at the
next major version. Internal helpers likewise use neutral gps_cnav_* /
GPSCNAV* / CNAV_* names.

Relocate the signal-agnostic GPS primitives UInt320 (packed-word type) and
_merge_keyed (dictionary helper) out of gps/l1ca.jl and gps/l1c_d.jl into the
shared gnss.jl, so the CNAV core depends only on shared utilities and not on
sibling signal files.

The L2 signal types (GPSL2CM/GPSL2CL) come from GNSSSignals v2.6.0, so
[compat] requires GNSSSignals 2.6.

Tests: add a synthetic round-trip suite plus a Spirent-derived golden
fixture (test/data/gps_l2c_prn25_nav_bits.bin, PRN 25, one full 24-message
broadcast cycle) whose decoded parameters match Spirent's own reference
decode across every message type. Factor the shared CNAV transmit-chain test
helpers into test/cnav_test_utils.jl, reused by the L5I and L2C tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.83099% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.13%. Comparing base (49336b7) to head (452eb2a).

Files with missing lines Patch % Lines
src/gps/cnav.jl 96.70% 9 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master      #65   +/-   ##
=======================================
  Coverage   98.13%   98.13%           
=======================================
  Files          12       14    +2     
  Lines        1499     1505    +6     
=======================================
+ Hits         1471     1477    +6     
  Misses         28       28           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@siebc
siebc merged commit bc9a607 into master Jul 1, 2026
9 checks passed
@siebc
siebc deleted the sc/gps-l2 branch July 1, 2026 08:57
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.

2 participants