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 <miguel.gomezlopez@uclm.es>
This commit is contained in:
miguekf
2026-08-04 23:05:33 +02:00
committed by kalmancito
parent 81f4c8846b
commit f129a4af8e
8 changed files with 198 additions and 130 deletions
@@ -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
@@ -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_));
}
@@ -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 <pmt/pmt_sugar.h> // 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<double>(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<int32_t> &bits, std::vector<std::pair<int32_t, std::vector<int32_t>>> &msg_candidates)
void sbas_l1_telemetry_decoder_gs::Frame_Detector::get_frame_candidates(const std::vector<int32_t> &bits, const std::vector<double> &bit_stamps, std::vector<std::pair<double, std::vector<int32_t>>> &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<double>(in[0].Tracking_sample_counter) / static_cast<double>(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<int32_t> 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<double> 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_candiate_int_t> 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_Msg> 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<double>(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: <gps_week> <tow_s> <prn> <msg_type> : <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: <gps_week> <tow_s> <prn> <msg_type> : <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
@@ -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<double> 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<double> d_sample_buf; //!< input buffer holding the samples to be processed in one block
std::vector<double> d_sample_stamps; //!< absolute reception timestamp [s] of each sample in d_sample_buf, same indexing
typedef std::pair<int32_t, std::vector<int32_t>> msg_candiate_int_t;
typedef std::pair<int32_t, std::vector<uint8_t>> msg_candiate_char_t;
// first element: absolute reception timestamp [s] of the message's first bit
typedef std::pair<double, std::vector<int32_t>> msg_candiate_int_t;
typedef std::pair<double, std::vector<uint8_t>> 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<int32_t> &bits, std::vector<std::pair<int32_t, std::vector<int32_t>>> &msg_candidates);
// bit_stamps[i] must hold the absolute reception timestamp [s] of bits[i]
void get_frame_candidates(const std::vector<int32_t> &bits, const std::vector<double> &bit_stamps, std::vector<std::pair<double, std::vector<int32_t>>> &msg_candidates);
private:
std::deque<int32_t> d_buffer;
std::deque<double> d_bit_stamps; //!< absolute reception timestamp [s] of each bit in d_buffer, same indexing
} d_frame_detector;
+5 -3
View File
@@ -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)
+14 -33
View File
@@ -16,6 +16,7 @@
#include "gnss_satellite.h"
#include "GLONASS_L1_L2_CA.h"
#include "SBAS_L1.h"
#include <stdexcept>
#include <utility>
@@ -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")
{
+1
View File
@@ -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"
@@ -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 <gtest/gtest.h>
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);
}
}
}