mirror of
https://github.com/gnss-sdr/gnss-sdr
synced 2026-10-19 05:52:36 +00:00
Merge branch 'ft/sbas' of https://github.com/gnss-sdr/gnss-sdr-vtl into gnss-sdr-ft/sbas
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
; 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 =============================
|
||||
;
|
||||
; 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; 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 "<--" ====================
|
||||
|
||||
[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 ; <-- 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 ###########################################################
|
||||
; 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 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
|
||||
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: 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
|
||||
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
|
||||
@@ -86,6 +86,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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "Galileo_E5a.h"
|
||||
#include "Galileo_E5b.h"
|
||||
#include "Galileo_E6.h"
|
||||
#include "SBAS_L1.h"
|
||||
#include "acq_conf.h"
|
||||
#include "beidou_b1c_signal_replica.h"
|
||||
#include "beidou_b1i_signal_replica.h"
|
||||
@@ -44,6 +45,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
|
||||
@@ -104,6 +106,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;
|
||||
}
|
||||
@@ -190,6 +194,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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
@@ -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 <string>
|
||||
|
||||
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
|
||||
@@ -34,6 +34,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
|
||||
@@ -70,6 +71,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}
|
||||
|
||||
@@ -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 <array>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
void sbas_l1_code_gen_complex_sampled(own::span<std::complex<float>> 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<float>(sampling_freq);
|
||||
const float tc = 1.0F / static_cast<float>(code_chip_rate);
|
||||
|
||||
std::array<std::complex<float>, code_length> code_aux{};
|
||||
sbas_l1_code_gen_complex(code_aux, prn, 0U);
|
||||
|
||||
const auto samples_per_code = static_cast<int32_t>(dest.size());
|
||||
for (int32_t i = 0; i < samples_per_code; i++)
|
||||
{
|
||||
const auto idx = static_cast<int32_t>(std::floor(ts * static_cast<float>(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<float> dest, uint32_t prn)
|
||||
{
|
||||
gps_l1_ca_code_gen_float(dest, static_cast<int32_t>(prn), 0U);
|
||||
}
|
||||
|
||||
|
||||
void sbas_l1_code_gen_complex(own::span<std::complex<float>> dest, uint32_t prn, uint32_t chip_shift)
|
||||
{
|
||||
constexpr uint32_t code_length = 1023;
|
||||
std::array<int32_t, code_length> ca_code_int{};
|
||||
gps_l1_ca_code_gen_int(ca_code_int, static_cast<int32_t>(prn), chip_shift);
|
||||
for (uint32_t ii = 0; ii < code_length; ++ii)
|
||||
{
|
||||
dest[ii] = {static_cast<float>(ca_code_int[ii]), 0.0F};
|
||||
}
|
||||
}
|
||||
@@ -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 <complex>
|
||||
#include <cstdint>
|
||||
#if HAS_STD_SPAN
|
||||
#include <span>
|
||||
namespace own = std;
|
||||
#else
|
||||
#include <gsl-lite/gsl-lite.hpp>
|
||||
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<std::complex<float>> 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<float> 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<std::complex<float>> dest, uint32_t prn, uint32_t chip_shift);
|
||||
|
||||
/** \} */
|
||||
/** \} */
|
||||
|
||||
#endif // GNSS_SDR_SBAS_SIGNAL_REPLICA_H
|
||||
@@ -2,19 +2,22 @@
|
||||
* \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
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#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
|
||||
@@ -23,6 +26,9 @@
|
||||
#include <cmath> // for abs
|
||||
#include <exception> // for exception
|
||||
#include <iomanip> // for operator<<, setw
|
||||
#include <iostream> // for std::cout
|
||||
#include <string> // for std::string
|
||||
#include <utility> // for std::move
|
||||
|
||||
#if USE_GLOG_AND_GFLAGS
|
||||
#include <glog/logging.h>
|
||||
@@ -37,25 +43,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 +88,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,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 ? 0 : -1);
|
||||
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()
|
||||
{
|
||||
@@ -225,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;
|
||||
@@ -239,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
|
||||
@@ -274,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)
|
||||
@@ -284,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,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)
|
||||
@@ -421,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
|
||||
@@ -436,30 +494,147 @@ 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
|
||||
<< ")";
|
||||
// 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<int>(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 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;
|
||||
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::trunc);
|
||||
if (d_ems_file.is_open())
|
||||
{
|
||||
// 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 raw message dump 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<unsigned int>(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<Sbas_Raw_Msg>::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();
|
||||
d_sample_stamps.clear();
|
||||
}
|
||||
|
||||
// UPDATE GNSS SYNCHRO DATA
|
||||
|
||||
@@ -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>;
|
||||
|
||||
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
|
||||
@@ -48,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
|
||||
@@ -57,9 +69,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,12 +88,15 @@ 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<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
|
||||
@@ -123,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;
|
||||
|
||||
|
||||
|
||||
@@ -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<int>(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);
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,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"
|
||||
@@ -84,6 +85,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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
@@ -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 <string>
|
||||
|
||||
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
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "Galileo_E5b.h"
|
||||
#include "Galileo_E6.h"
|
||||
#include "MATH_CONSTANTS.h"
|
||||
#include "SBAS_L1.h"
|
||||
#include "beidou_b1c_signal_replica.h"
|
||||
#include "beidou_b1i_signal_replica.h"
|
||||
#include "beidou_b3i_signal_replica.h"
|
||||
@@ -54,6 +55,7 @@
|
||||
#include "matlab_writter_helper.h"
|
||||
#include "qzss.h"
|
||||
#include "qzss_signal_replica.h"
|
||||
#include "sbas_signal_replica.h"
|
||||
#include "tracking_discriminators.h"
|
||||
#include <gnuradio/io_signature.h> // for io_signature
|
||||
#include <gnuradio/thread/thread.h> // for scoped_lock
|
||||
@@ -616,7 +618,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<int32_t>(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";
|
||||
@@ -1108,6 +1149,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));
|
||||
|
||||
@@ -74,6 +74,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"
|
||||
@@ -215,6 +217,7 @@ const auto signal_mapping = std::vector<std::pair<std::string, std::string>>{
|
||||
{"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)
|
||||
@@ -510,6 +513,10 @@ std::unique_ptr<AcquisitionInterface> get_acq_block(
|
||||
{
|
||||
return std::make_unique<PcpsAcquisitionAdapter>(configuration, role, implementation, in_streams, out_streams, QZS_J5);
|
||||
}
|
||||
else if (implementation == "SBAS_L1_PCPS_Acquisition")
|
||||
{
|
||||
return std::make_unique<SbasL1PcpsAcquisition>(configuration, role, in_streams, out_streams);
|
||||
}
|
||||
#if OPENCL_BLOCKS
|
||||
else if (implementation == "GPS_L1_CA_PCPS_OpenCl_Acquisition")
|
||||
{
|
||||
@@ -626,6 +633,10 @@ std::unique_ptr<TrackingInterface> get_trk_block(
|
||||
{
|
||||
return std::make_unique<DllPllTrackingAdapter>(configuration, role, implementation, in_streams, out_streams, QZS_J5);
|
||||
}
|
||||
else if (implementation == "SBAS_L1_DLL_PLL_Tracking")
|
||||
{
|
||||
return std::make_unique<SbasL1DllPllTracking>(configuration, role, in_streams, out_streams);
|
||||
}
|
||||
#if CUDA_GPU_ACCEL
|
||||
else if (implementation == "GPS_L1_CA_DLL_PLL_Tracking_GPU")
|
||||
{
|
||||
@@ -709,7 +720,9 @@ std::unique_ptr<TelemetryDecoderInterface> 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")
|
||||
{
|
||||
|
||||
@@ -98,6 +98,7 @@ const auto signal_mapping = std::unordered_map<std::string, std::pair<std::strin
|
||||
{"2G", {"Glonass", "L2"}},
|
||||
{"J1", {"QZSS", "L1"}},
|
||||
{"J5", {"QZSS", "L5"}},
|
||||
{"S1", {"SBAS", "L1"}},
|
||||
};
|
||||
|
||||
|
||||
@@ -264,6 +265,7 @@ void GNSSFlowgraph::init()
|
||||
mapStringValues_["B3"] = evBDS_B3;
|
||||
mapStringValues_["J1"] = evQZS_J1;
|
||||
mapStringValues_["J5"] = evQZS_J5;
|
||||
mapStringValues_["S1"] = evSBAS_1C;
|
||||
|
||||
// fill the signals queue with the satellites ID's to be searched by the acquisition
|
||||
set_signals_list();
|
||||
@@ -2103,7 +2105,7 @@ void GNSSFlowgraph::set_signals_list()
|
||||
// GLONASS uses FDMA, so only one orbital slot per frequency channel is queued for acquisition.
|
||||
std::unordered_map<std::string, std::set<unsigned int>> 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}},
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*!
|
||||
* \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 <cstdint>
|
||||
|
||||
/** \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.
|
||||
// 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)
|
||||
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
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
@@ -36,7 +36,8 @@ enum signal_flag : uint32_t
|
||||
BDS_B3 = 0x1 << 10,
|
||||
BDS_B1C = 0x1 << 13,
|
||||
QZS_J1 = 0x1 << 11,
|
||||
QZS_J5 = 0x1 << 12
|
||||
QZS_J5 = 0x1 << 12,
|
||||
SBAS_S1 = 0x1 << 13
|
||||
};
|
||||
|
||||
#endif // GNSS_SDR_SIGNAL_FLAG_H
|
||||
|
||||
@@ -176,6 +176,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"
|
||||
|
||||
@@ -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 <array>
|
||||
#include <chrono>
|
||||
#include <complex>
|
||||
@@ -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<std::complex<float>, 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<std::complex<float>, 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<float, 1023> 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<int32_t>(SBAS_L1_OPT_ACQ_FS_SPS); // 2 Msps
|
||||
const int32_t code_rate = static_cast<int32_t>(SBAS_L1_CODE_RATE_CPS);
|
||||
const int32_t code_length = static_cast<int32_t>(SBAS_L1_CODE_LENGTH_CHIPS);
|
||||
const int samples_per_code = static_cast<int>(std::round(
|
||||
static_cast<double>(fs) / (static_cast<double>(code_rate) / static_cast<double>(code_length))));
|
||||
|
||||
std::vector<std::complex<float>> 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<int>(std::round(static_cast<double>(fs) / static_cast<double>(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";
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* \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>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
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 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 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user