Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ interleave!

## Data Types

Every concrete per-signal data type subtypes the abstract supertype of its
constellation, which in turn subtypes `AbstractGNSSData`. The supertypes carry
the facts every signal of a constellation shares (e.g. the Galileo
ephemeris/clock completeness checks), stated once via subtype dispatch.

```@docs
GNSSDecoder.AbstractGPSData
GNSSDecoder.AbstractGalileoData
```

### GPS L1 C/A

```@docs
Expand Down
32 changes: 5 additions & 27 deletions src/galileo/e1b.jl
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ OS SIS ICD, Issue 2.2.

Galileo OS SIS ICD, Issue 2.2, Tables 42-55, 67-87
"""
Base.@kwdef struct GalileoE1BData <: AbstractGNSSData
Base.@kwdef struct GalileoE1BData <: AbstractGalileoData
WN::Union{Nothing,Int64} = nothing
TOW::Union{Nothing,Int64} = nothing

Expand Down Expand Up @@ -492,32 +492,10 @@ function Base.:(==)(a::GalileoE1BData, b::GalileoE1BData)
return true
end

function is_ephemeris_decoded(data::GalileoE1BData)
!isnothing(data.t_0e) &&
!isnothing(data.M_0) &&
!isnothing(data.e) &&
!isnothing(data.sqrt_A) &&
!isnothing(data.Ω_0) &&
!isnothing(data.i_0) &&
!isnothing(data.ω) &&
!isnothing(data.i_dot) &&
!isnothing(data.Ω_dot) &&
!isnothing(data.Δn) &&
!isnothing(data.C_uc) &&
!isnothing(data.C_us) &&
!isnothing(data.C_rc) &&
!isnothing(data.C_rs) &&
!isnothing(data.C_ic) &&
!isnothing(data.C_is)
end

function is_clock_correction_decoded(data::GalileoE1BData)
!isnothing(data.t_0c) &&
!isnothing(data.a_f0) &&
!isnothing(data.a_f1) &&
!isnothing(data.a_f2)
end

# `is_ephemeris_decoded` and `is_clock_correction_decoded` are per-constellation
# facts (identical fields for I/NAV and F/NAV), defined once on
# `AbstractGalileoData` in `galileo/galileo.jl`. Only the health-status check
# below is genuinely per-signal (E1B carries E1-B/E5b health).
function is_health_status_decoded(data::GalileoE1BData)
!isnothing(data.signal_health_e1b) &&
!isnothing(data.signal_health_e5b) &&
Expand Down
32 changes: 5 additions & 27 deletions src/galileo/e5a.jl
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ the decoder multiplies by π), matching the convention used by [`GalileoE1BData`

Galileo OS SIS ICD, Issue 2.2, §5.1, Tables 75-80
"""
Base.@kwdef struct GalileoE5aData <: AbstractGNSSData
Base.@kwdef struct GalileoE5aData <: AbstractGalileoData
WN::Union{Nothing,Int64} = nothing
TOW::Union{Nothing,Int64} = nothing

Expand Down Expand Up @@ -369,32 +369,10 @@ function Base.:(==)(a::GalileoE5aData, b::GalileoE5aData)
return true
end

function is_ephemeris_decoded(data::GalileoE5aData)
!isnothing(data.t_0e) &&
!isnothing(data.M_0) &&
!isnothing(data.e) &&
!isnothing(data.sqrt_A) &&
!isnothing(data.Ω_0) &&
!isnothing(data.i_0) &&
!isnothing(data.ω) &&
!isnothing(data.i_dot) &&
!isnothing(data.Ω_dot) &&
!isnothing(data.Δn) &&
!isnothing(data.C_uc) &&
!isnothing(data.C_us) &&
!isnothing(data.C_rc) &&
!isnothing(data.C_rs) &&
!isnothing(data.C_ic) &&
!isnothing(data.C_is)
end

function is_clock_correction_decoded(data::GalileoE5aData)
!isnothing(data.t_0c) &&
!isnothing(data.a_f0) &&
!isnothing(data.a_f1) &&
!isnothing(data.a_f2)
end

# `is_ephemeris_decoded` and `is_clock_correction_decoded` are per-constellation
# facts (identical fields for I/NAV and F/NAV), defined once on
# `AbstractGalileoData` in `galileo/galileo.jl`. Only the health-status check
# below is genuinely per-signal (E5a carries only E5a health).
function is_health_status_decoded(data::GalileoE5aData)
!isnothing(data.signal_health_e5a) && !isnothing(data.data_validity_status_e5a)
end
Expand Down
32 changes: 32 additions & 0 deletions src/galileo/galileo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,38 @@ function GalileoAlmanac(
)
end

# Ephemeris/clock completeness are per-constellation facts: I/NAV (E1B) and
# F/NAV (E5a) broadcast the same orbital and clock parameters, so the "all
# present?" checks are identical and dispatch on the constellation supertype
# `AbstractGalileoData` (both `GalileoE1BData` and `GalileoE5aData` subtype it).
# The health-status and positioning-readiness checks genuinely differ per signal
# and stay in `e1b.jl` / `e5a.jl`.
function is_ephemeris_decoded(data::AbstractGalileoData)
!isnothing(data.t_0e) &&
!isnothing(data.M_0) &&
!isnothing(data.e) &&
!isnothing(data.sqrt_A) &&
!isnothing(data.Ω_0) &&
!isnothing(data.i_0) &&
!isnothing(data.ω) &&
!isnothing(data.i_dot) &&
!isnothing(data.Ω_dot) &&
!isnothing(data.Δn) &&
!isnothing(data.C_uc) &&
!isnothing(data.C_us) &&
!isnothing(data.C_rc) &&
!isnothing(data.C_rs) &&
!isnothing(data.C_ic) &&
!isnothing(data.C_is)
end

function is_clock_correction_decoded(data::AbstractGalileoData)
!isnothing(data.t_0c) &&
!isnothing(data.a_f0) &&
!isnothing(data.a_f1) &&
!isnothing(data.a_f2)
end

"""
galileo_viterbi(decoder, soft_page, interleaver_rows, interleaver_cols, ::Type{T}) -> T

Expand Down
32 changes: 32 additions & 0 deletions src/gnss.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@ abstract type AbstractGNSSConstants end
abstract type AbstractGNSSData end
abstract type AbstractGNSSCache end

"""
AbstractGPSData <: AbstractGNSSData

Abstract supertype for the decoded navigation data of a signal transmitted by
the GPS constellation, e.g. `GPSL1CAData`, `GPSCNAVData`.

Its purpose is to carry the constellation-level facts every GPS signal's data
shares, so they can be stated once (on the supertype, via subtype dispatch)
instead of once per signal. Constellation membership is encoded at the struct
definition site — the `<: AbstractGPSData` line written anyway — so a new GPS
signal inherits the shared behaviour with nothing to remember. Genuinely
per-signal facts (the subframe/message-type completeness checks, the health-bit
selection in `is_sat_healthy`) stay defined on the concrete data types.
"""
abstract type AbstractGPSData <: AbstractGNSSData end

"""
AbstractGalileoData <: AbstractGNSSData

Abstract supertype for the decoded navigation data of a signal transmitted by
the Galileo constellation, e.g. `GalileoE1BData`, `GalileoE5aData`.

The Galileo counterpart to [`AbstractGPSData`](@ref). It carries the facts every
Galileo signal's data shares: `is_ephemeris_decoded` and
`is_clock_correction_decoded` check the same orbital and clock fields for I/NAV
(E1B) and F/NAV (E5a), so they are defined once on this supertype (see
`src/galileo/galileo.jl`) instead of once per signal. The health-status and
positioning-readiness checks genuinely differ per signal and stay on the
concrete data types.
"""
abstract type AbstractGalileoData <: AbstractGNSSData end

# Physical constants common to every GNSS handled here. Each per-signal
# `*Constants` struct exposes these as fields (so the orbit/clock math reads
# `state.constants.PI` etc.); the defaults are sourced from here to keep a single
Expand Down
2 changes: 1 addition & 1 deletion src/gps/cnav.jl
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ code that applies these corrections must handle `nothing` (treat as 0).

IS-GPS-705J, Figures 20-1 through 20-17 and Tables 20-I through 20-XIa.
"""
Base.@kwdef struct GPSCNAVData <: AbstractGNSSData
Base.@kwdef struct GPSCNAVData <: AbstractGPSData
last_message_id::Int = 0
TOW::Union{Nothing,Int64} = nothing
alert_flag::Union{Nothing,Bool} = nothing
Expand Down
2 changes: 1 addition & 1 deletion src/gps/l1c_d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ CRC-valid SF3 page regardless of whether its page format is parsed.

IS-GPS-800J, Figures 3.5-1 through 3.5-9 and Tables 3.5-1, 3.5-3 … 3.5-8.
"""
Base.@kwdef struct GPSL1C_DData <: AbstractGNSSData
Base.@kwdef struct GPSL1C_DData <: AbstractGPSData
toi::Union{Nothing,Int} = nothing
ITOW::Union{Nothing,Int64} = nothing
WN::Union{Nothing,Int64} = nothing
Expand Down
2 changes: 1 addition & 1 deletion src/gps/l1ca.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ subframes 1, 2, and 3 of the GPS LNAV message. All parameters conform to IS-GPS-

IS-GPS-200N, Tables 20-I, 20-II, 20-III, Sections 20.3.3.3-20.3.3.4
"""
Base.@kwdef struct GPSL1CAData <: AbstractGNSSData
Base.@kwdef struct GPSL1CAData <: AbstractGPSData
last_subframe_id::Int = 0
integrity_status_flag::Union{Nothing,Bool} = nothing
TOW::Union{Nothing,Int64} = nothing
Expand Down
64 changes: 64 additions & 0 deletions test/gnss_supertype.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@testset "GNSS data supertypes" begin
# The per-constellation abstract supertypes sit between `AbstractGNSSData`
# and the concrete per-signal data types.
@test GNSSDecoder.AbstractGPSData <: GNSSDecoder.AbstractGNSSData
@test GNSSDecoder.AbstractGalileoData <: GNSSDecoder.AbstractGNSSData

gps_data = [GNSSDecoder.GPSL1CAData, GNSSDecoder.GPSL1C_DData, GNSSDecoder.GPSCNAVData]
galileo_data = [GNSSDecoder.GalileoE1BData, GNSSDecoder.GalileoE5aData]

# Constellation membership is encoded at each struct's definition site.
for D in gps_data
@test D <: GNSSDecoder.AbstractGPSData
@test !(D <: GNSSDecoder.AbstractGalileoData)
end
for D in galileo_data
@test D <: GNSSDecoder.AbstractGalileoData
@test !(D <: GNSSDecoder.AbstractGPSData)
end

# `is_ephemeris_decoded` / `is_clock_correction_decoded` are stated once per
# constellation: every Galileo data type dispatches to the single method on
# `AbstractGalileoData` rather than a per-signal copy.
for D in galileo_data
@test which(GNSSDecoder.is_ephemeris_decoded, (D,)).sig == Tuple{
typeof(GNSSDecoder.is_ephemeris_decoded),
GNSSDecoder.AbstractGalileoData,
}
@test which(GNSSDecoder.is_clock_correction_decoded, (D,)).sig == Tuple{
typeof(GNSSDecoder.is_clock_correction_decoded),
GNSSDecoder.AbstractGalileoData,
}
end

# The collapsed methods still behave: all fields present ⇒ decoded, a
# missing field ⇒ not decoded. Exercised for both Galileo signals.
for D in galileo_data
empty = D()
@test !GNSSDecoder.is_ephemeris_decoded(empty)
@test !GNSSDecoder.is_clock_correction_decoded(empty)

full_eph = D(;
t_0e = 0,
M_0 = 0.0,
e = 0.0,
sqrt_A = 0.0,
Ω_0 = 0.0,
i_0 = 0.0,
ω = 0.0,
i_dot = 0.0,
Ω_dot = 0.0,
Δn = 0.0,
C_uc = 0.0,
C_us = 0.0,
C_rc = 0.0,
C_rs = 0.0,
C_ic = 0.0,
C_is = 0.0,
)
@test GNSSDecoder.is_ephemeris_decoded(full_eph)

full_clock = D(; t_0c = 0, a_f0 = 0.0, a_f1 = 0.0, a_f2 = 0.0)
@test GNSSDecoder.is_clock_correction_decoded(full_clock)
end
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ end
Aqua.test_all(GNSSDecoder)
end

include("gnss_supertype.jl")
include("bit_fiddling.jl")
include("gpsl1.jl")
include("gps_l1c_d.jl")
Expand Down
Loading