From 07b01b4bdd9589d5b5c9712ac17d5efb95cfe6c1 Mon Sep 17 00:00:00 2001 From: siebc <226531417+siebc@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:19:29 +0000 Subject: [PATCH] feat: export is_decoding_completed_for_positioning Export the positioning-readiness predicate and add a `GNSSDecoderState` method that forwards to the validated `data` field, so a receiver (e.g. PositionVelocityTime.jl) can gate PVT on it alongside `is_sat_healthy` without reaching into decoder internals. Whenever the predicate is `true` the health field `is_sat_healthy` inspects is guaranteed decoded, so the two checks compose safely. The docstring records what it deliberately does not gate on (ephemeris freshness, second-order corrections, the alert flag). Also document the export in the API reference, which the docs build requires via `checkdocs = :exports`. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/src/api.md | 12 ++++++++++++ src/GNSSDecoder.jl | 1 + src/gnss.jl | 38 ++++++++++++++++++++++++++++++++++++++ test/gps_l2cm.jl | 6 ++++++ 4 files changed, 57 insertions(+) diff --git a/docs/src/api.md b/docs/src/api.md index 20c57ee..0019460 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -35,6 +35,18 @@ reset_decoder_state is_sat_healthy ``` +## Positioning Readiness + +Pair [`is_decoding_completed_for_positioning`](@ref) with [`is_sat_healthy`](@ref) +to gate use of a satellite in a fix: the first confirms the required navigation +data set has been decoded and validated, the second that the satellite is +broadcasting healthy. See the docstring for what it deliberately does *not* +gate on (ephemeris freshness, second-order corrections, the alert flag). + +```@docs +is_decoding_completed_for_positioning +``` + ## Signal Metadata `GNSSSignals.get_data_frequency` is extended for [`GNSSDecoderState`](@ref): it diff --git a/src/GNSSDecoder.jl b/src/GNSSDecoder.jl index 7bf1203..82d0724 100755 --- a/src/GNSSDecoder.jl +++ b/src/GNSSDecoder.jl @@ -21,6 +21,7 @@ export decode, GalileoE1BDecoderState, GalileoE5aDecoderState, is_sat_healthy, + is_decoding_completed_for_positioning, GNSSDecoderState, reset_decoder_state # v2 shared utilities — see issue #36. Used directly by issue #37 (Galileo diff --git a/src/gnss.jl b/src/gnss.jl index 1caed60..b5de01f 100644 --- a/src/gnss.jl +++ b/src/gnss.jl @@ -153,6 +153,44 @@ function deques_equal(a::CircularDeque{T}, b::CircularDeque{T}) where {T} return true end +""" +$(TYPEDSIGNATURES) + +Report whether a decoder has recovered the minimum navigation data a +positioning engine needs from this satellite: a time of week, a full ephemeris +(orbit) set, and the SV clock-correction polynomial (plus, on the signals that +carry it in the same required set, the broadcast week number and single-band +group delay). Dispatches on the validated [`data`](@ref GNSSDecoderState) field, +so it only becomes `true` once the required message set has passed CRC/parity +**and** the cross-subframe issue-of-data consistency check that promotes +`raw_data` to `data`. + +This is the readiness gate a receiver (e.g. `PositionVelocityTime.jl`) should +pair with [`is_sat_healthy`](@ref): whenever this returns `true`, the health +field `is_sat_healthy` inspects is guaranteed to have been decoded, so the two +can be checked together without a separate `nothing` guard. + +!!! note "What this deliberately does *not* gate on" + + A `true` here means the *data set* is complete and self-consistent — it is a + necessary condition for using the SV in a fix, not a blanket guarantee that + no further judgement is required: + + - **Ephemeris freshness.** Only presence is checked, not age. The decoder + has no notion of "now", so the consumer must still reject ephemerides + outside their fit interval (`fit_interval` / `t_oe` age). + - **Second-order corrections.** Group delay / inter-signal corrections + (`T_GD`, `ISC_*`) beyond the single required band, Klobuchar + ionosphere, and UTC parameters are intentionally excluded because they + are broadcast far less often; apply them when present and treat + `nothing` as zero rather than waiting for them. + - **Alert flag.** `is_sat_healthy` reflects the broadcast health bits + only; a receiver that wants to honour the L1 C/A alert flag (or + equivalent) must check it separately. +""" +is_decoding_completed_for_positioning(state::GNSSDecoderState) = + is_decoding_completed_for_positioning(state.data) + "Soft-symbol buffer accessor — the per-signal cache stores it as `soft_buffer`." soft_buffer(state::GNSSDecoderState) = state.cache.soft_buffer diff --git a/test/gps_l2cm.jl b/test/gps_l2cm.jl index cfafc85..a094761 100644 --- a/test/gps_l2cm.jl +++ b/test/gps_l2cm.jl @@ -55,6 +55,9 @@ end @test state_a.constants isa GNSSDecoder.GPSL2CMConstants @test isnothing(state_a.num_bits_after_valid_syncro_sequence) @test !state_a.is_shifted_by_180_degrees + # A fresh decoder has no validated data, so the exported + # state-forwarding readiness predicate is `false`. + @test !is_decoding_completed_for_positioning(state_a) end @testset "Shares the L5I CNAV decode path" begin @@ -315,6 +318,9 @@ end @test isnothing(d.ism) @test GNSSDecoder.is_decoding_completed_for_positioning(d) + # Exported state-forwarding method: dispatches on the validated `data`, + # so it agrees with the `data`-typed check above. + @test is_decoding_completed_for_positioning(state) @test is_sat_healthy(state) end