From 81f4c8846bbb63d8bf7d8705e2ce83d614582ce1 Mon Sep 17 00:00:00 2001 From: kalmancito Date: Thu, 30 Jul 2026 17:40:43 +0200 Subject: [PATCH 1/4] Add SBAS support --- AUTHORS | 1 + .../SBAS/gnss-sdr_SBAS_EGNOS_rx.conf | 141 ++++++++++++++ src/algorithms/PVT/libs/rinex_printer.cc | 2 + .../acquisition/adapters/CMakeLists.txt | 2 + .../adapters/pcps_acquisition_adapter.cc | 7 + .../adapters/sbas_l1_pcps_acquisition.cc | 30 +++ .../adapters/sbas_l1_pcps_acquisition.h | 53 ++++++ src/algorithms/libs/CMakeLists.txt | 2 + src/algorithms/libs/sbas_signal_replica.cc | 71 +++++++ src/algorithms/libs/sbas_signal_replica.h | 55 ++++++ .../adapters/sbas_l1_telemetry_decoder.cc | 35 ++++ .../sbas_l1_telemetry_decoder_gs.cc | 175 ++++++++++++++++-- .../sbas_l1_telemetry_decoder_gs.h | 15 +- .../tracking/adapters/CMakeLists.txt | 2 + .../adapters/dll_pll_tracking_adapter.cc | 3 + .../adapters/sbas_l1_dll_pll_tracking.cc | 35 ++++ .../adapters/sbas_l1_dll_pll_tracking.h | 62 +++++++ .../gnuradio_blocks/dll_pll_veml_tracking.cc | 47 ++++- src/core/receiver/gnss_block_factory.cc | 15 +- src/core/receiver/gnss_flowgraph.cc | 4 +- src/core/system_parameters/SBAS_L1.h | 68 +++++++ src/core/system_parameters/signal_flag.h | 3 +- .../arithmetic/code_generation_test.cc | 89 +++++++++ 23 files changed, 890 insertions(+), 27 deletions(-) create mode 100644 conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf create mode 100644 src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.cc create mode 100644 src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.h create mode 100644 src/algorithms/libs/sbas_signal_replica.cc create mode 100644 src/algorithms/libs/sbas_signal_replica.h create mode 100644 src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc create mode 100644 src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.cc create mode 100644 src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.h create mode 100644 src/core/system_parameters/SBAS_L1.h diff --git a/AUTHORS b/AUTHORS index ca3ccd8cf..65c0be14d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -69,6 +69,7 @@ Marc Molina marc.molina.pena@gmail.com Contributor Marc Sales marcsales92@gmail.com Contributor Marcus Alagar mvala079@gmail.com Contributor Mathieu Favreau favreau.mathieu@hotmail.com Contributor +Miguel Gómez López mgomezl@ing.uc3m.es Contributor Minhaj Uddin Ahmad mahmad12@crimson.ua.edu Contributor Pedro Pereira pereirapedrocp@gmail.com Contributor Piyush Gupta piyush04111999@gmail.com Contributor diff --git a/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf b/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf new file mode 100644 index 000000000..4db50f24a --- /dev/null +++ b/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf @@ -0,0 +1,141 @@ +; GNSS-SDR configuration for SBAS / EGNOS L1 signal reception +; Reference: RTCA DO-229E, Appendix A +; +; SPDX-License-Identifier: GPL-3.0-or-later +; SPDX-FileCopyrightText: (C) 2010-2026 (see AUTHORS file for a list of contributors) +; +; ============================= SBAS / EGNOS notes ============================= +; +; PRN | Satellite | Service +; ------+-------------------+------------------ +; 120 | INMARSAT 3F2 AOR-E| EGNOS (Europe) +; 121 | INMARSAT 3F4 IND | EGNOS (test/spare) +; 123 | INMARSAT 4F3 | EGNOS +; 124 | Artemis | EGNOS (Europe) +; 126 | INMARSAT 3F5 IOR-W| EGNOS (Europe) +; 136 | Galaxy 15 / spare | EGNOS (test) +; 138 | ANIK F1R | WAAS (North America) +; +; Signal parameters: +; - Frequency: 1575.42 MHz (same as GPS L1) +; - Code rate: 1.023 Mcps, 1023-chip code, 1 ms period +; - Data rate: 250 bps raw, 500 sps after rate-1/2 FEC (K=7) +; - Symbol period: 2 ms ← max coherent integration: 2 code periods +; - Low Doppler: GEO satellites have < ±100 Hz Doppler for a static receiver; +; doppler_max=1000 is generous, 500 often sufficient +; - No pilot: All SBAS signal power is in the data channel +; +; ================== Adjust the fields marked with "<--" ==================== + +[GNSS-SDR] + +;######### GLOBAL OPTIONS ######################################################## +GNSS-SDR.internal_fs_sps=2048000 ; matches capture sampling frequency + + +;######### SIGNAL SOURCE ######################################################### +SignalSource.implementation=File_Signal_Source +SignalSource.filename=/home/victoria/SBAS_test/noeleec_maranhosa_SBAS.dat ; recorded on 1575.42 MHz +SignalSource.item_type=gr_complex +SignalSource.sampling_frequency=2048000 +SignalSource.samples=0 ; 0 = process entire file (~389 s) +SignalSource.repeat=false +SignalSource.dump=false + + +;######### SIGNAL CONDITIONER ##################################################### +SignalConditioner.implementation=Signal_Conditioner +DataTypeAdapter.implementation=Pass_Through +InputFilter.implementation=Pass_Through +InputFilter.item_type=gr_complex +InputFilter.output_item_type=gr_complex +InputFilter.taps_item_type=float +InputFilter.number_of_taps=5 +InputFilter.number_of_bands=2 +InputFilter.band1_begin=0.0 +InputFilter.band1_end=0.70 +InputFilter.band2_begin=0.80 +InputFilter.band2_end=1.0 +InputFilter.ampl1_begin=1.0 +InputFilter.ampl1_end=1.0 +InputFilter.ampl2_begin=0.0 +InputFilter.ampl2_end=0.0 +InputFilter.band1_error=1.0 +InputFilter.band2_error=1.0 +InputFilter.filter_type=bandpass +InputFilter.grid_density=16 +InputFilter.sampling_frequency=2048000 +InputFilter.IF=0 ; baseband — captured directly on 1575.42 MHz +Resampler.implementation=Pass_Through; +Resampler.sample_freq_in=2048000 +Resampler.sample_freq_out=2048000 +Resampler.item_type=gr_complex + + +;######### CHANNELS ############################################################## +; Signal code "S1" identifies the SBAS L1 channel type. +; Assign the SBAS PRN numbers you want to track (PRN 120–138). +Channel.signal=S1 +Channels.in_acquisition=3 +Channels_S1.count=3 ; satellites present in this recording + +; Pre-assign the PRNs present in this recording so acquisition starts immediately. +Channel0.satellite=121 ; INMARSAT-3F4 IND (EGNOS test) +Channel1.satellite=136 ; Galaxy 15 / spare (EGNOS test) +Channel2.satellite=123 ; INMARSAT-4F3 (unhealthy but trackable) + + +;######### ACQUISITION ########################################################### +; GEO satellites are quasi-stationary: valid Doppler range is very narrow. +; A doppler_max of 500–1000 Hz is typically more than sufficient. +; Lower values speed up acquisition significantly. +Acquisition_S1.implementation=SBAS_L1_PCPS_Acquisition +Acquisition_S1.item_type=gr_complex +Acquisition_S1.pfa=0.01 ; probability of false alarm – adapts to noise floor +Acquisition_S1.doppler_max=5000 ; [Hz] – GEO Doppler is < ±100 Hz for static RX +Acquisition_S1.doppler_step=250 ; [Hz] – fine step for GEO low-Doppler satellites +Acquisition_S1.dump=false +Acquisition_S1.dump_filename=/home/victoria/SBAS_test/process/sbas_acquisition + + +;######### TRACKING ############################################################## +; extend_correlation_symbols: maximum 2 (each encoded symbol spans 2 ms). +; Setting this to 2 doubles coherent integration time for improved tracking +; sensitivity, at the cost of requiring symbol-edge alignment. +Tracking_S1.implementation=SBAS_L1_DLL_PLL_Tracking +Tracking_S1.item_type=gr_complex +;Tracking_S1.early_late_space_chips=0.5 +Tracking_S1.pll_bw_hz=25.0 ; [Hz] – narrow BW for low-dynamics GEO +Tracking_S1.dll_bw_hz=1 ; [Hz] – very stable for GEO +Tracking_S1.extend_correlation_symbols=1 ; 1ms per output; SBAS telemetry decoder handles FEC/bit-sync +Tracking_S1.track_pilot=false ; SBAS has no pilot component +Tracking_S1.dump=true +Tracking_S1.dump_filename=/home/victoria/SBAS_test/process/sbas_tracking_ch_ + + +;######### TELEMETRY DECODER ##################################################### +; The SBAS telemetry decoder implements Viterbi decoding (rate-1/2, K=7), +; CRC-24Q verification, and RTCA DO-229 message framing. +TelemetryDecoder_S1.implementation=SBAS_L1_Telemetry_Decoder +TelemetryDecoder_S1.dump=true +TelemetryDecoder_S1.dump_filename=/home/victoria/SBAS_test/process/sbas_telemetry.dat + + +;######### OBSERVABLES ########################################################### +Observables.implementation=Hybrid_Observables +Observables.dump=true +Observables.dump_filename=/home/victoria/SBAS_test/process/sbas_observables.dat + + +;######### PVT ################################################################### +; Note: This phase does not apply SBAS DGPS/integrity corrections in the PVT +; solver. SBAS is used here as a ranging source only. +PVT.implementation=RTKLIB_PVT +PVT.positioning_mode=Single +PVT.output_rate_ms=100 +PVT.display_rate_ms=500 +PVT.trop_model=Saastamoinen +PVT.flag_rtcm_server=false +PVT.rinex_version=3 +PVT.dump=true +PVT.dump_filename=/home/victoria/SBAS_test/process/sbas_pvt.dat diff --git a/src/algorithms/PVT/libs/rinex_printer.cc b/src/algorithms/PVT/libs/rinex_printer.cc index f49f912c4..222aa13a5 100644 --- a/src/algorithms/PVT/libs/rinex_printer.cc +++ b/src/algorithms/PVT/libs/rinex_printer.cc @@ -78,6 +78,8 @@ std::string signal_flag_to_string(signal_flag flag) { case GPS_1C: return "1C"; + case SBAS_S1: + return "S1"; case GPS_2S: return "2S"; case GPS_L5: diff --git a/src/algorithms/acquisition/adapters/CMakeLists.txt b/src/algorithms/acquisition/adapters/CMakeLists.txt index 047a68a0f..1e3a30c7c 100644 --- a/src/algorithms/acquisition/adapters/CMakeLists.txt +++ b/src/algorithms/acquisition/adapters/CMakeLists.txt @@ -8,11 +8,13 @@ set(ACQ_ADAPTER_SOURCES pcps_acquisition_adapter.cc pcps_acquisition_adapter_custom.cc + sbas_l1_pcps_acquisition.cc ) set(ACQ_ADAPTER_HEADERS pcps_acquisition_adapter.h pcps_acquisition_adapter_custom.h + sbas_l1_pcps_acquisition.h ) if(ENABLE_FPGA) diff --git a/src/algorithms/acquisition/adapters/pcps_acquisition_adapter.cc b/src/algorithms/acquisition/adapters/pcps_acquisition_adapter.cc index 4aa99d0ff..034e9ae70 100644 --- a/src/algorithms/acquisition/adapters/pcps_acquisition_adapter.cc +++ b/src/algorithms/acquisition/adapters/pcps_acquisition_adapter.cc @@ -27,6 +27,7 @@ #include "Galileo_E5a.h" #include "Galileo_E5b.h" #include "Galileo_E6.h" +#include "SBAS_L1.h" #include "acq_conf.h" #include "beidou_b1i_signal_replica.h" #include "beidou_b3i_signal_replica.h" @@ -42,6 +43,7 @@ #include "gps_sdr_signal_replica.h" #include "qzss.h" #include "qzss_signal_replica.h" +#include "sbas_signal_replica.h" #include "signal_flag.h" #if USE_GLOG_AND_GFLAGS @@ -100,6 +102,8 @@ signal_info get_signal_info(signal_flag sig_flag) return {QZSS_L1_CHIP_RATE, QZSS_L1_OPT_ACQ_FS_SPS, QZSS_L1_CODE_LENGTH, QZSS_L1_PERIOD_MS}; case QZS_J5: return {QZSS_L5_CHIP_RATE, QZSS_L5_OPT_ACQ_FS_SPS, QZSS_L5_CODE_LENGTH, QZSS_L5I_PERIOD_MS}; + case SBAS_S1: + return {SBAS_L1_CODE_RATE_CPS, SBAS_L1_OPT_ACQ_FS_SPS, SBAS_L1_CODE_LENGTH_CHIPS, SBAS_L1_CODE_PERIOD_MS}; default: break; } @@ -180,6 +184,9 @@ void code_gen_complex_sampled(signal_flag sig_flag, const Acq_Conf& conf, const case QZS_J5: qzss_l5i_code_gen_complex_sampled(dest, gnss_synchro.PRN, sampling_freq); break; + case SBAS_S1: + sbas_l1_code_gen_complex_sampled(dest, gnss_synchro.PRN, sampling_freq); + break; default: break; } diff --git a/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.cc b/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.cc new file mode 100644 index 000000000..cf801827e --- /dev/null +++ b/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.cc @@ -0,0 +1,30 @@ +/*! + * \file sbas_l1_pcps_acquisition.cc + * \brief Adapts a PCPS acquisition block to an AcquisitionInterface for + * SBAS L1 signals + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "sbas_l1_pcps_acquisition.h" + + +SbasL1PcpsAcquisition::SbasL1PcpsAcquisition( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams) + : PcpsAcquisitionAdapter(configuration, role, "SBAS_L1_PCPS_Acquisition", in_streams, out_streams, SBAS_S1) +{ +} diff --git a/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.h b/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.h new file mode 100644 index 000000000..3c432a021 --- /dev/null +++ b/src/algorithms/acquisition/adapters/sbas_l1_pcps_acquisition.h @@ -0,0 +1,53 @@ +/*! + * \file sbas_l1_pcps_acquisition.h + * \brief Adapts a PCPS acquisition block to an AcquisitionInterface for + * SBAS L1 signals + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_SBAS_L1_PCPS_ACQUISITION_H +#define GNSS_SDR_SBAS_L1_PCPS_ACQUISITION_H + +#include "pcps_acquisition_adapter.h" +#include + +class ConfigurationInterface; + +/** \addtogroup Acquisition + * \{ */ +/** \addtogroup Acq_adapters + * \{ */ + +/*! + * \brief This class adapts a PCPS acquisition block to an AcquisitionInterface + * for SBAS L1 signals (PRN 120–138, same chip rate and code length as GPS L1 C/A). + */ +class SbasL1PcpsAcquisition : public PcpsAcquisitionAdapter +{ +public: + SbasL1PcpsAcquisition( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams); + + ~SbasL1PcpsAcquisition() = default; +}; + + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_SBAS_L1_PCPS_ACQUISITION_H diff --git a/src/algorithms/libs/CMakeLists.txt b/src/algorithms/libs/CMakeLists.txt index dc564821a..054eab727 100644 --- a/src/algorithms/libs/CMakeLists.txt +++ b/src/algorithms/libs/CMakeLists.txt @@ -33,6 +33,7 @@ set(GNSS_SPLIBS_SOURCES short_x2_to_cshort.cc gnss_sdr_string_literals.cc qzss_signal_replica.cc + sbas_signal_replica.cc ) set(GNSS_SPLIBS_HEADERS @@ -68,6 +69,7 @@ set(GNSS_SPLIBS_HEADERS gnss_time.h matlab_writter_helper.h qzss_signal_replica.h + sbas_signal_replica.h ) set(GNSS_SPLIBS_HEADERS ${GNSS_SPLIBS_HEADERS} diff --git a/src/algorithms/libs/sbas_signal_replica.cc b/src/algorithms/libs/sbas_signal_replica.cc new file mode 100644 index 000000000..8d788a7c6 --- /dev/null +++ b/src/algorithms/libs/sbas_signal_replica.cc @@ -0,0 +1,71 @@ +/*! + * \file sbas_signal_replica.cc + * \brief This file implements signal generators for SBAS L1 signals + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * SBAS PRN codes (120–138) are generated by the GPS L1 C/A code generator + * using SBAS-specific G2 initial state delays (index offset -88 into the + * delays table, covering indices 31..50 for PRN 120..138). + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "sbas_signal_replica.h" +#include "gps_sdr_signal_replica.h" +#include +#include + + +void sbas_l1_code_gen_complex_sampled(own::span> dest, uint32_t prn, int32_t sampling_freq) +{ + constexpr int32_t code_chip_rate = 1023000; + constexpr int32_t code_length = 1023; + + const float ts = 1.0F / static_cast(sampling_freq); + const float tc = 1.0F / static_cast(code_chip_rate); + + std::array, code_length> code_aux{}; + sbas_l1_code_gen_complex(code_aux, prn, 0U); + + const auto samples_per_code = static_cast(dest.size()); + for (int32_t i = 0; i < samples_per_code; i++) + { + const auto idx = static_cast(std::floor(ts * static_cast(i) / tc)); + if (i == samples_per_code - 1) + { + dest[i] = code_aux[code_length - 1]; + } + else + { + dest[i] = code_aux[idx]; + } + } +} + + +void sbas_l1_code_gen_float(own::span dest, uint32_t prn) +{ + gps_l1_ca_code_gen_float(dest, static_cast(prn), 0U); +} + + +void sbas_l1_code_gen_complex(own::span> dest, uint32_t prn, uint32_t chip_shift) +{ + constexpr uint32_t code_length = 1023; + std::array ca_code_int{}; + gps_l1_ca_code_gen_int(ca_code_int, static_cast(prn), chip_shift); + for (uint32_t ii = 0; ii < code_length; ++ii) + { + dest[ii] = {static_cast(ca_code_int[ii]), 0.0F}; + } +} diff --git a/src/algorithms/libs/sbas_signal_replica.h b/src/algorithms/libs/sbas_signal_replica.h new file mode 100644 index 000000000..6eea88688 --- /dev/null +++ b/src/algorithms/libs/sbas_signal_replica.h @@ -0,0 +1,55 @@ +/*! + * \file sbas_signal_replica.h + * \brief This file implements signal generators for SBAS L1 signals + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * SBAS PRN codes (120–138) share the same Gold-code generator as GPS L1 C/A + * but use SBAS-specific G2 initial state delays. The underlying code generator + * already handles SBAS PRNs; these wrappers provide a clean SBAS-specific API. + * + * Reference: RTCA DO-229E, Appendix A + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_SBAS_SIGNAL_REPLICA_H +#define GNSS_SDR_SBAS_SIGNAL_REPLICA_H + +#include +#include +#if HAS_STD_SPAN +#include +namespace own = std; +#else +#include +namespace own = gsl_lite; +#endif + +/** \addtogroup Algorithms_Library + * \{ */ +/** \addtogroup Algorithm_libs algorithms_libs + * \{ */ + +//! Generates complex SBAS L1 C/A code for the desired SV PRN, sampled at sampling_freq [Hz] +void sbas_l1_code_gen_complex_sampled(own::span> dest, uint32_t prn, int32_t sampling_freq); + +//! Generates real SBAS L1 C/A code for the desired SV PRN (chip_shift=0) +void sbas_l1_code_gen_float(own::span dest, uint32_t prn); + +//! Generates complex SBAS L1 C/A code for the desired SV PRN with a given chip_shift +void sbas_l1_code_gen_complex(own::span> dest, uint32_t prn, uint32_t chip_shift); + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_SBAS_SIGNAL_REPLICA_H diff --git a/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc b/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc new file mode 100644 index 000000000..7bd57dc90 --- /dev/null +++ b/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc @@ -0,0 +1,35 @@ +/*! + * \file sbas_l1_telemetry_decoder.cc + * \brief Implementation of an adapter of a SBAS telemetry data decoder block + * to a TelemetryDecoderInterface + * \author Daniel Fehr 2013. daniel.co(at)bluewin.ch + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "sbas_l1_telemetry_decoder.h" +#include "sbas_l1_telemetry_decoder_gs.h" + +SbasL1TelemetryDecoder::SbasL1TelemetryDecoder( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams) : TelemetryDecoderAdapterBase(configuration, + role, + in_streams, + out_streams), + dump_filename_(configuration != nullptr ? configuration->property(role + ".dump_filename", std::string("./navigation.dat")) : std::string("./navigation.dat")), + dump_(configuration != nullptr ? configuration->property(role + ".dump", false) : false) +{ + InitializeDecoder(sbas_l1_make_telemetry_decoder_gs(dump_, dump_filename_)); +} diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc index 5e3521168..8617e0f2b 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc @@ -2,13 +2,15 @@ * \file sbas_l1_telemetry_decoder_gs.cc * \brief Implementation of a SBAS telemetry data decoder block * \author Daniel Fehr 2013. daniel.co(at)bluewin.ch + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com * * ----------------------------------------------------------------------------- * * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. * This file is part of GNSS-SDR. * - * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) * SPDX-License-Identifier: GPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -23,6 +25,9 @@ #include // for abs #include // for exception #include // for operator<<, setw +#include // for std::cout +#include // for std::string +#include // for std::move #if USE_GLOG_AND_GFLAGS #include @@ -37,25 +42,35 @@ #define LMORE 5 // -sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs(bool dump) +sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs( + bool dump, + std::string dump_filename) { - return sbas_l1_telemetry_decoder_gs_sptr(new sbas_l1_telemetry_decoder_gs(dump)); + return sbas_l1_telemetry_decoder_gs_sptr(new sbas_l1_telemetry_decoder_gs(dump, std::move(dump_filename))); } -sbas_l1_telemetry_decoder_gs::sbas_l1_telemetry_decoder_gs(bool dump) - : telemetry_impl_interface("sbas_l1_telemetry_decoder_gs", - gr::io_signature::make(1, 1, sizeof(Gnss_Synchro)), - gr::io_signature::make(1, 1, sizeof(Gnss_Synchro))), - d_dump(dump), - d_channel(0), - d_block_size(D_SAMPLES_PER_SYMBOL * D_SYMBOLS_PER_BIT * D_BLOCK_SIZE_IN_BITS) +sbas_l1_telemetry_decoder_gs::sbas_l1_telemetry_decoder_gs( + bool dump, + std::string dump_filename) : telemetry_impl_interface("sbas_l1_telemetry_decoder_gs", + gr::io_signature::make(1, 1, sizeof(Gnss_Synchro)), + gr::io_signature::make(1, 1, sizeof(Gnss_Synchro))), + d_dump(dump), + d_channel(0), + d_block_size(D_SAMPLES_PER_SYMBOL * D_SYMBOLS_PER_BIT * D_BLOCK_SIZE_IN_BITS) { configure_basic_outputs(); // initialize internal vars LOG(INFO) << "SBAS L1 TELEMETRY PROCESSING: satellite " << d_satellite; set_output_multiple(1); + + if (d_dump && !dump_filename.empty()) + { + // Store the stem; actual EMS file is opened lazily in general_work() + // after set_satellite() has been called and d_satellite PRN is valid. + d_dump_filename = std::move(dump_filename); + } } @@ -72,6 +87,10 @@ sbas_l1_telemetry_decoder_gs::~sbas_l1_telemetry_decoder_gs() LOG(WARNING) << "Exception in destructor closing the dump file " << ex.what(); } } + if (d_ems_file.is_open()) + { + d_ems_file.close(); + } } @@ -446,17 +465,135 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( << " relative_preamble_start=" << valid_msg.first << " message_sample_offset=" << message_sample_offset << ")"; - // Sbas_Raw_Msg sbas_raw_msg(message_sample_stamp, this->d_satellite.get_PRN(), it->second); - // sbas_raw_msgs.push_back(sbas_raw_msg); + // extract message type: bits 8-13 of 250-bit message (top 6 bits of byte[1]) + const int msg_type = (valid_msg.second.size() > 1) + ? ((static_cast(valid_msg.second[1]) >> 2) & 0x3F) + : -1; + VLOG(EVENT) << "SBAS PRN " << d_satellite.get_PRN() + << " MT" << msg_type + << " at t=" << std::fixed << std::setprecision(3) + << message_sample_stamp << " s"; + // Lazy-open EMS file on first decoded message so PRN is known + if (d_dump && !d_dump_filename.empty() && !d_ems_file.is_open()) + { + std::string ems_fn = d_dump_filename; + const std::string dat_ext(".dat"); + if (ems_fn.size() > dat_ext.size() && + ems_fn.compare(ems_fn.size() - dat_ext.size(), dat_ext.size(), dat_ext) == 0) + { + ems_fn = ems_fn.substr(0, ems_fn.size() - dat_ext.size()); + } + ems_fn += "_PRN" + std::to_string(d_satellite.get_PRN()) + ".ems"; + d_ems_file.open(ems_fn, std::ios::out | std::ios::app); + if (d_ems_file.is_open()) + { + // Write header only if file is new (empty) + d_ems_file.seekp(0, std::ios::end); + if (d_ems_file.tellp() == 0) + { + d_ems_file << "; SBAS/EGNOS Message Service (EMS) file\n"; + d_ems_file << "; Generated by GNSS-SDR v0.0.20\n"; + d_ems_file << "; Satellite: SBAS PRN " << d_satellite.get_PRN() << "\n"; + d_ems_file << "; TOW field: receiver time in seconds from recording start\n"; + d_ems_file << "; GPS week = 0 (no absolute GPS fix in this output)\n"; + d_ems_file << "; Msg bytes: 32 bytes (250-bit SBAS frame zero-padded to 256 bits)\n"; + d_ems_file << "; Layout: preamble(8b) type(6b) data(212b) CRC-24Q(24b) pad(6b)\n"; + d_ems_file << "; Format: : <64_hex_chars>\n"; + d_ems_file << ";\n"; + } + LOG(INFO) << "SBAS EMS file opened: " << ems_fn; + } + else + { + LOG(WARNING) << "Could not open SBAS EMS file: " << ems_fn; + } + } + if (d_ems_file.is_open()) + { + d_ems_file << std::dec << std::setfill(' ') + << std::setw(4) << 0 << " " + << std::setw(10) << std::fixed << std::setprecision(3) + << message_sample_stamp << " " + << std::setw(3) << d_satellite.get_PRN() << " " + << std::setw(2) << msg_type << " : "; + for (const auto byte : valid_msg.second) + { + d_ems_file << std::setw(2) << std::setfill('0') << std::hex + << static_cast(byte); + } + d_ems_file << std::dec << std::setfill(' ') << '\n'; + d_ems_file.flush(); + } + // Sbas_Raw_Msg sbas_raw_msg(message_sample_stamp, this->d_satellite.get_PRN(), it->second); + // sbas_raw_msgs.push_back(sbas_raw_msg); +#if __cplusplus == 201103L + const int default_precision = std::cout.precision(); +#else + const auto default_precision{std::cout.precision()}; +#endif + std::cout << "New SBAS L1 NAV message received in channel " << d_channel + << ": MT" << msg_type << " from satellite " << d_satellite + << " with CN0=" << std::setprecision(2) << current_symbol.CN0_dB_hz + << std::setprecision(default_precision) << " dB-Hz" << std::endl; } - // parse messages - // and send them to the SBAS raw message queue - // for(std::vector::iterator it = sbas_raw_msgs.begin(); it != sbas_raw_msgs.end(); it++) - // { - // std::cout << "SBAS message type " << it->get_msg_type() << " from PRN" << it->get_prn() << " received\n"; - // sbas_telemetry_data.update(*it); - // } + // TODO: parse per-message-type content and apply SBAS corrections + // ----------------------------------------------------------------------- + // SBAS L1 message parsing status (RTCA DO-229E / ICAO SARPs Appendix B) + // ----------------------------------------------------------------------- + // SIGNAL / TRANSPORT LAYER — implemented: + // [x] Viterbi FEC decoding (rate-1/2, K=7 ) + // [x] Three-preamble detection (0x53 / 0x9A / 0xC6) + // [x] CRC-24Q verification + // [x] 6-bit message type extraction + // [x] Raw EMS file output (32 bytes / message) + // + // NAVIGATION DATA LAYER — NOT yet implemented (all content is written to + // the EMS file as raw bytes; no correction data is used by the receiver): + // + // Satellite information messages (DO-229E §A.4.4): + // [ ] MT1 — PRN mask assignments (up to 51 of 210 GNSS/GEO PRNs) + // [ ] MT2–5 — Fast corrections + UDREI for satellites in mask + // [ ] MT6 — Integrity information (UDREI for all 51 mask slots) + // [ ] MT7 — Fast correction degradation factors (aij table) + // [ ] MT9 — GEO navigation message (ECEF pos/vel, URA, t0) + // [ ] MT17 — GEO satellite almanacs (health/status + rough position) + // [ ] MT24 — Mixed fast corrections / long-term corrections + // [ ] MT25 — Long-term satellite error corrections (orbit + clock) + // [ ] MT28 — Clock–Ephemeris covariance matrix (optional) + // + // Ionospheric messages (DO-229E §A.4.4.9–10): + // [ ] MT18 — Ionospheric grid point (IGP) masks (per band 0–10) + // [ ] MT26 — Ionospheric grid delays (GIVD) + error bounds (GIVEI) + // + // Ancillary messages: + // [ ] MT0 — Don't Use / test-mode flag (safety de-selection) + // [ ] MT10 — Degradation parameters (σ²_flt, σ²_iono for PA ops) + // [ ] MT12 — SBAS Network time / UTC offset (GPS-week + SOW) + // [ ] MT27 — Service message / δUDRE regional factors (optional) + // [ ] MT62 — Internal test message (optional, not used by EGNOS) + // [ ] MT63 — Null message (optional, not used by EGNOS) + // + // SBAS L5 / DFMC messages (DO-229F, dual-freq multi-constellation): + // [ ] MT31 — Satellite Mask (up to 92 SV slots) + // [ ] MT32 — Clock-Ephemeris corrections + covariance matrix + // [ ] MT34–36 — Integrity messages (DFRECIs / DFREIs) + // [ ] MT37 — Degradation parameters + DFREI scale table + // [ ] MT39/40 — SBAS satellite ephemeris (Keplerian) + covariance + // [ ] MT47 — SBAS satellite almanacs (Keplerian, 2 GEOs per msg) + // + // Receiver-level corrections (depends on navigation layer above): + // [ ] Apply fast + long-term satellite corrections to pseudoranges + // [ ] Apply ionospheric grid delay corrections (MOPS §A.4.4.10) + // [ ] Enable SBAS ranging (set Flag_valid_word = true when ranging ok) + // [ ] Propagate SBAS correction quality (UDRE, GIVE) into PVT + // + // Reference: https://gssc.esa.int/navipedia/index.php/ + // The_EGNOS_SBAS_Message_Format_Explained + // ----------------------------------------------------------------------- + // (old hook kept for reference) + // for (auto it = sbas_raw_msgs.begin(); it != sbas_raw_msgs.end(); ++it) + // sbas_telemetry_data.update(*it); // clear all processed samples in the input buffer d_sample_buf.clear(); diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h index acad02fd4..5cdaa920b 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h @@ -2,13 +2,15 @@ * \file sbas_l1_telemetry_decoder_gs.h * \brief Interface of a SBAS telemetry data decoder block * \author Daniel Fehr 2013. daniel.co(at)bluewin.ch + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com * * ----------------------------------------------------------------------------- * * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. * This file is part of GNSS-SDR. * - * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) * SPDX-License-Identifier: GPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -36,7 +38,9 @@ class sbas_l1_telemetry_decoder_gs; using sbas_l1_telemetry_decoder_gs_sptr = gnss_shared_ptr; -sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs(bool dump); +sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs( + bool dump, + std::string dump_filename = std::string()); /*! * \brief This class implements a block that decodes the SBAS integrity and @@ -57,9 +61,11 @@ public: gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) override; private: - friend sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs(bool dump); + friend sbas_l1_telemetry_decoder_gs_sptr sbas_l1_make_telemetry_decoder_gs( + bool dump, + std::string dump_filename); - explicit sbas_l1_telemetry_decoder_gs(bool dump); + explicit sbas_l1_telemetry_decoder_gs(bool dump, std::string dump_filename = std::string()); void viterbi_decoder(double *page_part_symbols, int32_t *page_part_bits); void align_samples(); @@ -74,6 +80,7 @@ private: std::string d_dump_filename; std::ofstream d_dump_file; + std::ofstream d_ems_file; size_t d_block_size; //!< number of samples which are processed during one invocation of the algorithms std::vector d_sample_buf; //!< input buffer holding the samples to be processed in one block diff --git a/src/algorithms/tracking/adapters/CMakeLists.txt b/src/algorithms/tracking/adapters/CMakeLists.txt index 97ee94048..2a70f793f 100644 --- a/src/algorithms/tracking/adapters/CMakeLists.txt +++ b/src/algorithms/tracking/adapters/CMakeLists.txt @@ -32,6 +32,7 @@ set(TRACKING_ADAPTER_SOURCES gps_l1_ca_tcp_connector_tracking.cc gps_l1_ca_gaussian_tracking.cc gps_l1_ca_kf_tracking.cc + sbas_l1_dll_pll_tracking.cc ${OPT_TRACKING_ADAPTERS_SOURCES} ) @@ -41,6 +42,7 @@ set(TRACKING_ADAPTER_HEADERS gps_l1_ca_tcp_connector_tracking.h gps_l1_ca_gaussian_tracking.h gps_l1_ca_kf_tracking.h + sbas_l1_dll_pll_tracking.h ${OPT_TRACKING_ADAPTERS_HEADERS} ) diff --git a/src/algorithms/tracking/adapters/dll_pll_tracking_adapter.cc b/src/algorithms/tracking/adapters/dll_pll_tracking_adapter.cc index 12e40bf69..96b492807 100644 --- a/src/algorithms/tracking/adapters/dll_pll_tracking_adapter.cc +++ b/src/algorithms/tracking/adapters/dll_pll_tracking_adapter.cc @@ -25,6 +25,7 @@ #include "Galileo_E5a.h" #include "Galileo_E5b.h" #include "Galileo_E6.h" +#include "SBAS_L1.h" #include "configuration_interface.h" #include "display.h" #include "qzss.h" @@ -80,6 +81,8 @@ signal_info get_signal_info(signal_flag sig_flag) return {'J', {'J', '1', '\0'}, "QZSS L1 C/A", QZSS_L1_CHIP_RATE, QZSS_L1_CODE_LENGTH, 20, false, true}; case QZS_J5: return {'J', {'J', '5', '\0'}, "QZSS L5", QZSS_L5_CHIP_RATE, QZSS_L5_CODE_LENGTH, QZSS_L5I_NH_CODE_LENGTH, true, false}; + case SBAS_S1: + return {'S', {'S', '1', '\0'}, "SBAS L1", SBAS_L1_CODE_RATE_CPS, SBAS_L1_CODE_LENGTH_CHIPS, SBAS_L1_MAX_COHERENT_INTEGRATION_SYMBOLS, false, true}; default: break; } diff --git a/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.cc b/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.cc new file mode 100644 index 000000000..30eb25eee --- /dev/null +++ b/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.cc @@ -0,0 +1,35 @@ +/*! + * \file sbas_l1_dll_pll_tracking.cc + * \brief Interface of an adapter of a DLL+PLL tracking loop block + * for SBAS L1 signals to a TrackingInterface + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * Code DLL + carrier PLL according to the algorithms described in: + * K.Borre, D.M.Akos, N.Bertelsen, P.Rinder, and S.H.Jensen, + * A Software-Defined GPS and Galileo Receiver. A Single-Frequency + * Approach, Birkhauser, 2007 + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "sbas_l1_dll_pll_tracking.h" + + +SbasL1DllPllTracking::SbasL1DllPllTracking( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams) + : DllPllTrackingAdapter(configuration, role, "SBAS_L1_DLL_PLL_Tracking", in_streams, out_streams, SBAS_S1) +{ +} diff --git a/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.h b/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.h new file mode 100644 index 000000000..2aea4f40d --- /dev/null +++ b/src/algorithms/tracking/adapters/sbas_l1_dll_pll_tracking.h @@ -0,0 +1,62 @@ +/*! + * \file sbas_l1_dll_pll_tracking.h + * \brief Interface of an adapter of a DLL+PLL tracking loop block + * for SBAS L1 signals to a TrackingInterface + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * Code DLL + carrier PLL according to the algorithms described in: + * K.Borre, D.M.Akos, N.Bertelsen, P.Rinder, and S.H.Jensen, + * A Software-Defined GPS and Galileo Receiver. A Single-Frequency + * Approach, Birkhauser, 2007 + * + * SBAS-specific note: the encoded channel symbol period is 2 ms (rate-1/2 + * FEC at 500 sps), so coherent integration is limited to 2 code periods. + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_SBAS_L1_DLL_PLL_TRACKING_H +#define GNSS_SDR_SBAS_L1_DLL_PLL_TRACKING_H + +#include "dll_pll_tracking_adapter.h" +#include + +class ConfigurationInterface; + +/** \addtogroup Tracking + * Classes for GNSS signal tracking. + * \{ */ +/** \addtogroup Tracking_adapters tracking_adapters + * Wrap GNU Radio blocks for GNSS signal tracking with a TrackingInterface + * \{ */ + + +/*! + * \brief This class implements a code DLL + carrier PLL tracking loop + * block adapter for SBAS L1 signals (PRN 120–138). + */ +class SbasL1DllPllTracking : public DllPllTrackingAdapter +{ +public: + SbasL1DllPllTracking(const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams); + + ~SbasL1DllPllTracking() override = default; +}; + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_SBAS_L1_DLL_PLL_TRACKING_H diff --git a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc index bb02503b0..c12263a3e 100644 --- a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc +++ b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc @@ -33,6 +33,7 @@ #include "Galileo_E5b.h" #include "Galileo_E6.h" #include "MATH_CONSTANTS.h" +#include "SBAS_L1.h" #include "beidou_b1i_signal_replica.h" #include "beidou_b3i_signal_replica.h" #include "galileo_e1_signal_replica.h" @@ -51,6 +52,7 @@ #include "matlab_writter_helper.h" #include "qzss.h" #include "qzss_signal_replica.h" +#include "sbas_signal_replica.h" #include "tracking_discriminators.h" #include // for io_signature #include // for scoped_lock @@ -587,7 +589,46 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_) d_code_samples_per_chip = 0U; d_symbols_per_bit = 0; } - } + } // end QZSS + else if (d_trk_parameters.system == 'S') + { + d_systemName = "SBAS"; + if (d_signal_type == "S1") + { + d_signal_carrier_freq = SBAS_L1_FREQ_HZ; + d_code_period = SBAS_L1_CODE_PERIOD_S; + d_code_chip_rate = SBAS_L1_CODE_RATE_CPS; + d_correlation_length_ms = 1; + d_code_samples_per_chip = 1; + d_code_length_chips = static_cast(SBAS_L1_CODE_LENGTH_CHIPS); + // SBAS has no pilot and no secondary code. + // FEC (rate-1/2, K=7) and bit-sync are handled by sbas_l1_telemetry_decoder_gs. + d_secondary = false; + d_trk_parameters.track_pilot = false; + d_trk_parameters.slope = 1.0; + d_trk_parameters.spc = d_trk_parameters.early_late_space_chips; + d_trk_parameters.y_intercept = 1.0; + // SBAS telemetry decoder handles preamble detection and Viterbi FEC. + // Set d_secondary_code_length to a non-zero value for buffer capacity. + // d_symbols_per_bit=1: each 1 ms correlation is forwarded directly; + // disables the GPS-style histogram bit synchronizer (wrong for SBAS). + d_secondary_code_length = 20U; // placeholder (not used with d_secondary=false) + d_secondary_code_string = GPS_CA_PREAMBLE_SYMBOLS_STR; // placeholder + d_symbols_per_bit = 1; + } + else + { + LOG(WARNING) << "Invalid Signal argument when instantiating tracking blocks"; + std::cerr << "Invalid Signal argument when instantiating tracking blocks\n"; + d_correlation_length_ms = 1; + d_secondary = false; + d_signal_carrier_freq = 0.0; + d_code_period = 0.0; + d_code_length_chips = 0; + d_code_samples_per_chip = 0U; + d_symbols_per_bit = 0; + } + } // end SBAS else { LOG(WARNING) << "Invalid System argument when instantiating tracking blocks"; @@ -1053,6 +1094,10 @@ void dll_pll_veml_tracking::start_tracking() qzss_l5i_code_gen_float(d_tracking_code, d_acquisition_gnss_synchro->PRN); } } + else if (d_systemName == "SBAS" && d_signal_type == "S1") + { + sbas_l1_code_gen_float(d_tracking_code, d_acquisition_gnss_synchro->PRN); + } d_multicorrelator_cpu.set_local_code_and_taps(d_code_samples_per_chip * d_code_length_chips, d_tracking_code.data(), d_local_code_shift_chips.data()); std::fill_n(d_correlator_outs.begin(), d_n_correlator_taps, gr_complex(0.0, 0.0)); diff --git a/src/core/receiver/gnss_block_factory.cc b/src/core/receiver/gnss_block_factory.cc index 0007974f4..95a639507 100644 --- a/src/core/receiver/gnss_block_factory.cc +++ b/src/core/receiver/gnss_block_factory.cc @@ -73,6 +73,8 @@ #include "pulse_blanking_filter.h" #include "rtklib_pvt.h" #include "rtl_tcp_signal_source.h" +#include "sbas_l1_dll_pll_tracking.h" +#include "sbas_l1_pcps_acquisition.h" #include "sbas_l1_telemetry_decoder_gs.h" #include "signal_conditioner.h" #include "signal_flag.h" @@ -213,6 +215,7 @@ const auto signal_mapping = std::vector>{ {"7X", "GALILEO E5b I (I/NAV OS)"}, {"J1", "QZSS L1 C/A"}, {"J5", "QZSS L5"}, + {"S1", "SBAS L1"}, }; unsigned int get_channel_count(const ConfigurationInterface* configuration) @@ -504,6 +507,10 @@ std::unique_ptr get_acq_block( { return std::make_unique(configuration, role, implementation, in_streams, out_streams, QZS_J5); } + else if (implementation == "SBAS_L1_PCPS_Acquisition") + { + return std::make_unique(configuration, role, in_streams, out_streams); + } #if OPENCL_BLOCKS else if (implementation == "GPS_L1_CA_PCPS_OpenCl_Acquisition") { @@ -616,6 +623,10 @@ std::unique_ptr get_trk_block( { return std::make_unique(configuration, role, implementation, in_streams, out_streams, QZS_J5); } + else if (implementation == "SBAS_L1_DLL_PLL_Tracking") + { + return std::make_unique(configuration, role, in_streams, out_streams); + } #if CUDA_GPU_ACCEL else if (implementation == "GPS_L1_CA_DLL_PLL_Tracking_GPU") { @@ -695,7 +706,9 @@ std::unique_ptr get_tlm_block( } else if (implementation == "SBAS_L1_Telemetry_Decoder") { - telemetry = sbas_l1_make_telemetry_decoder_gs(configuration != nullptr ? configuration->property(role + ".dump", false) : false); + telemetry = sbas_l1_make_telemetry_decoder_gs( + configuration != nullptr ? configuration->property(role + ".dump", false) : false, + configuration != nullptr ? configuration->property(role + ".dump_filename", std::string()) : std::string()); } else if (implementation == "Galileo_E5a_Telemetry_Decoder") { diff --git a/src/core/receiver/gnss_flowgraph.cc b/src/core/receiver/gnss_flowgraph.cc index bcdee5e2c..4f69bcf00 100644 --- a/src/core/receiver/gnss_flowgraph.cc +++ b/src/core/receiver/gnss_flowgraph.cc @@ -96,6 +96,7 @@ const auto signal_mapping = std::unordered_map> available_prn_map = { {"GPS", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}}, - {"SBAS", {123, 131, 135, 136, 138}}, + {"SBAS", {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138}}, {"Galileo", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36}}, {"Glonass", {1, 2, 3, 4, 9, 10, 11, 12, 18, 19, 20, 21, 26, 27, 28}}, diff --git a/src/core/system_parameters/SBAS_L1.h b/src/core/system_parameters/SBAS_L1.h new file mode 100644 index 000000000..720f3146c --- /dev/null +++ b/src/core/system_parameters/SBAS_L1.h @@ -0,0 +1,68 @@ +/*! + * \file SBAS_L1.h + * \brief Defines system parameters for SBAS L1 signal + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com + * + * Reference: RTCA DO-229E, Minimum Operational Performance Standards for + * Global Positioning System/Wide Area Augmentation System Airborne Equipment + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_SBAS_L1_H +#define GNSS_SDR_SBAS_L1_H + +#include "gnss_frequencies.h" +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup System_Parameters + * \{ */ + +// Carrier and code frequencies +constexpr double SBAS_L1_FREQ_HZ = FREQ1; //!< SBAS L1 carrier frequency [Hz] (same as GPS L1) +constexpr double SBAS_L1_CODE_RATE_CPS = 1.023e6; //!< SBAS L1 C/A code chip rate [chips/s] +constexpr double SBAS_L1_CODE_LENGTH_CHIPS = 1023.0; //!< SBAS L1 C/A code length [chips] +constexpr double SBAS_L1_CODE_PERIOD_S = 0.001; //!< SBAS L1 C/A code period [s] +constexpr uint32_t SBAS_L1_CODE_PERIOD_MS = 1U; //!< SBAS L1 C/A code period [ms] + +// Optimum acquisition sampling frequency +constexpr uint32_t SBAS_L1_OPT_ACQ_FS_SPS = 2000000U; //!< Sampling frequency that maximises acquisition SNR [samples/s] + +// PRN range (RTCA DO-229, Table A-1) +constexpr uint32_t SBAS_L1_PRN_MIN = 120U; //!< Minimum SBAS PRN (inclusive) +constexpr uint32_t SBAS_L1_PRN_MAX = 138U; //!< Maximum SBAS PRN (inclusive) + +// Message rates and encoding +// SBAS broadcasts at 250 raw data bits/s, convolutionally encoded +// at rate 1/2, K=7, yielding a channel symbol rate of 500 symbols/s. +constexpr int32_t SBAS_L1_DATA_RATE_BPS = 250; //!< Raw data bit rate [bits/s] +constexpr int32_t SBAS_L1_CHANNEL_SYMBOL_RATE_SPS = 500; //!< Encoded channel symbol rate (rate-1/2 FEC) [symbols/s] +constexpr int32_t SBAS_L1_CHANNEL_SYMBOL_PERIOD_MS = 2; //!< Encoded channel symbol period [ms] (1/500 sps) +constexpr int32_t SBAS_L1_MSG_LENGTH_BITS = 250; //!< Information bits per 1-second message frame [bits] + +// Maximum coherent integration extension in terms of 1 ms code periods. +// Each encoded symbol spans 2 code periods (2 ms), so extending beyond 2 +// would straddle a symbol boundary and reduce coherent gain. +constexpr int32_t SBAS_L1_MAX_COHERENT_INTEGRATION_SYMBOLS = 2; + +// Preamble bytes (RTCA DO-229, Section A.4.4.2) +constexpr uint8_t SBAS_L1_PREAMBLE_1 = 0x53U; //!< First preamble byte (binary: 0101 0011) +constexpr uint8_t SBAS_L1_PREAMBLE_2 = 0x9AU; //!< Second preamble byte (binary: 1001 1010) +constexpr uint8_t SBAS_L1_PREAMBLE_3 = 0xC6U; //!< Third preamble byte (binary: 1100 0110) + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_SBAS_L1_H diff --git a/src/core/system_parameters/signal_flag.h b/src/core/system_parameters/signal_flag.h index 6f6e8fba5..519bdc6cb 100644 --- a/src/core/system_parameters/signal_flag.h +++ b/src/core/system_parameters/signal_flag.h @@ -35,7 +35,8 @@ enum signal_flag : uint32_t BDS_B1 = 0x1 << 9, BDS_B3 = 0x1 << 10, QZS_J1 = 0x1 << 11, - QZS_J5 = 0x1 << 12 + QZS_J5 = 0x1 << 12, + SBAS_S1 = 0x1 << 13 }; #endif // GNSS_SDR_SIGNAL_FLAG_H diff --git a/tests/unit-tests/arithmetic/code_generation_test.cc b/tests/unit-tests/arithmetic/code_generation_test.cc index ec656b84c..228e8f406 100644 --- a/tests/unit-tests/arithmetic/code_generation_test.cc +++ b/tests/unit-tests/arithmetic/code_generation_test.cc @@ -17,9 +17,11 @@ */ #include "Galileo_E6.h" +#include "SBAS_L1.h" #include "galileo_e6_signal_replica.h" #include "gnss_signal_replica.h" #include "gps_sdr_signal_replica.h" +#include "sbas_signal_replica.h" #include #include #include @@ -397,3 +399,90 @@ TEST(CodeGenerationTest, GalileoE6BTest) ASSERT_FLOAT_EQ(gale6b_code[i].real(), expected_output[i].real()); } } + + +// ===== SBAS L1 signal replica tests ===== + +TEST(CodeGenerationTest, SbasL1ComplexCodePrn120Test) +{ + // PRN 120 is the lowest SBAS PRN; verify code generates without error + // and has correct length. Values are ±1 (BPSK spreading). + std::array, 1023> dest{}; + const uint32_t prn = SBAS_L1_PRN_MIN; // 120 + sbas_l1_code_gen_complex(dest, prn, 0U); + + for (const auto& chip : dest) + { + ASSERT_FLOAT_EQ(std::abs(chip.imag()), 0.0F); + ASSERT_TRUE(chip.real() == 1.0F || chip.real() == -1.0F) + << "Chip value out of range for PRN " << prn; + } +} + + +TEST(CodeGenerationTest, SbasL1ComplexCodePrn138Test) +{ + // PRN 138 is the highest SBAS PRN; verify code generates without error. + std::array, 1023> dest{}; + const uint32_t prn = SBAS_L1_PRN_MAX; // 138 + sbas_l1_code_gen_complex(dest, prn, 0U); + + for (const auto& chip : dest) + { + ASSERT_FLOAT_EQ(std::abs(chip.imag()), 0.0F); + ASSERT_TRUE(chip.real() == 1.0F || chip.real() == -1.0F) + << "Chip value out of range for PRN " << prn; + } +} + + +TEST(CodeGenerationTest, SbasL1FloatCodePrn121Test) +{ + // PRN 121 — verify float variant generates 1023 chips of ±1. + std::array dest{}; + sbas_l1_code_gen_float(dest, 121U); + + for (const auto& chip : dest) + { + ASSERT_TRUE(chip == 1.0F || chip == -1.0F) + << "Float chip value out of range for PRN 121"; + } +} + + +TEST(CodeGenerationTest, SbasL1SampledCodeAutocorrelationTest) +{ + // Verify that the sampled SBAS L1 code has a sharp autocorrelation peak + // at zero lag (property of maximal-length sequences). + const int32_t fs = static_cast(SBAS_L1_OPT_ACQ_FS_SPS); // 2 Msps + const int32_t code_rate = static_cast(SBAS_L1_CODE_RATE_CPS); + const int32_t code_length = static_cast(SBAS_L1_CODE_LENGTH_CHIPS); + const int samples_per_code = static_cast(std::round( + static_cast(fs) / (static_cast(code_rate) / static_cast(code_length)))); + + std::vector> code(samples_per_code); + sbas_l1_code_gen_complex_sampled(code, 120U, fs); + + // Compute circular autocorrelation at lag 0 + float r0 = 0.0F; + for (const auto& s : code) + { + r0 += s.real() * s.real() + s.imag() * s.imag(); + } + + // Compute autocorrelation at a full-chip lag to measure the sidelobe level. + // At 2 Msps with 1.023 Mcps, each chip spans ~2 samples, so lag=2 takes + // us to an adjacent-chip offset well outside the main correlation peak. + const int chip_lag = static_cast(std::round(static_cast(fs) / static_cast(code_rate))); + float r1 = 0.0F; + for (int i = 0; i < samples_per_code; ++i) + { + const int j = (i + chip_lag) % samples_per_code; + r1 += code[i].real() * code[j].real() + code[i].imag() * code[j].imag(); + } + + // Peak at zero lag should dominate (ratio > 10) + ASSERT_GT(r0, 0.0F); + ASSERT_GT(r0 / (std::abs(r1) + 1e-6F), 10.0F) + << "Autocorrelation peak-to-sidelobe ratio too small for SBAS PRN 120"; +} From f129a4af8e2d44f56604ee17cc08b5dacb0ca85c Mon Sep 17 00:00:00 2001 From: miguekf Date: Mon, 3 Aug 2026 23:50:10 +0200 Subject: [PATCH 2/4] Address PR #1059 review: PRN whitelist, SBAS coherent integration, timestamp, reset, dead code, config - gnss_satellite.cc: accept the full supported SBAS PRN range (120-138) instead of a stale 6-PRN whitelist that rejected valid PRNs such as WAAS PRN 133. Per-PRN satellite/service names are no longer hardcoded, since that assignment changes over time and cannot be reliably kept up to date here (fixes review comments on PRN support and outdated satellite info). - SBAS_L1.h: cap SBAS_L1_MAX_COHERENT_INTEGRATION_SYMBOLS at 1 (1 ms). The telemetry decoder performs its own unsynchronized sample/symbol pairing, so extending tracking's coherent integration to 2 ms is unsafe and corrupts that pairing. - sbas_l1_telemetry_decoder_gs.{h,cc}: carry an absolute per-sample/per-bit timestamp through the sample buffer and Frame_Detector, replacing the buggy offset that anchored the message timestamp to whichever sample was current when decoding happened, which could be off by up to a full message duration. Add sbas_l1_telemetry_decoder_gs::compute_message_timestamp() as a small, independently testable helper for the residual sub-bit correction, and a matching deterministic unit test. - sbas_l1_telemetry_decoder_gs.{h,cc}: implement a real reset() that clears all decoder buffers/aligners and closes the dump file, so a channel reassigned to a different PRN never mixes messages into the previous satellite's file. - Remove the orphan sbas_l1_telemetry_decoder.cc adapter: it includes a header that no longer exists and is not part of any CMake target (the factory instantiates the GNU Radio block directly), so it silently never compiled. - Dump file: overwrite instead of append on each run, remove the hardcoded GNSS-SDR version string, and document that the .ems output is an EMS-like raw message dump, not a conformant EMS file. - conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf: drop the outdated PRN/satellite table and per-channel satellite comments, correct the coherent-integration note to 1 ms, and clarify that SBAS is not used as a ranging source in this configuration. Signed-off-by: miguekf --- .../SBAS/gnss-sdr_SBAS_EGNOS_rx.conf | 38 +++--- .../adapters/sbas_l1_telemetry_decoder.cc | 35 ------ .../sbas_l1_telemetry_decoder_gs.cc | 108 ++++++++++++------ .../sbas_l1_telemetry_decoder_gs.h | 24 +++- src/core/system_parameters/SBAS_L1.h | 8 +- src/core/system_parameters/gnss_satellite.cc | 47 +++----- tests/test_main.cc | 1 + .../sbas_l1_telemetry_decoder_test.cc | 67 +++++++++++ 8 files changed, 198 insertions(+), 130 deletions(-) delete mode 100644 src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc create mode 100644 tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc diff --git a/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf b/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf index 4db50f24a..b8bec15f3 100644 --- a/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf +++ b/conf/File_input/SBAS/gnss-sdr_SBAS_EGNOS_rx.conf @@ -6,24 +6,25 @@ ; ; ============================= SBAS / EGNOS notes ============================= ; -; PRN | Satellite | Service -; ------+-------------------+------------------ -; 120 | INMARSAT 3F2 AOR-E| EGNOS (Europe) -; 121 | INMARSAT 3F4 IND | EGNOS (test/spare) -; 123 | INMARSAT 4F3 | EGNOS -; 124 | Artemis | EGNOS (Europe) -; 126 | INMARSAT 3F5 IOR-W| EGNOS (Europe) -; 136 | Galaxy 15 / spare | EGNOS (test) -; 138 | ANIK F1R | WAAS (North America) +; Supported PRN range: 120-138 (RTCA DO-229, Table A-1). Which augmentation +; service (EGNOS, WAAS, ...) and satellite owns a given PRN changes over time +; as satellites are reassigned, so no PRN-to-satellite table is kept here; +; check the current SBAS almanac/status for the PRNs active in your recording. ; ; Signal parameters: ; - Frequency: 1575.42 MHz (same as GPS L1) ; - Code rate: 1.023 Mcps, 1023-chip code, 1 ms period ; - Data rate: 250 bps raw, 500 sps after rate-1/2 FEC (K=7) -; - Symbol period: 2 ms ← max coherent integration: 2 code periods +; - Symbol period: 2 ms; coherent integration is limited to 1 code period (1 ms) +; because the telemetry decoder performs its own, unsynchronized +; sample/symbol pairing and does not tolerate extended tracking +; integration straddling a symbol boundary. ; - Low Doppler: GEO satellites have < ±100 Hz Doppler for a static receiver; ; doppler_max=1000 is generous, 500 often sufficient ; - No pilot: All SBAS signal power is in the data channel +; - No ranging: This SBAS implementation does not produce pseudoranges; SBAS +; is used here only to decode and dump L1 NAV messages, not as +; a ranging source for the PVT solution. ; ; ================== Adjust the fields marked with "<--" ==================== @@ -80,9 +81,9 @@ Channels.in_acquisition=3 Channels_S1.count=3 ; satellites present in this recording ; Pre-assign the PRNs present in this recording so acquisition starts immediately. -Channel0.satellite=121 ; INMARSAT-3F4 IND (EGNOS test) -Channel1.satellite=136 ; Galaxy 15 / spare (EGNOS test) -Channel2.satellite=123 ; INMARSAT-4F3 (unhealthy but trackable) +Channel0.satellite=121 ; <-- set to a PRN present in your recording +Channel1.satellite=136 ; <-- set to a PRN present in your recording +Channel2.satellite=123 ; <-- set to a PRN present in your recording ;######### ACQUISITION ########################################################### @@ -99,9 +100,9 @@ Acquisition_S1.dump_filename=/home/victoria/SBAS_test/process/sbas_acquisition ;######### TRACKING ############################################################## -; extend_correlation_symbols: maximum 2 (each encoded symbol spans 2 ms). -; Setting this to 2 doubles coherent integration time for improved tracking -; sensitivity, at the cost of requiring symbol-edge alignment. +; extend_correlation_symbols is capped to 1 code period (1 ms): the telemetry +; decoder performs its own, unsynchronized sample/symbol pairing and cannot +; tolerate extended coherent integration straddling a symbol boundary. Tracking_S1.implementation=SBAS_L1_DLL_PLL_Tracking Tracking_S1.item_type=gr_complex ;Tracking_S1.early_late_space_chips=0.5 @@ -128,8 +129,9 @@ Observables.dump_filename=/home/victoria/SBAS_test/process/sbas_observables.dat ;######### PVT ################################################################### -; Note: This phase does not apply SBAS DGPS/integrity corrections in the PVT -; solver. SBAS is used here as a ranging source only. +; Note: SBAS is not used as a ranging source in this configuration. The SBAS +; telemetry decoder never marks its output as valid for pseudorange computation, +; and no SBAS DGPS/integrity corrections are applied to the PVT solution. PVT.implementation=RTKLIB_PVT PVT.positioning_mode=Single PVT.output_rate_ms=100 diff --git a/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc b/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc deleted file mode 100644 index 7bd57dc90..000000000 --- a/src/algorithms/telemetry_decoder/adapters/sbas_l1_telemetry_decoder.cc +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * \file sbas_l1_telemetry_decoder.cc - * \brief Implementation of an adapter of a SBAS telemetry data decoder block - * to a TelemetryDecoderInterface - * \author Daniel Fehr 2013. daniel.co(at)bluewin.ch - * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es - * \author Víctor Castillo Agüero, 2026. victorcastilloaguero(at)gmail.com - * - * ----------------------------------------------------------------------------- - * - * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. - * This file is part of GNSS-SDR. - * - * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) - * SPDX-License-Identifier: GPL-3.0-or-later - * - * ----------------------------------------------------------------------------- - */ - -#include "sbas_l1_telemetry_decoder.h" -#include "sbas_l1_telemetry_decoder_gs.h" - -SbasL1TelemetryDecoder::SbasL1TelemetryDecoder( - const ConfigurationInterface* configuration, - const std::string& role, - unsigned int in_streams, - unsigned int out_streams) : TelemetryDecoderAdapterBase(configuration, - role, - in_streams, - out_streams), - dump_filename_(configuration != nullptr ? configuration->property(role + ".dump_filename", std::string("./navigation.dat")) : std::string("./navigation.dat")), - dump_(configuration != nullptr ? configuration->property(role + ".dump", false) : false) -{ - InitializeDecoder(sbas_l1_make_telemetry_decoder_gs(dump_, dump_filename_)); -} diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc index 8617e0f2b..54dd303f5 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc @@ -17,6 +17,7 @@ */ #include "sbas_l1_telemetry_decoder_gs.h" +#include "SBAS_L1.h" #include "gnss_synchro.h" #include "viterbi_decoder_sbas.h" #include // for mp @@ -108,6 +109,31 @@ void sbas_l1_telemetry_decoder_gs::set_channel(int32_t channel) } +void sbas_l1_telemetry_decoder_gs::reset() +{ + d_sample_buf.clear(); + d_sample_stamps.clear(); + d_sample_aligner.reset(); + d_symbol_aligner_and_decoder.reset(); + d_frame_detector.reset(); + d_crc_verifier.reset(); + if (d_ems_file.is_open()) + { + d_ems_file.close(); + } +} + + +double sbas_l1_telemetry_decoder_gs::compute_message_timestamp(double first_bit_stamp_s, bool sample_aligned, bool symbol_aligned) +{ + // Residual sub-bit correction: which of the two possible sample/symbol + // alignments was chosen shifts the true message start by a few samples + // with respect to the first bit's own timestamp. + const int32_t offset_samples = (sample_aligned ? 0 : -1) + D_SAMPLES_PER_SYMBOL * (symbol_aligned ? -1 : 0); + return first_bit_stamp_s + static_cast(offset_samples) * SBAS_L1_CODE_PERIOD_S; +} + + // ### helper class for sample alignment ### sbas_l1_telemetry_decoder_gs::Sample_Aligner::Sample_Aligner() { @@ -244,10 +270,11 @@ bool sbas_l1_telemetry_decoder_gs::Symbol_Aligner_And_Decoder::get_bits(const st void sbas_l1_telemetry_decoder_gs::Frame_Detector::reset() { d_buffer.clear(); + d_bit_stamps.clear(); } -void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const std::vector &bits, std::vector>> &msg_candidates) +void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const std::vector &bits, const std::vector &bit_stamps, std::vector>> &msg_candidates) { std::stringstream ss; const uint32_t sbas_msg_length = 250; @@ -258,15 +285,15 @@ void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const st << "d_buffer.size()=" << d_buffer.size() << "\tbits.size()=" << bits.size(); ss << "copy bits "; int32_t count = 0; - // copy new bits into the working buffer - for (auto bit_it = bits.cbegin(); bit_it < bits.cend(); ++bit_it) + // copy new bits (and their absolute timestamps) into the working buffer + for (size_t i = 0; i < bits.size(); i++) { - d_buffer.push_back(*bit_it); - ss << *bit_it; + d_buffer.push_back(bits[i]); + d_bit_stamps.push_back(bit_stamps[i]); + ss << bits[i]; count++; } VLOG(SAMP_SYNC) << ss.str() << " into working buffer (" << count << " bits)"; - int32_t relative_preamble_start = 0; while (d_buffer.size() >= sbas_msg_length) { // compare with all preambles @@ -293,7 +320,8 @@ void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const st candidate_bit_it = candidate_bit_it == 0 ? 1 : 0; } } - msg_candidates.emplace_back(relative_preamble_start, candidate); + // the candidate's absolute timestamp is that of its first (preamble) bit + msg_candidates.emplace_back(d_bit_stamps.front(), candidate); ss.str(""); ss << "preamble " << preample_it - preambles.begin() << (inv_preamble_detected ? " inverted" : " normal") << " detected! candidate="; for (auto bit_it = candidate.begin(); bit_it < candidate.end(); ++bit_it) @@ -303,9 +331,9 @@ void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const st VLOG(EVENT) << ss.str(); } } - relative_preamble_start++; - // remove bit in front + // remove bit (and its timestamp) in front d_buffer.pop_front(); + d_bit_stamps.pop_front(); } } @@ -424,8 +452,11 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( // copy correlation samples into samples vector d_sample_buf.push_back(current_symbol.Prompt_I); // add new symbol to the symbol queue - // store the time stamp of the first sample in the processed sample block + // absolute reception timestamp of this sample; stored so that, once a message is + // eventually found several blocks later, its true timestamp can be recovered instead + // of approximated from whichever sample happens to be current at that later time. const double sample_stamp = static_cast(in[0].Tracking_sample_counter) / static_cast(in[0].fs); + d_sample_stamps.push_back(sample_stamp); // decode only if enough samples in buffer if (d_sample_buf.size() >= d_block_size) @@ -440,10 +471,18 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( std::vector bits; const bool symbol_alignment = d_symbol_aligner_and_decoder.get_bits(symbols, bits); + // absolute timestamp of each decoded bit: the timestamp of the first + // sample that contributed to it, taken from this block's own samples + std::vector bit_stamps(bits.size()); + for (size_t i = 0; i < bits.size(); i++) + { + bit_stamps[i] = d_sample_stamps[i * D_SAMPLES_PER_SYMBOL * D_SYMBOLS_PER_BIT]; + } + // search for preambles // and extract the corresponding message candidates std::vector msg_candidates; - d_frame_detector.get_frame_candidates(bits, msg_candidates); + d_frame_detector.get_frame_candidates(bits, bit_stamps, msg_candidates); // verify checksum // and return the valid messages @@ -455,15 +494,12 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( // std::vector sbas_raw_msgs; for (const auto &valid_msg : valid_msgs) { - const int32_t message_sample_offset = - (sample_alignment ? 0 : -1) + D_SAMPLES_PER_SYMBOL * (symbol_alignment ? -1 : 0) + D_SAMPLES_PER_SYMBOL * D_SYMBOLS_PER_BIT * valid_msg.first; - const double message_sample_stamp = sample_stamp + static_cast(message_sample_offset) / 1000.0; + // valid_msg.first is the absolute timestamp of the message's first bit + const double message_sample_stamp = compute_message_timestamp(valid_msg.first, sample_alignment, symbol_alignment); VLOG(EVENT) << "message_sample_stamp=" << message_sample_stamp - << " (sample_stamp=" << sample_stamp + << " (first_bit_stamp=" << valid_msg.first << " sample_alignment=" << sample_alignment << " symbol_alignment=" << symbol_alignment - << " relative_preamble_start=" << valid_msg.first - << " message_sample_offset=" << message_sample_offset << ")"; // extract message type: bits 8-13 of 250-bit message (top 6 bits of byte[1]) const int msg_type = (valid_msg.second.size() > 1) @@ -473,7 +509,8 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( << " MT" << msg_type << " at t=" << std::fixed << std::setprecision(3) << message_sample_stamp << " s"; - // Lazy-open EMS file on first decoded message so PRN is known + // Lazy-open message dump file on first decoded message so PRN is known. + // Dumps are overwritten (not appended) on every run. if (d_dump && !d_dump_filename.empty() && !d_ems_file.is_open()) { std::string ems_fn = d_dump_filename; @@ -484,28 +521,28 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( ems_fn = ems_fn.substr(0, ems_fn.size() - dat_ext.size()); } ems_fn += "_PRN" + std::to_string(d_satellite.get_PRN()) + ".ems"; - d_ems_file.open(ems_fn, std::ios::out | std::ios::app); + d_ems_file.open(ems_fn, std::ios::out | std::ios::trunc); if (d_ems_file.is_open()) { - // Write header only if file is new (empty) - d_ems_file.seekp(0, std::ios::end); - if (d_ems_file.tellp() == 0) - { - d_ems_file << "; SBAS/EGNOS Message Service (EMS) file\n"; - d_ems_file << "; Generated by GNSS-SDR v0.0.20\n"; - d_ems_file << "; Satellite: SBAS PRN " << d_satellite.get_PRN() << "\n"; - d_ems_file << "; TOW field: receiver time in seconds from recording start\n"; - d_ems_file << "; GPS week = 0 (no absolute GPS fix in this output)\n"; - d_ems_file << "; Msg bytes: 32 bytes (250-bit SBAS frame zero-padded to 256 bits)\n"; - d_ems_file << "; Layout: preamble(8b) type(6b) data(212b) CRC-24Q(24b) pad(6b)\n"; - d_ems_file << "; Format: : <64_hex_chars>\n"; - d_ems_file << ";\n"; - } - LOG(INFO) << "SBAS EMS file opened: " << ems_fn; + // NOTE on naming/format: this is a raw dump of decoded SBAS L1 messages + // using the same field layout as ESA's EGNOS Message Service (EMS) + // archive files (week, tow, prn, type : hex bytes). It is NOT a + // conformant EMS file: this receiver has no absolute GPS time + // reference, so "week" is always 0 and "tow" is the receiver's + // elapsed time since recording start, not the real GPS time of week. + d_ems_file << "; Raw SBAS L1 message dump (EMS-like layout, not a conformant EMS file)\n"; + d_ems_file << "; Satellite: SBAS PRN " << d_satellite.get_PRN() << "\n"; + d_ems_file << "; TOW field: receiver time in seconds from recording start\n"; + d_ems_file << "; GPS week = 0 (no absolute GPS fix in this output)\n"; + d_ems_file << "; Msg bytes: 32 bytes (250-bit SBAS frame zero-padded to 256 bits)\n"; + d_ems_file << "; Layout: preamble(8b) type(6b) data(212b) CRC-24Q(24b) pad(6b)\n"; + d_ems_file << "; Format: : <64_hex_chars>\n"; + d_ems_file << ";\n"; + LOG(INFO) << "SBAS raw message dump file opened: " << ems_fn; } else { - LOG(WARNING) << "Could not open SBAS EMS file: " << ems_fn; + LOG(WARNING) << "Could not open SBAS raw message dump file: " << ems_fn; } } if (d_ems_file.is_open()) @@ -597,6 +634,7 @@ int sbas_l1_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( // clear all processed samples in the input buffer d_sample_buf.clear(); + d_sample_stamps.clear(); } // UPDATE GNSS SYNCHRO DATA diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h index 5cdaa920b..9e335876f 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.h @@ -52,7 +52,15 @@ public: ~sbas_l1_telemetry_decoder_gs() override; void set_satellite(const Gnss_Satellite &satellite) override; //!< Set satellite PRN void set_channel(int32_t channel) override; //!< Set receiver's channel - inline void reset() override {}; + //! Clears all internal decoding state (buffers, aligners, Viterbi decoders) + //! and closes the dump file, so that a channel reassigned to a different + //! PRN never mixes messages from the previous satellite into its file. + void reset() override; + + //! Exposed for unit testing: combines an absolute per-bit reception + //! timestamp with the residual sample/symbol alignment correction to + //! obtain the final message timestamp. + static double compute_message_timestamp(double first_bit_stamp_s, bool sample_aligned, bool symbol_aligned); /*! * \brief This is where all signal processing takes place @@ -82,11 +90,13 @@ private: std::ofstream d_dump_file; std::ofstream d_ems_file; - size_t d_block_size; //!< number of samples which are processed during one invocation of the algorithms - std::vector d_sample_buf; //!< input buffer holding the samples to be processed in one block + size_t d_block_size; //!< number of samples which are processed during one invocation of the algorithms + std::vector d_sample_buf; //!< input buffer holding the samples to be processed in one block + std::vector d_sample_stamps; //!< absolute reception timestamp [s] of each sample in d_sample_buf, same indexing - typedef std::pair> msg_candiate_int_t; - typedef std::pair> msg_candiate_char_t; + // first element: absolute reception timestamp [s] of the message's first bit + typedef std::pair> msg_candiate_int_t; + typedef std::pair> msg_candiate_char_t; // helper class for sample alignment class Sample_Aligner @@ -130,10 +140,12 @@ private: { public: void reset(); - void get_frame_candidates(const std::vector &bits, std::vector>> &msg_candidates); + // bit_stamps[i] must hold the absolute reception timestamp [s] of bits[i] + void get_frame_candidates(const std::vector &bits, const std::vector &bit_stamps, std::vector>> &msg_candidates); private: std::deque d_buffer; + std::deque d_bit_stamps; //!< absolute reception timestamp [s] of each bit in d_buffer, same indexing } d_frame_detector; diff --git a/src/core/system_parameters/SBAS_L1.h b/src/core/system_parameters/SBAS_L1.h index 720f3146c..dcdaf632f 100644 --- a/src/core/system_parameters/SBAS_L1.h +++ b/src/core/system_parameters/SBAS_L1.h @@ -53,9 +53,11 @@ constexpr int32_t SBAS_L1_CHANNEL_SYMBOL_PERIOD_MS = 2; //!< Encoded channel s constexpr int32_t SBAS_L1_MSG_LENGTH_BITS = 250; //!< Information bits per 1-second message frame [bits] // Maximum coherent integration extension in terms of 1 ms code periods. -// Each encoded symbol spans 2 code periods (2 ms), so extending beyond 2 -// would straddle a symbol boundary and reduce coherent gain. -constexpr int32_t SBAS_L1_MAX_COHERENT_INTEGRATION_SYMBOLS = 2; +// Kept at 1 (no extension): the tracking correlator dumps are not +// synchronized to the encoded symbol boundary, so extending beyond a single +// code period would straddle that boundary and corrupt the sample pairing +// that sbas_l1_telemetry_decoder_gs performs on its own. +constexpr int32_t SBAS_L1_MAX_COHERENT_INTEGRATION_SYMBOLS = 1; // Preamble bytes (RTCA DO-229, Section A.4.4.2) constexpr uint8_t SBAS_L1_PREAMBLE_1 = 0x53U; //!< First preamble byte (binary: 0101 0011) diff --git a/src/core/system_parameters/gnss_satellite.cc b/src/core/system_parameters/gnss_satellite.cc index 10e8b7559..5894c65f5 100644 --- a/src/core/system_parameters/gnss_satellite.cc +++ b/src/core/system_parameters/gnss_satellite.cc @@ -16,6 +16,7 @@ #include "gnss_satellite.h" #include "GLONASS_L1_L2_CA.h" +#include "SBAS_L1.h" #include #include @@ -268,20 +269,19 @@ void Gnss_Satellite::set_PRN(uint32_t PRN_) } else if (system == "SBAS") { - if ((PRN_ == 120) // EGNOS Test Platform.Inmarsat 3-F2 (Atlantic Ocean Region-East) - || (PRN_ == 123) // EGNOS Operational Platform. Astra 5B - || (PRN_ == 131) // WAAS Eutelsat 117 West B - || (PRN_ == 135) // WAAS Galaxy 15 - || (PRN_ == 136) // EGNOS Operational Platform. SES-5 (a.k.a. Sirius 5 or Astra 4B) - || (PRN_ == 138)) // WAAS Anik F1R - { - PRN = PRN_; - } - else + // SBAS PRN assignments (RTCA DO-229, Table A-1) are reassigned between + // satellites over time, so no specific PRN-to-satellite mapping is + // hardcoded here. Any PRN within the supported range is accepted; + // see SBAS_L1_PRN_MIN/SBAS_L1_PRN_MAX for the currently supported range. + if (PRN_ < SBAS_L1_PRN_MIN || PRN_ > SBAS_L1_PRN_MAX) { DLOG(INFO) << "This PRN is not defined"; PRN = 0; } + else + { + PRN = PRN_; + } } else if (system == "Galileo") { @@ -493,29 +493,10 @@ std::string Gnss_Satellite::what_block(const std::string& system_, uint32_t PRN_ } if (system_ == "SBAS") { - switch (PRN_) - { - case 120: - block_ = std::string("EGNOS Test Platform"); // Inmarsat 3-F2 (Atlantic Ocean Region-East) - break; - case 123: - block_ = std::string("EGNOS"); // EGNOS Operational Platform. Astra 5B - break; - case 131: // NOLINT(bugprone-branch-clone) - block_ = std::string("WAAS"); // WAAS Eutelsat 117 West B - break; - case 135: - block_ = std::string("WAAS"); // WAAS Galaxy 15 - break; - case 136: - block_ = std::string("EGNOS"); // EGNOS Operational Platform. SES-5 (a.k.a. Sirius 5 or Astra 4B) - break; - case 138: - block_ = std::string("WAAS"); // WAAS Anik F1R - break; - default: - block_ = std::string("Unknown"); - } + // Which augmentation service (EGNOS, WAAS, GAGAN, ...) owns a given PRN + // changes over time as satellites are reassigned, so no per-PRN service + // name is hardcoded here to avoid publishing stale information. + block_ = std::string("SBAS"); } if (system_ == "Galileo") { diff --git a/tests/test_main.cc b/tests/test_main.cc index f1a674668..26493b5e6 100644 --- a/tests/test_main.cc +++ b/tests/test_main.cc @@ -175,6 +175,7 @@ private: #include "unit-tests/signal-processing-blocks/sources/gnss_sdr_valve_test.cc" #include "unit-tests/signal-processing-blocks/sources/unpack_2bit_samples_test.cc" #include "unit-tests/signal-processing-blocks/telemetry_decoder/galileo_fnav_inav_decoder_test.cc" +#include "unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc" #include "unit-tests/signal-processing-blocks/tracking/bit_synchronizer_test.cc" #include "unit-tests/signal-processing-blocks/tracking/cpu_multicorrelator_real_codes_test.cc" #include "unit-tests/signal-processing-blocks/tracking/cpu_multicorrelator_test.cc" diff --git a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc new file mode 100644 index 000000000..2c04ef862 --- /dev/null +++ b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc @@ -0,0 +1,67 @@ +/*! + * \file sbas_l1_telemetry_decoder_test.cc + * \brief This class implements a deterministic unit test for the message + * timestamp computation of the SBAS L1 telemetry decoder. + * \author Miguel Gómez López, 2026. mgomezl(at)ing.uc3m.es + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "SBAS_L1.h" +#include "sbas_l1_telemetry_decoder_gs.h" +#include + +TEST(SbasL1TelemetryDecoderTest, TimestampMatchesFirstBitWhenFullyAligned) +{ + // When both sample and symbol alignment already match the chosen + // hypothesis, no residual correction is applied: the message timestamp + // must equal the first bit's own absolute timestamp. + const double first_bit_stamp_s = 123.456; + const double message_stamp_s = sbas_l1_telemetry_decoder_gs::compute_message_timestamp(first_bit_stamp_s, true, true); + EXPECT_DOUBLE_EQ(first_bit_stamp_s, message_stamp_s); +} + + +TEST(SbasL1TelemetryDecoderTest, TimestampCorrectionIsBoundedBySymbolPeriod) +{ + // The residual sample/symbol alignment correction must never exceed one + // encoded symbol period: it only resolves a small, sub-bit ambiguity, not + // the block-level offset that used to cause ~1 s errors. + const double first_bit_stamp_s = 1000.0; + const double max_correction_s = 2.0 * SBAS_L1_CODE_PERIOD_S; // 1 symbol = 2 code periods + + for (const bool sample_aligned : {false, true}) + { + for (const bool symbol_aligned : {false, true}) + { + const double message_stamp_s = sbas_l1_telemetry_decoder_gs::compute_message_timestamp(first_bit_stamp_s, sample_aligned, symbol_aligned); + EXPECT_NEAR(first_bit_stamp_s, message_stamp_s, max_correction_s); + } + } +} + + +TEST(SbasL1TelemetryDecoderTest, TimestampIsNeverInTheFuture) +{ + // A decoded message can only be timestamped at or before the time its + // first bit was received; the previous implementation could report a + // timestamp roughly 1 s ahead of the true preamble reception time. + const double first_bit_stamp_s = 42.0; + + for (const bool sample_aligned : {false, true}) + { + for (const bool symbol_aligned : {false, true}) + { + const double message_stamp_s = sbas_l1_telemetry_decoder_gs::compute_message_timestamp(first_bit_stamp_s, sample_aligned, symbol_aligned); + EXPECT_LE(message_stamp_s, first_bit_stamp_s); + } + } +} From 68bac23bcbc1ddeacf634c14ae7bb663fc2915cd Mon Sep 17 00:00:00 2001 From: kalmancito Date: Tue, 4 Aug 2026 00:35:03 +0200 Subject: [PATCH 3/4] Fix sign convention in SBAS message timestamp correction The symbol alignment term of compute_message_timestamp() used the opposite sign convention to the sample alignment term. In both aligners, true means the non-shifted hypothesis won (no correction) and false means the shifted one won (-1 unit), so the symbol term must be (symbol_aligned ? 0 : -1). Two of the unit tests added along with that function were failing as a result, which is what broke the check step of the CI on all three platforms. Also express the correction bound in whole code periods rather than comparing doubles at an exact boundary: the worst case is 3 code periods (1 from the sample-pairing ambiguity plus 2 from the symbol-pairing one), not 2, and the floating-point comparison was fragile at that boundary. Co-Authored-By: Claude Opus 5 --- .../sbas_l1_telemetry_decoder_gs.cc | 2 +- .../sbas_l1_telemetry_decoder_test.cc | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc index 54dd303f5..69581c905 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/sbas_l1_telemetry_decoder_gs.cc @@ -129,7 +129,7 @@ double sbas_l1_telemetry_decoder_gs::compute_message_timestamp(double first_bit_ // Residual sub-bit correction: which of the two possible sample/symbol // alignments was chosen shifts the true message start by a few samples // with respect to the first bit's own timestamp. - const int32_t offset_samples = (sample_aligned ? 0 : -1) + D_SAMPLES_PER_SYMBOL * (symbol_aligned ? -1 : 0); + const int32_t offset_samples = (sample_aligned ? 0 : -1) + D_SAMPLES_PER_SYMBOL * (symbol_aligned ? 0 : -1); return first_bit_stamp_s + static_cast(offset_samples) * SBAS_L1_CODE_PERIOD_S; } diff --git a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc index 2c04ef862..aac495880 100644 --- a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc +++ b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc @@ -18,6 +18,8 @@ #include "SBAS_L1.h" #include "sbas_l1_telemetry_decoder_gs.h" #include +#include +#include TEST(SbasL1TelemetryDecoderTest, TimestampMatchesFirstBitWhenFullyAligned) { @@ -32,18 +34,22 @@ TEST(SbasL1TelemetryDecoderTest, TimestampMatchesFirstBitWhenFullyAligned) TEST(SbasL1TelemetryDecoderTest, TimestampCorrectionIsBoundedBySymbolPeriod) { - // The residual sample/symbol alignment correction must never exceed one - // encoded symbol period: it only resolves a small, sub-bit ambiguity, not - // the block-level offset that used to cause ~1 s errors. + // The correction is always a whole number of code periods, and spans at + // most one code period (sample-pairing ambiguity) plus one symbol, i.e. + // two code periods (bit-pairing ambiguity). It only resolves a small + // sub-bit ambiguity, not the block-level offset that used to cause ~1 s + // errors. const double first_bit_stamp_s = 1000.0; - const double max_correction_s = 2.0 * SBAS_L1_CODE_PERIOD_S; // 1 symbol = 2 code periods + const int64_t max_correction_code_periods = 3; for (const bool sample_aligned : {false, true}) { for (const bool symbol_aligned : {false, true}) { const double message_stamp_s = sbas_l1_telemetry_decoder_gs::compute_message_timestamp(first_bit_stamp_s, sample_aligned, symbol_aligned); - EXPECT_NEAR(first_bit_stamp_s, message_stamp_s, max_correction_s); + const int64_t correction_code_periods = std::llround((first_bit_stamp_s - message_stamp_s) / SBAS_L1_CODE_PERIOD_S); + EXPECT_GE(correction_code_periods, 0); + EXPECT_LE(correction_code_periods, max_correction_code_periods); } } } From ef7df8f5ad4f90b6f63d1364fa2da9e90a8c0797 Mon Sep 17 00:00:00 2001 From: miguekf Date: Tue, 4 Aug 2026 22:12:32 +0200 Subject: [PATCH 4/4] Guard SBAS Viterbi startup traceback Wait until the continuous decoder has accumulated the requested traceback depth before advancing trellis iterators. Add a regression test for the 30-bit SBAS input block versus its 35-bit traceback depth. Signed-off-by: miguekf --- .../telemetry_decoder/libs/viterbi_decoder_sbas.cc | 9 +++++++++ .../telemetry_decoder/sbas_l1_telemetry_decoder_test.cc | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/algorithms/telemetry_decoder/libs/viterbi_decoder_sbas.cc b/src/algorithms/telemetry_decoder/libs/viterbi_decoder_sbas.cc index 02c27e28b..6f42fd4e3 100644 --- a/src/algorithms/telemetry_decoder/libs/viterbi_decoder_sbas.cc +++ b/src/algorithms/telemetry_decoder/libs/viterbi_decoder_sbas.cc @@ -101,6 +101,15 @@ float Viterbi_Decoder_Sbas::decode_continuous(const double sym[], // do add compare select do_acs(sym, nbits_requested); + // Keep accumulating trellis history until a complete traceback is + // available. do_traceback() can clamp the requested depth, but + // do_tb_and_decode() expects that same depth to be present and advances + // its deque iterators by traceback_depth. + if (traceback_depth > static_cast(d_trellis_paths.size())) + { + nbits_decoded = 0; + return d_indicator_metric; + } // the ML sequence in the newest part of the trellis can not be decoded // since it depends on the future values -> traceback, but don't decode const int state = do_traceback(traceback_depth); diff --git a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc index aac495880..35e8cafef 100644 --- a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc +++ b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/sbas_l1_telemetry_decoder_test.cc @@ -31,7 +31,6 @@ TEST(SbasL1TelemetryDecoderTest, TimestampMatchesFirstBitWhenFullyAligned) EXPECT_DOUBLE_EQ(first_bit_stamp_s, message_stamp_s); } - TEST(SbasL1TelemetryDecoderTest, TimestampCorrectionIsBoundedBySymbolPeriod) { // The correction is always a whole number of code periods, and spans at