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
8

In 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)")
end

Galileo 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)
10

GPS 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)
10

GPS 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)
10

GPS 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)
10

BeiDou 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)
10

The 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)
10

State 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
1

Checking 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
false

Data Fields

GPS L1 Data

After successful decoding, state.data contains:

FieldDescription
TOWTime of Week (seconds)
trans_weekTransmission week number
svhealthSatellite health status
t_0e, t_0cReference times for ephemeris and clock
eEccentricity
sqrt_ASquare root of semi-major axis
M_0Mean anomaly at reference time
Ω_0, ωLongitude of ascending node, argument of perigee
i_0, i_dotInclination and rate
Δn, Ω_dotMean motion difference, rate of right ascension
C_rs, C_rc, C_us, C_uc, C_is, C_icHarmonic correction terms
a_f0, a_f1, a_f2Clock correction coefficients
T_GDGroup delay differential

Galileo E1B Data

Similar ephemeris and clock parameters are available for Galileo, plus:

FieldDescription
WNWeek number
signal_health_e1bE1B signal health status
data_validity_status_e1bData validity status
broadcast_group_delay_e1_e5aE1-E5a group delay
broadcast_group_delay_e1_e5bE1-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:

FieldDescription
toi, ITOW, WNTime 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..β3Klobuchar 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_almanacsPer-SV almanac dictionaries (pages 3/4)
differential_correctionsPer-SV differential corrections (page 5)
text_messageBroadcast text (page 6)