From f129a4af8e2d44f56604ee17cc08b5dacb0ca85c Mon Sep 17 00:00:00 2001 From: miguekf Date: Mon, 3 Aug 2026 23:50:10 +0200 Subject: [PATCH] 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); + } + } +}