GNSSDecoder.jl
A Julia package for decoding GNSS (Global Navigation Satellite System) navigation messages.
Supported Systems
- GPS L1 C/A: Decodes the 50 bps LNAV data stream from GPS L1 civil signals
- GPS L1C-D: Decodes the 100 sps CNAV-2 data stream from the modernized GPS L1C signal's data component
- GPS L2C: Decodes the 50 sps CNAV data stream from the GPS L2 CM (civil-moderate) signal component
- GPS L5I: Decodes the 100 sps CNAV data stream from the GPS L5 in-phase signal component
- Galileo E1B: Decodes the 250 bps I/NAV data stream from Galileo E1B Open Service signals
- Galileo E5a: Decodes the 50 sps F/NAV data stream from the Galileo E5a in-phase (data) component
- BeiDou B1I / B3I: Decode the legacy D1 (50 bps, MEO/IGSO) and D2 (500 bps, GEO) NAV messages
- BeiDou B1C: Decodes the 100 sps B-CNAV1 data stream from the B1C data component
- BeiDou B2a: Decodes the 200 sps B-CNAV2 data stream from the B2a data component
- BeiDou B2b: Decodes the 1000 sps B-CNAV3 data stream from the B2b_I signal
Installation
using Pkg
Pkg.add("GNSSDecoder")Quick Start
GPS L1 C/A Decoding
Initialize a decoder and process soft symbols from your tracking loop. The decoder consumes Float32 soft symbols where the sign carries the bit decision (positive ⇒ bit 0, negative ⇒ bit 1) and the magnitude carries confidence (AFF3CT LLR convention):
julia> using GNSSDecoder
julia> state = GPSL1CADecoderState(1); # Initialize decoder for PRN 1
julia> state.prn # Access PRN
1
julia> typeof(state)
GNSSDecoderState{GNSSDecoder.GPSL1CAData, GNSSDecoder.GPSL1CAConstants, GNSSDecoder.GPSL1CACache}Process incoming soft symbols and check the decoder state:
julia> state = decode(state, Float32[+1, -1, -1, -1, +1, -1, +1, +1], 8); # Decode 8 soft symbols
julia> GNSSDecoder.num_bits_buffered(state) # Symbols are now buffered
8In a real application, you would decode soft symbols from a tracking loop. With Tracking.jl v2, take them from get_soft_bits, which returns the polarity-corrected, amplitude-weighted soft bits for the tracked satellite:
for i in 1:iterations
# Track signal (e.g., with Tracking.jl)
track_state = track!(measurement, track_state)
# Soft symbols for this satellite (Float32; sign = bit, magnitude = confidence)
soft_symbols = get_soft_bits(track_state, state.prn)
# Decode navigation message
state = decode(state, soft_symbols, length(soft_symbols))
end
# After decoding completes, access the data
if !isnothing(state.data.TOW)
println("Time of Week: $(state.data.TOW)")
endGalileo E1B Decoding
julia> using GNSSDecoder
julia> state = GalileoE1BDecoderState(1); # Initialize decoder for PRN 1
julia> state.prn
1
julia> typeof(state)
GNSSDecoderState{GNSSDecoder.GalileoE1BData, GNSSDecoder.GalileoE1BConstants, GNSSDecoder.GalileoE1BCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10GPS L1C-D Decoding
The GPS L1C-D (CNAV-2) decoder synchronises on the BCH-encoded TOI counter (no fixed preamble), LDPC-decodes subframes 2 and 3, and validates each with CRC-24Q. Construction loads the LDPC parity-check matrices shipped with the package:
julia> using GNSSDecoder
julia> state = GPSL1C_DDecoderState(1); # Initialize decoder for PRN 1
julia> state.prn
1
julia> typeof(state)
GNSSDecoderState{GPSL1C_DData, GNSSDecoder.GPSL1C_DConstants, GNSSDecoder.GPSL1C_DCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10GPS L5I Decoding
The GPS L5I (CNAV) decoder consumes the 100 sps FEC-encoded channel symbols. The rate-1/2 K=7 convolutional FEC runs continuously across message boundaries, so each sync attempt Viterbi-decodes the buffered 616-symbol window, looks for the 8-bit preamble at both ends of the decoded window, and validates the 300-bit message with CRC-24Q:
julia> using GNSSDecoder
julia> state = GPSL5IDecoderState(1); # Initialize decoder for PRN 1
julia> state.prn
1
julia> typeof(state)
GNSSDecoderState{GPSCNAVData, GNSSDecoder.GPSCNAVConstants{:GPSL5I}, GNSSDecoder.GPSCNAVCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10GPS L2C Decoding
GPS L2C broadcasts the same CNAV message as GPS L5I (IS-GPS-200N §30), on the L2 CM component at 50 sps. Decoding therefore reuses the shared GPS CNAV core, and decoded fields land in the same GPSCNAVData container; only the health check differs (it reports the L2 signal-health bit):
julia> using GNSSDecoder
julia> state = GPSL2CMDecoderState(1); # Initialize decoder for PRN 1
julia> state.prn
1
julia> typeof(state)
GNSSDecoderState{GPSCNAVData, GNSSDecoder.GPSCNAVConstants{:GPSL2CM}, GNSSDecoder.GPSCNAVCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10BeiDou Decoding
All five BeiDou open-service signals are decoded through the same API. B1I and B3I carry the identical legacy message — D1 NAV (50 bps) on MEO/IGSO satellites and D2 NAV (500 bps) on GEO satellites, selected automatically by PRN — so they share the BeiDouDNAVData container the way GPS L5I and L2C share GPSCNAVData:
julia> using GNSSDecoder
julia> state = BeiDouB1IDecoderState(20); # PRN 20: MEO/IGSO, D1 NAV
julia> typeof(state)
GNSSDecoderState{BeiDouDNAVData, GNSSDecoder.BeiDouDNAVConstants{:BeiDouB1I}, GNSSDecoder.BeiDouDNAVCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10The modernized BDS-3 signals B1C (B-CNAV1), B2a (B-CNAV2), and B2b (B-CNAV3) decode their ICDs' 64-ary LDPC codes via the exact binary-image parity matrices in data/ (see scripts/generate_beidou_alist.jl); their LDPC belief-propagation stage is scale-sensitive, so feed confidence-weighted soft symbols on a roughly LLR-like scale (≈ 2·r/σ²) for best sensitivity — see decode:
julia> using GNSSDecoder
julia> state = BeiDouB2aDecoderState(30); # Initialize decoder for PRN 30
julia> typeof(state)
GNSSDecoderState{BeiDouB2aData, GNSSDecoder.BeiDouB2aConstants, GNSSDecoder.BeiDouB2aCache}
julia> state = decode(state, Float32[+1, -1, +1, +1, -1, -1, -1, -1, -1, +1], 10); # Decode 10 soft symbols
julia> GNSSDecoder.num_bits_buffered(state)
10State Management
Resetting After Signal Loss
If signal tracking is lost and reacquired, use reset_decoder_state to clear buffers while preserving previously decoded ephemeris:
julia> using GNSSDecoder
julia> state = GPSL1CADecoderState(1);
julia> state = decode(state, Float32[+1, +1, +1, +1, +1, +1, +1, +1], 8); # Some decoding
julia> GNSSDecoder.num_bits_buffered(state)
8
julia> state = reset_decoder_state(state); # Reset after signal loss
julia> GNSSDecoder.num_bits_buffered(state) # Buffers are cleared
0
julia> state.prn # PRN is preserved
1Checking Satellite Health
julia> using GNSSDecoder
julia> state = GPSL1CADecoderState(1);
julia> is_sat_healthy(state) # Health not yet decoded
false
julia> state = GalileoE1BDecoderState(1);
julia> is_sat_healthy(state) # Health not yet decoded
falseData Fields
GPS L1 Data
After successful decoding, state.data contains:
| Field | Description |
|---|---|
TOW | Time of Week (seconds) |
trans_week | Transmission week number |
svhealth | Satellite health status |
t_0e, t_0c | Reference times for ephemeris and clock |
e | Eccentricity |
sqrt_A | Square root of semi-major axis |
M_0 | Mean anomaly at reference time |
Ω_0, ω | Longitude of ascending node, argument of perigee |
i_0, i_dot | Inclination and rate |
Δn, Ω_dot | Mean motion difference, rate of right ascension |
C_rs, C_rc, C_us, C_uc, C_is, C_ic | Harmonic correction terms |
a_f0, a_f1, a_f2 | Clock correction coefficients |
T_GD | Group delay differential |
Galileo E1B Data
Similar ephemeris and clock parameters are available for Galileo, plus:
| Field | Description |
|---|---|
WN | Week number |
signal_health_e1b | E1B signal health status |
data_validity_status_e1b | Data validity status |
broadcast_group_delay_e1_e5a | E1-E5a group delay |
broadcast_group_delay_e1_e5b | E1-E5b group delay |
GPS L1C-D Data
CNAV-2 clock-and-ephemeris data plus the subframe-3 page payloads — see GPSL1C_DData for the full field list:
| Field | Description |
|---|---|
toi, ITOW, WN | Time of interval, interval time of week, week number |
t_0e, ΔA, e, M_0, ω, Ω_0, i_0, … | Clock and ephemeris (CED) parameters |
α0..α3, β0..β3 | Klobuchar ionospheric coefficients (subframe-3 page 1) |
A0_UTC, Δt_LS, … | UTC parameters (page 1) |
A0_GGTO, t_GGTO, … | GPS/GNSS time offset and EOP (page 2) |
reduced_almanacs, midi_almanacs | Per-SV almanac dictionaries (pages 3/4) |
differential_corrections | Per-SV differential corrections (page 5) |
text_message | Broadcast text (page 6) |