Merge branch 'OuWenhao16-contrib/beidou-b1c-clean' into next

This commit is contained in:
Carles Fernandez
2026-08-08 10:12:54 +02:00
69 changed files with 12205 additions and 145 deletions
+1
View File
@@ -75,5 +75,6 @@ Piyush Gupta piyush04111999@gmail.com Contributor
Rodrigo Muñoz rodrigo.munoz@proteinlab.cl Contributor
Stefan van der Linden spvdlinden@gmail.com Contributor
Víctor Castillo-Agüero victorcastilloaguero@gmail.com Contributor
Wenhao Ou ouwh@mail2.sysu.edu.cn Contributor
Will Silberman wsilberm@google.com Contributor
Carlos Paniego carpanie@hotmail.com Artwork
+5
View File
@@ -144,6 +144,11 @@ authors:
email: spvdlinden@gmail.com
family-names: "van der Linden"
given-names: Stefan
- alias: OuWenhao16
affiliation: "Sun Yat-sen University (SYSU)"
email: ouwh@mail2.sysu.edu.cn
family-names: Ou
given-names: Wenhao
cff-version: "1.2.0"
date-released: "2026-04-14"
identifiers:
+2
View File
@@ -26,6 +26,7 @@ In the L1 band:
- 🛰 GLONASS L1 C/A (centered at 1602.000 MHz) ✅
- 🛰 GPS L1 C/A (centered at 1575.420 MHz) ✅
- 🛰 Galileo E1b/c (centered at 1575.420 MHz) ✅
- 🛰 BeiDou B1C (centered at 1575.420 MHz) ✅
- 🛰 BeiDou B1I (centered at 1561.098 MHz) ✅
- 🛰 QZSS L1 C/A and C/B, where available (centered at 1575.420 MHz) ✅
@@ -1554,6 +1555,7 @@ identifiers:
| Galileo E1b/c | 1B |
| Glonass L1 C/A | 1G |
| Beidou B1I | B1 |
| Beidou B1C | 1D |
| QZSS L1 C/A | J1 |
| Galileo E6B | E6 |
| Beidou B3I | B3 |
@@ -0,0 +1,121 @@
; BeiDou B1C (1D) file-input example (IF = +20 kHz, Fs = 18 MHz, ibyte)
; Test data: https://drive.google.com/drive/folders/1UkjJwOMlKEFZiXYUdK4HKzTASYHXMsHL
; SPDX-License-Identifier: GPL-3.0-or-later
; SPDX-FileCopyrightText: (C) 2010-2026 (see AUTHORS file for a list of contributors)
;
; Mixed B1I+B1C PVT policy (same PRN):
; - Solver slot 0 holds either B1I or B1C (prefer-B1C XOR), not both.
; - satwavelen keeps RTKLIB BDS semantics (frq0=B1I, frq1=B2, frq2=B3I);
; B1C wavelength (FREQ1) is applied via CODE_L1P and nav->lam[][0] override.
; - When both B1I and B1C are tracked for the same PRN and usable B-CNAV1
; exists, PVT prefers B1C+CNAV1 and does not insert that PRN's B1I into
; the RTKLIB observation epoch (avoids dual DNAV/CNAV1 rows for one sat).
; - If B1C lacks usable CNAV1, B1I+DNAV is kept. RINEX/obs dumps still keep
; both signals; the preference applies only to the PVT engine.
; - Iono-Free-LC for BeiDou uses B1I+B3I only; B1C is single-frequency.
; - Dual-frequency IF: set PVT.iono_model=Iono-Free-LC with Channels_B1+B3.
[GNSS-SDR]
;######### GLOBAL OPTIONS ##################
GNSS-SDR.internal_fs_sps=18000000
GNSS-SDR.use_acquisition_resampler=true
;######### SIGNAL_SOURCE CONFIG ############
SignalSource.implementation=File_Signal_Source
SignalSource.filename=./L1_IF20KHz_FS18MHz.bin
SignalSource.item_type=ibyte
SignalSource.sampling_frequency=18000000
SignalSource.samples=0
SignalSource.enable_throttle_control=false
;######### SIGNAL_CONDITIONER CONFIG ############
SignalConditioner.implementation=Signal_Conditioner
DataTypeAdapter.implementation=Ibyte_To_Complex
DataTypeAdapter.item_type=ibyte
;######### INPUT_FILTER CONFIG ############
InputFilter.implementation=Freq_Xlating_Fir_Filter
InputFilter.input_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.45
InputFilter.band2_begin=0.55
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=18000000
InputFilter.IF=20000
Resampler.implementation=Pass_Through
;######### CHANNELS GLOBAL CONFIG ############
Channels_1D.count=6
Channels.in_acquisition=6
Channel.signal=1D
Channel0.satellite=23
Channel1.satellite=27
Channel2.satellite=28
Channel3.satellite=37
Channel4.satellite=43
Channel5.satellite=46
;######### ACQUISITION GLOBAL CONFIG ############
Acquisition_1D.implementation=BEIDOU_B1C_PCPS_Ambiguous_Acquisition
Acquisition_1D.item_type=gr_complex
Acquisition_1D.acquire_pilot=true
Acquisition_1D.qmboc=true
Acquisition_1D.pfa=0.005
Acquisition_1D.doppler_max=5000
Acquisition_1D.doppler_step=25
;######### TRACKING GLOBAL CONFIG ############
Tracking_1D.implementation=BEIDOU_B1C_DLL_PLL_VEML_Tracking
Tracking_1D.item_type=gr_complex
Tracking_1D.track_pilot=true
Tracking_1D.extend_correlation_symbols=1
Tracking_1D.bit_synchronization_time_limit_s=90
Tracking_1D.pull_in_time_s=5
Tracking_1D.pll_bw_hz=10.0
Tracking_1D.dll_bw_hz=0.5
Tracking_1D.pll_bw_narrow_hz=5.0
Tracking_1D.dll_bw_narrow_hz=0.2
Tracking_1D.pll_filter_order=2
Tracking_1D.early_late_space_chips=0.15
Tracking_1D.very_early_late_space_chips=0.5
Tracking_1D.cn0_min=20
Tracking_1D.b1c_secondary_lock_ratio=0.92
;######### TELEMETRY DECODER CONFIG ############
TelemetryDecoder_1D.implementation=BEIDOU_B1C_Telemetry_Decoder
TelemetryDecoder_1D.dump=false
;######### OBSERVABLES CONFIG ############
Observables.implementation=Hybrid_Observables
;######### PVT CONFIG ############
PVT.implementation=RTKLIB_PVT
PVT.positioning_mode=Single
PVT.output_rate_ms=100
PVT.display_rate_ms=500
; Broadcast: B1C uses BDGIM when B-CNAV1 page-1 iono is decoded; else GPS Klobuchar.
; For B1I+B3I iono-free LC use Iono-Free-LC (not applicable to B1C-only).
PVT.iono_model=Broadcast
PVT.trop_model=Saastamoinen
PVT.flag_rtcm_server=false
PVT.output_enabled=true
PVT.rinex_output_enabled=true
PVT.nmea_output_file_enabled=true
PVT.gpx_output_enabled=true
+17
View File
@@ -96,6 +96,21 @@ All notable changes to GNSS-SDR will be documented in this file.
### Improvements in Interoperability:
- Added the BeiDou B1C receiver chain, with signal identifier `1D`: acquisition
(`BEIDOU_B1C_PCPS_Ambiguous_Acquisition`, with optional QMBOC local replica),
tracking (`BEIDOU_B1C_DLL_PLL_VEML_Tracking`, tracking the pilot component by
default), and B-CNAV1 telemetry decoding (`BEIDOU_B1C_Telemetry_Decoder`,
including LDPC decoding of subframes 2 and 3 and BCH decoding of subframe 1).
The PVT engine uses the B-CNAV1 ephemeris, clock, and group-delay corrections
(TGD_B1Cp / ISC_B1Cd), implements the BDGIM ionospheric model broadcast in
B-CNAV1, and supports both B1C-only and mixed B1I+B1C configurations, keeping
DNAV and B-CNAV1 ephemerides isolated and preferring B1C over B1I when both
signals are available from the same satellite. B-CNAV1 ephemerides are also
written to RINEX navigation files (native CNV1 records in RINEX 4.02,
D1-style stand-in records in RINEX 3.02) and to the XML assistance-data
storage. A sample configuration file is provided at
`conf/File_input/Beidou/gnss-sdr_BDS_B1C_geb_if20k_fs18m_ibyte.conf`.
Contributed by @OuWenhao16.
- Added support for RINEX 4.02 output, activated by setting
`PVT.rinex_version=4` in the configuration file (or with the
`-RINEX_version=4.02` command-line flag). Observation files are generated in
@@ -382,6 +397,7 @@ https://gnss-sdr.org/design-forces/
https://www.youtube.com/watch?v=HnZkKj9a-QM
- Add the following signal sources for use when GNSS-SDR is operating on SoC
FPGA boards (`-DENABLE_FPGA=ON`):
- `ADRV9361_Z7035_Signal_Source_FPGA`: Analog Devices ADRV9361-Z7035 board.
- `FMCOMMS5_Signal_Source_FPGA`: FMCOMMS5 analog front-end.
- `MAX2771_EVKIT_Signal_Source_FPGA`: MAX2771 evaluation kit analog front-end.
@@ -390,6 +406,7 @@ https://gnss-sdr.org/design-forces/
When building GNSS-SDR for the SoC FPGA, the following options can be passed
to CMake with possible values of `ON` or `OFF`, and their default value is
`OFF`:
- `-DENABLE_AD9361`: Checks if the IIO driver is installed and builds the
`ADRV9361_Z7035_Signal_Source_FPGA` and the `FMCOMMS5_Signal_Source_FPGA`
sources.
+2 -2
View File
@@ -244,7 +244,7 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration,
int num_bands = 0;
if (signal_enabled_flags.check_any_enabled(GPS_1C, GAL_1B, GLO_1G, BDS_B1, QZS_J1))
if (signal_enabled_flags.check_any_enabled(GPS_1C, GAL_1B, GLO_1G, BDS_B1, BDS_B1C, QZS_J1))
{
num_bands += 1;
}
@@ -396,7 +396,7 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration,
{
nsys += SYS_GLO;
}
if (signal_enabled_flags.check_any_enabled(BDS_B1, BDS_B3))
if (signal_enabled_flags.check_any_enabled(BDS_B1, BDS_B3, BDS_B1C))
{
nsys += SYS_BDS;
}
@@ -18,6 +18,10 @@
#include "Galileo_CNAV.h"
#include "MATH_CONSTANTS.h"
#include "an_packet_printer.h"
#include "beidou_cnav1_ephemeris.h"
#include "beidou_cnav1_iono.h"
#include "beidou_cnav1_navigation_message.h"
#include "beidou_cnav1_utc_model.h"
#include "beidou_dnav_almanac.h"
#include "beidou_dnav_ephemeris.h"
#include "beidou_dnav_iono.h"
@@ -170,6 +174,10 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels,
d_beidou_dnav_iono_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Dnav_Iono>).hash_code()),
d_beidou_dnav_utc_model_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Dnav_Utc_Model>).hash_code()),
d_beidou_dnav_almanac_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Dnav_Almanac>).hash_code()),
d_beidou_cnav1_ephemeris_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Cnav1_Ephemeris>).hash_code()),
d_beidou_cnav1_iono_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Cnav1_Iono>).hash_code()),
d_beidou_cnav1_utc_model_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Cnav1_Utc_Model>).hash_code()),
d_beidou_cnav1_page_data_sptr_type_hash_code(typeid(std::shared_ptr<Beidou_Cnav1_PageData_Message>).hash_code()),
d_galileo_has_data_sptr_type_hash_code(typeid(std::shared_ptr<Galileo_HAS_data>).hash_code()),
d_rinex_version(conf_.rinex_version),
d_rx_time(0.0),
@@ -1305,6 +1313,36 @@ rtklib_pvt_gs::~rtklib_pvt_gs()
{
LOG(INFO) << "Failed to save BeiDou DNAV UTC model parameters, not valid data";
}
// save BeiDou B-CNAV1 ephemeris to XML file
file_name = d_xml_base_path + "bds_cnav1_ephemeris.xml";
if (d_internal_pvt_solver->beidou_cnav1_ephemeris_map.empty() == false)
{
std::ofstream ofs;
try
{
ofs.open(file_name.c_str(), std::ofstream::trunc | std::ofstream::out);
boost::archive::xml_oarchive xml(ofs);
xml << boost::serialization::make_nvp("GNSS-SDR_bds_cnav1_ephemeris_map", d_internal_pvt_solver->beidou_cnav1_ephemeris_map);
LOG(INFO) << "Saved BeiDou B-CNAV1 Ephemeris map data";
}
catch (const boost::archive::archive_exception& e)
{
LOG(WARNING) << e.what();
}
catch (const std::ofstream::failure& e)
{
LOG(WARNING) << "Problem opening output XML file";
}
catch (const std::exception& e)
{
LOG(WARNING) << e.what();
}
}
else
{
LOG(INFO) << "Failed to save BeiDou B-CNAV1 Ephemeris, map is empty";
}
}
if (d_log_timetag_file.is_open())
@@ -1805,6 +1843,55 @@ void rtklib_pvt_gs::msg_handler_telemetry(const pmt::pmt_t& msg)
}
DLOG(INFO) << "New BeiDou DNAV almanac record has arrived";
}
else if (msg_type_hash_code == d_beidou_cnav1_ephemeris_sptr_type_hash_code)
{
const auto bds_cnav1_eph = wht::any_cast<std::shared_ptr<Beidou_Cnav1_Ephemeris>>(pmt::any_ref(msg));
if (d_rinex_output_enabled && d_rp->is_rinex_header_written())
{
const auto eph_it = d_internal_pvt_solver->beidou_cnav1_ephemeris_map.find(bds_cnav1_eph->PRN);
if (eph_it == d_internal_pvt_solver->beidou_cnav1_ephemeris_map.cend() || eph_it->second.toe != bds_cnav1_eph->toe)
{
d_rp->log_rinex_nav_bds_cnav1({{bds_cnav1_eph->PRN, *bds_cnav1_eph}}, d_internal_pvt_solver->beidou_cnav1_page_data_map);
}
}
d_internal_pvt_solver->beidou_cnav1_ephemeris_map[bds_cnav1_eph->PRN] = *bds_cnav1_eph;
if (d_enable_rx_clock_correction == true)
{
d_user_pvt_solver->beidou_cnav1_ephemeris_map[bds_cnav1_eph->PRN] = *bds_cnav1_eph;
}
DLOG(INFO) << "New BeiDou B-CNAV1 ephemeris record has arrived from SAT ID " << bds_cnav1_eph->PRN;
}
else if (msg_type_hash_code == d_beidou_cnav1_iono_sptr_type_hash_code)
{
const auto bds_cnav1_iono = wht::any_cast<std::shared_ptr<Beidou_Cnav1_Iono>>(pmt::any_ref(msg));
d_internal_pvt_solver->beidou_cnav1_iono = *bds_cnav1_iono;
if (d_enable_rx_clock_correction == true)
{
d_user_pvt_solver->beidou_cnav1_iono = *bds_cnav1_iono;
}
DLOG(INFO) << "New BeiDou B-CNAV1 IONO record has arrived";
}
else if (msg_type_hash_code == d_beidou_cnav1_utc_model_sptr_type_hash_code)
{
const auto bds_cnav1_utc = wht::any_cast<std::shared_ptr<Beidou_Cnav1_Utc_Model>>(pmt::any_ref(msg));
d_internal_pvt_solver->beidou_cnav1_utc_model = *bds_cnav1_utc;
if (d_enable_rx_clock_correction == true)
{
d_user_pvt_solver->beidou_cnav1_utc_model = *bds_cnav1_utc;
}
DLOG(INFO) << "New BeiDou B-CNAV1 UTC record has arrived";
}
else if (msg_type_hash_code == d_beidou_cnav1_page_data_sptr_type_hash_code)
{
const auto bds_cnav1_page = wht::any_cast<std::shared_ptr<Beidou_Cnav1_PageData_Message>>(pmt::any_ref(msg));
d_internal_pvt_solver->beidou_cnav1_page_data_map[bds_cnav1_page->PRN] = bds_cnav1_page->page_data;
if (d_enable_rx_clock_correction == true)
{
d_user_pvt_solver->beidou_cnav1_page_data_map[bds_cnav1_page->PRN] = bds_cnav1_page->page_data;
}
DLOG(INFO) << "New BeiDou B-CNAV1 page data record has arrived from SAT ID " << bds_cnav1_page->PRN
<< " PageID=" << bds_cnav1_page->page_data.common.page_id;
}
else
{
LOG(WARNING) << "msg_handler_telemetry unknown object type!";
@@ -2263,6 +2350,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
const auto tmp_eph_iter_cnav = d_internal_pvt_solver->gps_cnav_ephemeris_map.find(gnss_synchro.PRN);
const auto tmp_eph_iter_glo_gnav = d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(gnss_synchro.PRN);
const auto tmp_eph_iter_bds_dnav = d_internal_pvt_solver->beidou_dnav_ephemeris_map.find(gnss_synchro.PRN);
const auto tmp_eph_iter_bds_cnav1 = d_internal_pvt_solver->beidou_cnav1_ephemeris_map.find(gnss_synchro.PRN);
bool store_valid_observable = false;
@@ -2363,6 +2451,14 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
store_valid_observable = true;
}
}
if (!d_osnma_strict && tmp_eph_iter_bds_cnav1 != d_internal_pvt_solver->beidou_cnav1_ephemeris_map.cend())
{
const uint32_t prn_aux = tmp_eph_iter_bds_cnav1->second.PRN;
if ((prn_aux == gnss_synchro.PRN) && (std::string(gnss_synchro.Signal, 2) == std::string("1D")))
{
store_valid_observable = true;
}
}
if (std::string(gnss_synchro.Signal, 2) == std::string("E6"))
{
if (d_osnma_strict)
@@ -2385,33 +2481,36 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
{
try
{
if (d_internal_pvt_solver->gps_ephemeris_map.empty() == false)
// Keep track of locking time. The ephemeris maps are keyed by
// PRN only, so gate on the observable's system to avoid
// cross-constellation PRN collisions (e.g., a BeiDou C19
// observable touching the GPS G19 lock-time slot).
switch (gnss_synchro.System)
{
case 'G':
if (tmp_eph_iter_gps != d_internal_pvt_solver->gps_ephemeris_map.cend())
{
d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time
d_rtcm_printer->lock_time(tmp_eph_iter_gps->second, gnss_synchro.RX_time, gnss_synchro);
}
}
if (d_internal_pvt_solver->galileo_ephemeris_map.empty() == false)
{
if (tmp_eph_iter_gal != d_internal_pvt_solver->galileo_ephemeris_map.cend())
{
d_rtcm_printer->lock_time(d_internal_pvt_solver->galileo_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time
}
}
if (d_internal_pvt_solver->gps_cnav_ephemeris_map.empty() == false)
{
if (tmp_eph_iter_cnav != d_internal_pvt_solver->gps_cnav_ephemeris_map.cend())
{
d_rtcm_printer->lock_time(d_internal_pvt_solver->gps_cnav_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time
d_rtcm_printer->lock_time(tmp_eph_iter_cnav->second, gnss_synchro.RX_time, gnss_synchro);
}
}
if (d_internal_pvt_solver->glonass_gnav_ephemeris_map.empty() == false)
{
break;
case 'E':
if (tmp_eph_iter_gal != d_internal_pvt_solver->galileo_ephemeris_map.cend())
{
d_rtcm_printer->lock_time(tmp_eph_iter_gal->second, gnss_synchro.RX_time, gnss_synchro);
}
break;
case 'R':
if (tmp_eph_iter_glo_gnav != d_internal_pvt_solver->glonass_gnav_ephemeris_map.cend())
{
d_rtcm_printer->lock_time(d_internal_pvt_solver->glonass_gnav_ephemeris_map.find(gnss_synchro.PRN)->second, gnss_synchro.RX_time, gnss_synchro); // keep track of locking time
d_rtcm_printer->lock_time(tmp_eph_iter_glo_gnav->second, gnss_synchro.RX_time, gnss_synchro);
}
break;
default:
break;
}
}
catch (const boost::exception& ex)
@@ -85,7 +85,7 @@ rtklib_pvt_gs_sptr rtklib_make_pvt_gs(uint32_t nchannels,
class rtklib_pvt_gs : public gr::sync_block
{
public:
~rtklib_pvt_gs(); //!< Default destructor
~rtklib_pvt_gs() override; //!< Default destructor
/*!
* \brief Get latest set of GPS ephemeris from PVT block
@@ -133,7 +133,7 @@ public:
time_t* UTC_time) const;
int work(int noutput_items, gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items); //!< PVT Signal Processing
gr_vector_void_star& output_items) override; //!< PVT Signal Processing
private:
friend rtklib_pvt_gs_sptr rtklib_make_pvt_gs(uint32_t nchannels,
@@ -243,6 +243,10 @@ private:
const size_t d_beidou_dnav_iono_sptr_type_hash_code;
const size_t d_beidou_dnav_utc_model_sptr_type_hash_code;
const size_t d_beidou_dnav_almanac_sptr_type_hash_code;
const size_t d_beidou_cnav1_ephemeris_sptr_type_hash_code;
const size_t d_beidou_cnav1_iono_sptr_type_hash_code;
const size_t d_beidou_cnav1_utc_model_sptr_type_hash_code;
const size_t d_beidou_cnav1_page_data_sptr_type_hash_code;
const size_t d_galileo_has_data_sptr_type_hash_code;
const double d_rinex_version;
+269 -6
View File
@@ -17,8 +17,11 @@
*/
#include "rinex_printer.h"
#include "Beidou_CNAV1.h"
#include "Beidou_DNAV.h"
#include "GLONASS_L1_L2_CA.h"
#include "beidou_cnav1_ephemeris.h"
#include "beidou_cnav1_navigation_message.h"
#include "beidou_dnav_ephemeris.h"
#include "beidou_dnav_iono.h"
#include "beidou_dnav_utc_model.h"
@@ -103,6 +106,8 @@ std::string signal_flag_to_string(signal_flag flag)
return "B1";
case BDS_B3:
return "B3";
case BDS_B1C:
return "1D";
case QZS_J1:
return "J1";
case QZS_J5:
@@ -117,7 +122,7 @@ std::map<std::string, signal_flag> string_to_signal_flag_map()
{
std::map<std::string, signal_flag> convertion_map;
for (const auto flag : {GPS_1C, GPS_2S, GPS_L5, GAL_1B, GAL_E5a, GAL_E5b, GAL_E6, GLO_1G, GLO_2G, BDS_B1, BDS_B3, QZS_J1, QZS_J5})
for (const auto flag : {GPS_1C, GPS_2S, GPS_L5, GAL_1B, GAL_E5a, GAL_E5b, GAL_E6, GLO_1G, GLO_2G, BDS_B1, BDS_B3, BDS_B1C, QZS_J1, QZS_J5})
{
convertion_map[signal_flag_to_string(flag)] = flag;
}
@@ -137,7 +142,7 @@ std::map<char, std::set<signal_flag>> get_constel_signal_flags(const Signal_Enab
{
std::map<char, std::set<signal_flag>> constel_signal_flags;
for (const auto& it : std::map<char, std::set<signal_flag>>{{'G', {GPS_1C, GPS_2S, GPS_L5}}, {'E', {GAL_1B, GAL_E5a, GAL_E5b, GAL_E6}}, {'R', {GLO_1G, GLO_2G}}, {'C', {BDS_B1, BDS_B3}}, {'J', {QZS_J1, QZS_J5}}})
for (const auto& it : std::map<char, std::set<signal_flag>>{{'G', {GPS_1C, GPS_2S, GPS_L5}}, {'E', {GAL_1B, GAL_E5a, GAL_E5b, GAL_E6}}, {'R', {GLO_1G, GLO_2G}}, {'C', {BDS_B1, BDS_B3, BDS_B1C}}, {'J', {QZS_J1, QZS_J5}}})
{
for (const auto flag : it.second)
{
@@ -470,6 +475,7 @@ std::map<std::string, std::string> getObservationCodes()
{"BEIDOU_B1_I", "1I"},
{"BEIDOU_B1_Q", "1Q"},
{"BEIDOU_B1_IQ", "1X"},
{"BEIDOU_B1C_D", "1D"},
{"BEIDOU_B1_I_RINEX4", "2I"},
{"BEIDOU_B1_Q_RINEX4", "2Q"},
{"BEIDOU_B1_IQ_RINEX4", "2X"},
@@ -1650,6 +1656,13 @@ std::string get_beidou_time_corr_line(const Beidou_Dnav_Utc_Model& utc_model)
}
std::string get_beidou_time_corr_line(const Beidou_Cnav1_Utc_Model& utc_model)
{
// Unlike DNAV, B-CNAV1 broadcasts the reference time and full BDT week.
return get_time_corr_line("BDUT", utc_model.A0, utc_model.A1, &utc_model.tot, &utc_model.WN_t);
}
std::string get_glonass_time_corr_line(const Glonass_Gnav_Utc_Model& utc_model)
{
// The GLUT correction is defined as -TauC (see RINEX specification, Table A5)
@@ -1735,6 +1748,18 @@ std::string get_leap_second_line(const Beidou_Dnav_Utc_Model& utc_model, const B
}
std::string get_leap_second_line(const Beidou_Cnav1_Utc_Model& utc_model)
{
// B-CNAV1 broadcasts BDT-UTC, a full 13-bit BDT week, and day 0-6;
// RINEX requires GPS-UTC, a continuous GPS week, and day 1-7.
const int32_t gps_week_lsf = utc_model.WN_LSF + BEIDOU_DNAV_BDT2GPST_WEEK_NUM_OFFSET;
const int32_t gps_delta_tls = utc_model.delta_t_LS + BEIDOU_DNAV_BDT2GPST_LEAP_SEC_OFFSET;
const int32_t gps_delta_tlsf = utc_model.delta_t_LSF + BEIDOU_DNAV_BDT2GPST_LEAP_SEC_OFFSET;
const int32_t gps_day_number = utc_model.DN + 1;
return get_leap_second_line(gps_delta_tls, gps_delta_tlsf, gps_week_lsf, gps_day_number);
}
void write_two_digits_string(const std::string& two_digit_string, bool remove_leading_zero, std::string& line)
{
if (remove_leading_zero && boost::lexical_cast<int32_t>(two_digit_string) < 10)
@@ -2078,6 +2103,31 @@ void add_ion_klobuchar_record(std::fstream& out, char sys_char, const std::strin
}
/*
* RINEX 4 Ionosphere (ION) BDGIM model data record
* (see RINEX 4.02 specification, Table A37)
*/
void add_ion_bdgim_record(std::fstream& out, char sys_char,
const boost::posix_time::ptime& transmit_time,
double alpha1, double alpha2, double alpha3, double alpha4, double alpha5,
double alpha6, double alpha7, double alpha8, double alpha9)
{
out << get_nav_record_header_line("ION", sys_char, 0, "CNVX") << '\n';
std::string line;
line += std::string(4, ' ');
line += get_v4_epoch_string(transmit_time);
line += doub2e(alpha1);
line += doub2e(alpha2);
line += doub2e(alpha3);
lengthCheck(line);
out << line << '\n';
out << get_nav_broadcast_orbit(&alpha4, &alpha5, &alpha6, &alpha7, 4) << '\n';
out << get_nav_broadcast_orbit(&alpha8, &alpha9, nullptr, nullptr, 4) << '\n';
}
/*
* RINEX 4 Ionosphere (ION) NeQuick-G model data record
* (see RINEX 4.02 specification, Table A36)
@@ -2309,6 +2359,7 @@ void add_obs_sys_obs_type_beidou(std::fstream& out,
{
const std::map<uint32_t, std::string> signal_to_code_map = {
{BDS_B1, version == 4 ? "BEIDOU_B1_I_RINEX4" : "BEIDOU_B1_I"},
{BDS_B1C, "BEIDOU_B1C_D"},
{BDS_B3, "BEIDOU_B3_I"},
};
@@ -2607,7 +2658,9 @@ Rinex_Printer::Rinex_Printer(uint32_t signal_enabled_flags,
d_rinex_header_gps_updated(!d_flags.has_gps),
d_rinex_header_galileo_updated(!d_flags.has_galileo),
d_rinex_header_glonass_updated(!d_flags.has_glonass),
d_rinex_header_beidou_iono_updated(!d_flags.has_beidou),
// Klobuchar comes from DNAV only; B1C-only configs get BDGIM,
// which has no RINEX 3 header representation.
d_rinex_header_beidou_iono_updated(!d_flags.check_any_enabled(BDS_B1, BDS_B3)),
d_rinex_header_beidou_time_updated(!d_flags.has_beidou),
d_rinex_header_written(false)
{
@@ -2697,12 +2750,14 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
const auto gps_cnav_ephemeris_iter = pvt_solver->gps_cnav_ephemeris_map.cbegin();
const auto glonass_gnav_ephemeris_iter = pvt_solver->glonass_gnav_ephemeris_map.cbegin();
const auto beidou_dnav_ephemeris_iter = pvt_solver->beidou_dnav_ephemeris_map.cbegin();
const auto beidou_cnav1_ephemeris_iter = pvt_solver->beidou_cnav1_ephemeris_map.cbegin();
const bool has_gps_lnav_eph = !pvt_solver->gps_ephemeris_map.empty();
const bool has_gps_cnav_eph = !pvt_solver->gps_cnav_ephemeris_map.empty();
const bool has_galileo_eph = !pvt_solver->galileo_ephemeris_map.empty();
const bool has_glonass_eph = !pvt_solver->glonass_gnav_ephemeris_map.empty();
const bool has_beidou_dnav_eph = !pvt_solver->beidou_dnav_ephemeris_map.empty();
const bool has_beidou_cnav1_eph = !pvt_solver->beidou_cnav1_ephemeris_map.empty();
// We require at least one ephemeris for an active signal of a constellation
// Note: this is currently required because ephemeris are used when creating the headers,
@@ -2725,7 +2780,7 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
{
return;
}
if (d_flags.has_beidou && !has_beidou_dnav_eph)
if (d_flags.has_beidou && !has_beidou_dnav_eph && !has_beidou_cnav1_eph)
{
return;
}
@@ -2762,7 +2817,14 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
}
else if (d_flags.has_beidou)
{
system_time = Rinex_Printer::compute_BDS_time(beidou_dnav_ephemeris_iter->second, rx_time);
if (has_beidou_cnav1_eph)
{
system_time = Rinex_Printer::compute_BDS_time(beidou_cnav1_ephemeris_iter->second, rx_time);
}
else
{
system_time = Rinex_Printer::compute_BDS_time(beidou_dnav_ephemeris_iter->second, rx_time);
}
seconds = fmod(rx_time, 60);
system_time_str = "BDT";
}
@@ -2888,20 +2950,30 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
if (d_flags.has_beidou)
{
const auto& utc_model = pvt_solver->beidou_dnav_utc_model;
const auto& cnav1_utc_model = pvt_solver->beidou_cnav1_utc_model;
if (d_version == 3 && pvt_solver->beidou_dnav_iono.valid)
{
iono_lines.emplace_back(get_beidou_iono_alpha_line(pvt_solver->beidou_dnav_iono));
iono_lines.emplace_back(get_beidou_iono_beta_line(pvt_solver->beidou_dnav_iono));
}
// BDGIM (B1C-only) has no RINEX 3 header representation.
if (d_version == 3 && utc_model.valid)
{
time_corr_lines.emplace_back(get_beidou_time_corr_line(utc_model));
}
else if (d_version == 3 && cnav1_utc_model.valid)
{
time_corr_lines.emplace_back(get_beidou_time_corr_line(cnav1_utc_model));
}
if (system_time_str == "BDT" && utc_model.valid)
{
leap_second_line = get_leap_second_line(utc_model, beidou_dnav_ephemeris_iter->second);
}
else if (system_time_str == "BDT" && cnav1_utc_model.valid)
{
leap_second_line = get_leap_second_line(cnav1_utc_model);
}
}
rinex_obs_header(obsFile, system_time_str, system_time, seconds);
@@ -2936,6 +3008,10 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
{
log_rinex_nav_bds_dnav(pvt_solver->beidou_dnav_ephemeris_map);
}
if (has_beidou_cnav1_eph)
{
log_rinex_nav_bds_cnav1(pvt_solver->beidou_cnav1_ephemeris_map, pvt_solver->beidou_cnav1_page_data_map);
}
d_rinex_header_written = true;
}
@@ -3021,6 +3097,16 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver,
leap_second_line = get_leap_second_line(pvt_solver->beidou_dnav_utc_model, beidou_dnav_ephemeris_iter->second);
}
}
else if (!d_rinex_header_beidou_time_updated && !d_flags.check_any_enabled(BDS_B1, BDS_B3) && pvt_solver->beidou_cnav1_utc_model.valid)
{
nav_header_info.emplace_back("BDUT", "TIME SYSTEM CORR", get_beidou_time_corr_line(pvt_solver->beidou_cnav1_utc_model));
d_rinex_header_beidou_time_updated = true;
if (system_time_str == "BDT")
{
leap_second_line = get_leap_second_line(pvt_solver->beidou_cnav1_utc_model);
}
}
if (nav_header_info.empty())
{
@@ -3357,12 +3443,47 @@ void Rinex_Printer::log_rinex_nav_v4_aux_records(const Rtklib_Solver* pvt_solver
leap_second_line = get_leap_second_line(utc_model, eph);
}
}
if (d_flags.check_any_enabled(BDS_B1C) && pvt_solver->beidou_cnav1_iono.valid && !pvt_solver->beidou_cnav1_ephemeris_map.empty())
{
const auto& eph = pvt_solver->beidou_cnav1_ephemeris_map.cbegin()->second;
const auto& iono = pvt_solver->beidou_cnav1_iono;
const std::string signature = make_record_signature(
iono.alpha1, iono.alpha2, iono.alpha3, iono.alpha4, iono.alpha5,
iono.alpha6, iono.alpha7, iono.alpha8, iono.alpha9);
if (signature != d_last_bds_cnav1_iono_signature)
{
add_ion_bdgim_record(out, satelliteSystem.at("Beidou"), Rinex_Printer::compute_BDS_time(eph, rx_time),
iono.alpha1, iono.alpha2, iono.alpha3, iono.alpha4, iono.alpha5,
iono.alpha6, iono.alpha7, iono.alpha8, iono.alpha9);
d_last_bds_cnav1_iono_signature = signature;
records_written = true;
}
}
if (d_flags.check_any_enabled(BDS_B1C) && pvt_solver->beidou_cnav1_utc_model.valid)
{
const auto& utc_model = pvt_solver->beidou_cnav1_utc_model;
const std::string signature = make_record_signature(utc_model.A0, utc_model.A1, utc_model.A2, utc_model.tot, utc_model.WN_t);
if (signature != d_last_bds_cnav1_sto_signature)
{
// B-CNAV1 provides the full 13-bit BDT reference week WN_t.
const boost::posix_time::ptime ref_epoch(boost::gregorian::date(2006, 1, 1),
boost::posix_time::seconds(static_cast<int64_t>(utc_model.WN_t) * 604800LL + static_cast<int64_t>(utc_model.tot)));
add_sto_record(out, satelliteSystem.at("Beidou"), "CNVX", ref_epoch,
"BDUT", "UTC(NTSC)", tow, utc_model.A0, utc_model.A1, utc_model.A2);
d_last_bds_cnav1_sto_signature = signature;
records_written = true;
}
if (system_time_str == "BDT" && leap_second_line.empty())
{
leap_second_line = get_leap_second_line(utc_model);
}
}
if (!leap_second_line.empty() && leap_second_line != d_last_leap_second_line)
{
update_obs_header(obsFile, leap_second_line);
update_nav_header_from_info(navFile, navfilename, {NavHeaderInfo("", "LEAP SECONDS", leap_second_line)});
d_last_leap_second_line = leap_second_line;
d_last_leap_second_line = std::move(leap_second_line);
records_written = true;
}
@@ -3801,6 +3922,139 @@ void Rinex_Printer::log_rinex_nav_bds_dnav(const std::map<int32_t, Beidou_Dnav_E
}
void Rinex_Printer::log_rinex_nav_bds_cnav1(const std::map<int32_t, Beidou_Cnav1_Ephemeris>& new_bds_eph,
const std::map<int32_t, Bds3_B1c_PageData>& bds_page_data)
{
auto& out = navFile;
const auto& sys_char = satelliteSystem.at("Beidou");
for (const auto& bds_ephemeris_iter : new_bds_eph)
{
const auto& eph = bds_ephemeris_iter.second;
const boost::posix_time::ptime p_utc_time = Rinex_Printer::compute_BDS_time(eph, eph.toc);
if (d_version == 4)
{
// B-CNAV1 EPH record, RINEX 4.02 Table A24 (CNV1).
out << get_nav_record_header_line("EPH", sys_char, eph.PRN, "CNV1") << '\n';
out << get_nav_sv_epoch_svclk_line(p_utc_time, sys_char, eph.PRN, eph.af0, eph.af1, eph.af2, d_version) << '\n';
// -------- BROADCAST ORBIT - 1: ADOT, Crs, Delta_n0, M0
out << get_nav_broadcast_orbit(&eph.Adot, &eph.Crs, &eph.delta_n, &eph.M_0, d_version) << '\n';
// -------- BROADCAST ORBIT - 2: Cuc, e, Cus, sqrt(A0)
const double sqrt_a0 = std::sqrt(eph.A0);
out << get_nav_broadcast_orbit(&eph.Cuc, &eph.ecc, &eph.Cus, &sqrt_a0, d_version) << '\n';
// -------- BROADCAST ORBIT - 3: ToE, Cic, OMEGA0, Cis
const auto toe_d = static_cast<double>(eph.toe);
out << get_nav_broadcast_orbit(&toe_d, &eph.Cic, &eph.OMEGA_0, &eph.Cis, d_version) << '\n';
// -------- BROADCAST ORBIT - 4: i0, Crc, omega, OMEGA_DOT
out << get_nav_broadcast_orbit(&eph.i_0, &eph.Crc, &eph.omega, &eph.OMEGAdot, d_version) << '\n';
// The stored page is the last one decoded, and each page type
// carries a different SISAI subset (ICD §6.2.3): page 1 has
// SISAI_oe and the SISAI_OC group, pages 2/4 only SISAI_OC,
// page 3 only SISAI_oe. The 22-bit SISAI_OC group packs
// t_op(11)/SISAI_ocb(5)/SISAI_oc1(3)/SISAI_oc2(3). Fields the
// stored page does not carry are left blank.
double top_s = 0.0;
double sisai_ocb = 0.0;
double sisai_oc1 = 0.0;
double sisai_oc2 = 0.0;
double sisai_oe = 0.0;
double sismai = 0.0;
double integrity_flags = 0.0;
const double* top_p = nullptr;
const double* sisai_ocb_p = nullptr;
const double* sisai_oc1_p = nullptr;
const double* sisai_oc2_p = nullptr;
const double* sisai_oe_p = nullptr;
const double* sismai_p = nullptr;
const double* integrity_flags_p = nullptr;
const auto page_it = bds_page_data.find(static_cast<int32_t>(eph.PRN));
if (page_it != bds_page_data.cend())
{
const auto& page = page_it->second;
const int32_t page_id = page.common.page_id;
if (page_id == 1 || page_id == 2 || page_id == 4)
{
const auto sisai_oc = static_cast<uint32_t>(page.sisaioc);
top_s = static_cast<double>((sisai_oc >> 11U) & 0x7FFU) * BEIDOU_CNAV1_TOE_TOC_LSB;
sisai_ocb = static_cast<double>((sisai_oc >> 6U) & 0x1FU);
sisai_oc1 = static_cast<double>((sisai_oc >> 3U) & 0x7U);
sisai_oc2 = static_cast<double>(sisai_oc & 0x7U);
top_p = &top_s;
sisai_ocb_p = &sisai_ocb;
sisai_oc1_p = &sisai_oc1;
sisai_oc2_p = &sisai_oc2;
}
if (page_id == 1 || page_id == 3)
{
sisai_oe = static_cast<double>(page.sisaioe);
sisai_oe_p = &sisai_oe;
}
sismai = static_cast<double>(page.common.sismai);
sismai_p = &sismai;
integrity_flags = static_cast<double>((page.common.dif ? 4 : 0) + (page.common.sif ? 2 : 0) + (page.common.aif ? 1 : 0));
integrity_flags_p = &integrity_flags;
}
// -------- BROADCAST ORBIT - 5: IDOT, Delta_n0_dot, SatType, t_op
const auto sat_type_d = static_cast<double>(eph.sat_type);
const double* sat_type_p = (eph.sat_type > 0) ? &sat_type_d : nullptr; // 0 = reserved
out << get_nav_broadcast_orbit(&eph.idot, &eph.delta_ndot, sat_type_p, top_p, d_version) << '\n';
// -------- BROADCAST ORBIT - 6: SISAI_oe, SISAI_ocb, SISAI_oc1, SISAI_oc2
out << get_nav_broadcast_orbit(sisai_oe_p, sisai_ocb_p, sisai_oc1_p, sisai_oc2_p, d_version) << '\n';
// -------- BROADCAST ORBIT - 7: ISC_B1Cd, spare, TGD_B1Cp, TGD_B2ap
out << get_nav_broadcast_orbit(&eph.ISC_B1Cd, nullptr, &eph.TGD_B1Cp, &eph.TGD_B2ap, d_version) << '\n';
// -------- BROADCAST ORBIT - 8: SISMAI, Health, B1C integrity flags, IODC
const auto health_d = static_cast<double>(eph.hs);
out << get_nav_broadcast_orbit(sismai_p, &health_d, integrity_flags_p, &eph.IODC, d_version) << '\n';
// -------- BROADCAST ORBIT - 9: t_tm, spare, spare, IODE
const auto ttm_d = static_cast<double>(eph.tow);
out << get_nav_broadcast_orbit(&ttm_d, nullptr, nullptr, &eph.IODE, d_version) << '\n';
continue;
}
// RINEX 3: D1-style stand-in (CNAV1 has no representation before
// RINEX 4). IODE/IODC land in AODE/AODC, TGD_B1Cp/TGD_B2ap in
// TGD1/TGD2, and the Adot term is dropped (no D1 field).
out << get_nav_sv_epoch_svclk_line(p_utc_time, sys_char, eph.PRN, eph.af0, eph.af1, eph.af2, d_version) << '\n';
const auto iode_d = eph.IODE;
out << get_nav_broadcast_orbit(&iode_d, &eph.Crs, &eph.delta_n, &eph.M_0, d_version) << '\n';
// B-CNAV1 broadcasts the semi-major axis (A0 at toe); the D1 field
// expects its square root.
const double sqrt_a = std::sqrt(eph.A0);
out << get_nav_broadcast_orbit(&eph.Cuc, &eph.ecc, &eph.Cus, &sqrt_a, d_version) << '\n';
const auto toe_d = static_cast<double>(eph.toe);
out << get_nav_broadcast_orbit(&toe_d, &eph.Cic, &eph.OMEGA_0, &eph.Cis, d_version) << '\n';
out << get_nav_broadcast_orbit(&eph.i_0, &eph.Crc, &eph.omega, &eph.OMEGAdot, d_version) << '\n';
const auto wn_d = static_cast<double>(eph.WN);
out << get_nav_broadcast_orbit(&eph.idot, nullptr, &wn_d, nullptr, d_version) << '\n';
// Acc left 0; health = SF3 HS; TGD1/TGD2 hold TGD_B1Cp/TGD_B2ap.
const double sv_accuracy = 0.0;
const auto health_d = static_cast<double>(eph.hs);
out << get_nav_broadcast_orbit(&sv_accuracy, &health_d, &eph.TGD_B1Cp, &eph.TGD_B2ap, d_version) << '\n';
const auto tow_d = static_cast<double>(eph.tow);
const auto iodc_d = eph.IODC;
out << get_nav_broadcast_orbit(&tow_d, &iodc_d, nullptr, nullptr, d_version) << '\n';
}
}
void Rinex_Printer::rinex_nav_header(std::fstream& out,
const std::vector<std::string>& iono_lines,
const std::vector<std::string>& time_corr_lines,
@@ -4145,6 +4399,15 @@ boost::posix_time::ptime Rinex_Printer::compute_BDS_time(const Beidou_Dnav_Ephem
}
boost::posix_time::ptime Rinex_Printer::compute_BDS_time(const Beidou_Cnav1_Ephemeris& eph, double obs_time) const
{
const double bds_t = obs_time;
const boost::posix_time::time_duration t = boost::posix_time::milliseconds(static_cast<int64_t>((bds_t + 604800 * static_cast<double>(eph.WN % 8192)) * 1000));
boost::posix_time::ptime p_time(boost::gregorian::date(2006, 1, 1), t);
return p_time;
}
boost::posix_time::ptime Rinex_Printer::compute_GPS_time(const Gps_Ephemeris& eph, double obs_time) const
{
// The RINEX v2.11 v3.00 format uses GPS time for the observations epoch, not UTC time, thus, no leap seconds needed here.
+17
View File
@@ -55,6 +55,8 @@
* \{ */
struct Bds3_B1c_PageData;
class Beidou_Cnav1_Ephemeris;
class Beidou_Dnav_Ephemeris;
class Beidou_Dnav_Iono;
class Beidou_Dnav_Utc_Model;
@@ -134,6 +136,18 @@ public:
*/
void log_rinex_nav_bds_dnav(const std::map<int32_t, Beidou_Dnav_Ephemeris>& new_bds_eph);
/*!
* \brief Print RINEX navigation records for BeiDou B-CNAV1 ephemerides.
*
* RINEX 4 writes proper CNV1 records (Table A24), taking SISAI/SISMAI/
* integrity and t_op from the last decoded SF3 page when it carries them.
* RINEX 3 has no CNAV1 representation, so records are written as D1-style
* stand-ins: sqrt(A0) in the sqrtA field with the Adot term dropped,
* IODE/IODC in AODE/AODC, and TGD_B1Cp/TGD_B2ap mapped into TGD1/TGD2.
*/
void log_rinex_nav_bds_cnav1(const std::map<int32_t, Beidou_Cnav1_Ephemeris>& new_bds_eph,
const std::map<int32_t, Bds3_B1c_PageData>& bds_page_data);
/*!
* \brief Returns true is the RINEX file headers are already written
*/
@@ -200,6 +214,7 @@ private:
* \param obs_time Observation time in BDT seconds of week
*/
boost::posix_time::ptime compute_BDS_time(const Beidou_Dnav_Ephemeris& eph, double obs_time) const;
boost::posix_time::ptime compute_BDS_time(const Beidou_Cnav1_Ephemeris& eph, double obs_time) const;
/*
* Computes the UTC time and returns a boost::posix_time::ptime object
@@ -267,6 +282,8 @@ private:
std::string d_last_glonass_sto_signature;
std::string d_last_beidou_iono_signature;
std::string d_last_beidou_sto_signature;
std::string d_last_bds_cnav1_iono_signature;
std::string d_last_bds_cnav1_sto_signature;
std::string d_last_leap_second_line;
const int32_t d_ref_gps_week;
+105 -19
View File
@@ -31,8 +31,10 @@
* -----------------------------------------------------------------------*/
#include "rtklib_solver.h"
#include "Beidou_CNAV1.h"
#include "Beidou_DNAV.h"
#include "Galileo_CNAV.h"
#include "gnss_obs_codes.h"
#include "gnss_sdr_filesystem.h"
#include "matlab_writter_helper.h"
#include "rtklib_rtkpos.h"
@@ -40,9 +42,9 @@
#include <algorithm>
#include <cmath>
#include <exception>
#include <iostream>
#include <iterator>
#include <limits>
#include <utility>
#include <vector>
#if USE_GLOG_AND_GFLAGS
@@ -69,10 +71,12 @@ Rtklib_Solver::Rtklib_Solver(const rtk_t &rtk,
d_rtklib_freq_index[1] = 1;
d_rtklib_freq_index[2] = 2;
// BDS: B1I and B1C share slot 0 (prefer-B1C XOR). Override lam[0] for B1C (FREQ1).
d_rtklib_band_index["1G"] = 0;
d_rtklib_band_index["1C"] = 0;
d_rtklib_band_index["1B"] = 0;
d_rtklib_band_index["B1"] = 0;
d_rtklib_band_index["1D"] = 0;
d_rtklib_band_index["B3"] = 2;
d_rtklib_band_index["2G"] = 1;
d_rtklib_band_index["2S"] = 1;
@@ -1775,17 +1779,34 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
}
case 'C':
{
// BEIDOU B1I
// - find the ephemeris for the current BEIDOU SV observation. The SV PRN ID is the map key
// BeiDou B1I / B1C / B3I (prefer B1C when CNAV1 + B1C obs are both present)
const std::string sig_(gnss_observables_iter->second.Signal);
const int bds_sat = static_cast<int>(gnss_observables_iter->second.PRN + NSATGPS + NSATGLO + NSATGAL + NSATQZS);
if (sig_ == "B1")
{
bool prefer_b1c = false;
for (const auto &obs_pair : gnss_observables_map)
{
if (obs_pair.second.System == 'C' &&
obs_pair.second.PRN == gnss_observables_iter->second.PRN &&
std::string(obs_pair.second.Signal, 2) == "1D" &&
beidou_cnav1_ephemeris_map.find(obs_pair.second.PRN) != beidou_cnav1_ephemeris_map.cend())
{
prefer_b1c = true;
break;
}
}
if (prefer_b1c)
{
DLOG(INFO) << "Skip B1I for PRN " << gnss_observables_iter->second.PRN
<< " (B1C present; prefer CNAV1)";
break;
}
beidou_ephemeris_iter = beidou_dnav_ephemeris_map.find(gnss_observables_iter->second.PRN);
if (beidou_ephemeris_iter != beidou_dnav_ephemeris_map.cend())
{
// convert ephemeris from GNSS-SDR class to RTKLIB structure
eph_data[valid_obs] = eph_to_rtklib(beidou_ephemeris_iter->second);
// convert observation from GNSS-SDR class to RTKLIB structure
obsd_t newobs{};
d_obs_data[valid_obs + glo_valid_obs] = insert_obs_to_rtklib(newobs,
gnss_observables_iter->second,
@@ -1793,12 +1814,35 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
d_rtklib_band_index.at(sig_));
valid_obs++;
}
else // the ephemeris are not available for this SV
else
{
DLOG(INFO) << "No ephemeris data for SV " << gnss_observables_iter->first;
}
}
// BeiDou B3
if (sig_ == "1D")
{
const auto cnav1_iter = beidou_cnav1_ephemeris_map.find(gnss_observables_iter->second.PRN);
if (cnav1_iter != beidou_cnav1_ephemeris_map.cend())
{
eph_data[valid_obs] = eph_to_rtklib(cnav1_iter->second);
const auto page_it = beidou_cnav1_page_data_map.find(cnav1_iter->second.PRN);
if (page_it != beidou_cnav1_page_data_map.cend())
{
eph_data[valid_obs].svh = page_it->second.common.hs;
}
obsd_t newobs{};
d_obs_data[valid_obs + glo_valid_obs] = insert_obs_to_rtklib(newobs,
gnss_observables_iter->second,
cnav1_iter->second.WN + BEIDOU_DNAV_BDT2GPST_WEEK_NUM_OFFSET,
d_rtklib_band_index.at(sig_));
valid_obs++;
}
else
{
DLOG(INFO) << "No B-CNAV1 ephemeris data for SV " << gnss_observables_iter->second.PRN;
}
}
// BeiDou B3: merge with DNAV/B1I only
if (sig_ == "B3")
{
beidou_ephemeris_iter = beidou_dnav_ephemeris_map.find(gnss_observables_iter->second.PRN);
@@ -1807,22 +1851,23 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
bool found_B1I_obs = false;
for (int i = 0; i < valid_obs; i++)
{
if (eph_data[i].sat == (static_cast<int>(gnss_observables_iter->second.PRN + NSATGPS + NSATGLO + NSATGAL + NSATQZS)))
if (eph_data[i].sat == bds_sat && eph_data[i].code != BDS_EPH_SOURCE_CNAV1)
{
d_obs_data[i + glo_valid_obs] = insert_obs_to_rtklib(d_obs_data[i + glo_valid_obs],
gnss_observables_iter->second,
beidou_ephemeris_iter->second.WN + BEIDOU_DNAV_BDT2GPST_WEEK_NUM_OFFSET,
d_rtklib_band_index.at(sig_));
found_B1I_obs = true;
break;
const unsigned char c0 = d_obs_data[i + glo_valid_obs].code[0];
if (c0 == CODE_L2I || c0 == CODE_L1I)
{
d_obs_data[i + glo_valid_obs] = insert_obs_to_rtklib(d_obs_data[i + glo_valid_obs],
gnss_observables_iter->second,
beidou_ephemeris_iter->second.WN + BEIDOU_DNAV_BDT2GPST_WEEK_NUM_OFFSET,
d_rtklib_band_index.at(sig_));
found_B1I_obs = true;
break;
}
}
}
if (!found_B1I_obs)
{
// insert BeiDou B3I obs as new obs and also insert its ephemeris
// convert ephemeris from GNSS-SDR class to RTKLIB structure
eph_data[valid_obs] = eph_to_rtklib(beidou_ephemeris_iter->second);
// convert observation from GNSS-SDR class to RTKLIB structure
const auto default_code_ = static_cast<unsigned char>(CODE_NONE);
obsd_t newobs = {{0, 0}, '0', '0', {}, {},
{default_code_, default_code_, default_code_},
@@ -1834,7 +1879,7 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
valid_obs++;
}
}
else // the ephemeris are not available for this SV
else
{
DLOG(INFO) << "No ephemeris data for SV " << gnss_observables_iter->second.PRN;
}
@@ -1926,6 +1971,23 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
d_nav_data.ion_cmp[6] = beidou_dnav_iono.beta2;
d_nav_data.ion_cmp[7] = beidou_dnav_iono.beta3;
}
if (beidou_cnav1_iono.valid)
{
d_nav_data.ion_bdgim[0] = beidou_cnav1_iono.alpha1;
d_nav_data.ion_bdgim[1] = beidou_cnav1_iono.alpha2;
d_nav_data.ion_bdgim[2] = beidou_cnav1_iono.alpha3;
d_nav_data.ion_bdgim[3] = beidou_cnav1_iono.alpha4;
d_nav_data.ion_bdgim[4] = beidou_cnav1_iono.alpha5;
d_nav_data.ion_bdgim[5] = beidou_cnav1_iono.alpha6;
d_nav_data.ion_bdgim[6] = beidou_cnav1_iono.alpha7;
d_nav_data.ion_bdgim[7] = beidou_cnav1_iono.alpha8;
d_nav_data.ion_bdgim[8] = beidou_cnav1_iono.alpha9;
d_nav_data.ion_bdgim_valid = 1;
}
else
{
d_nav_data.ion_bdgim_valid = 0;
}
if (gps_utc_model.valid)
{
d_nav_data.utc_gps[0] = gps_utc_model.A0;
@@ -1981,6 +2043,16 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
// BeiDou message broadcasts BDT-UTC.
d_nav_data.leaps = beidou_dnav_utc_model.DeltaT_LS + BEIDOU_DNAV_BDT2GPST_LEAP_SEC_OFFSET;
}
else if (beidou_cnav1_utc_model.valid)
{
d_nav_data.utc_cmp[0] = beidou_cnav1_utc_model.A0;
d_nav_data.utc_cmp[1] = beidou_cnav1_utc_model.A1;
d_nav_data.utc_cmp[2] = static_cast<double>(beidou_cnav1_utc_model.tot);
d_nav_data.utc_cmp[3] = static_cast<double>(beidou_cnav1_utc_model.WN_t);
// RTKLIB stores the GPS-UTC leap-second count, whereas the
// BeiDou message broadcasts BDT-UTC.
d_nav_data.leaps = beidou_cnav1_utc_model.delta_t_LS + BEIDOU_DNAV_BDT2GPST_LEAP_SEC_OFFSET;
}
/* update carrier wave length using native function call in RTKlib */
for (int i = 0; i < MAXSAT; i++)
@@ -1995,7 +2067,21 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
update_galileo_observation_wavelengths(d_obs_data[i]);
}
result = rtkpos(&d_rtk, d_obs_data.data(), valid_obs + glo_valid_obs, &d_nav_data);
const int nobs_total = valid_obs + glo_valid_obs;
/* B1C on slot 0: override lam[0] to FREQ1 (satwavelen frq0 is B1I). */
for (int k = 0; k < nobs_total; k++)
{
if (satsys(d_obs_data[k].sat, nullptr) != SYS_BDS)
{
continue;
}
const unsigned char c0 = d_obs_data[k].code[0];
if (c0 == CODE_L1D || c0 == CODE_L1P)
{
d_nav_data.lam[d_obs_data[k].sat - 1][0] = SPEED_OF_LIGHT_M_S / FREQ1;
}
}
result = rtkpos(&d_rtk, d_obs_data.data(), nobs_total, &d_nav_data);
if (result == 0)
{
+8
View File
@@ -35,6 +35,10 @@
#define GNSS_SDR_RTKLIB_SOLVER_H
#include "beidou_cnav1_ephemeris.h"
#include "beidou_cnav1_iono.h"
#include "beidou_cnav1_navigation_message.h"
#include "beidou_cnav1_utc_model.h"
#include "beidou_dnav_almanac.h"
#include "beidou_dnav_ephemeris.h"
#include "beidou_dnav_iono.h"
@@ -127,6 +131,7 @@ public:
std::map<int, Gps_CNAV_Ephemeris> gps_cnav_ephemeris_map; //!< Map storing new GPS_CNAV_Ephemeris
std::map<int, Glonass_Gnav_Ephemeris> glonass_gnav_ephemeris_map; //!< Map storing new GLONASS GNAV Ephemeris
std::map<int, Beidou_Dnav_Ephemeris> beidou_dnav_ephemeris_map; //!< Map storing new BeiDou DNAV Ephmeris
std::map<int, Beidou_Cnav1_Ephemeris> beidou_cnav1_ephemeris_map; //!< Map storing BeiDou B-CNAV1 ephemeris
Galileo_Utc_Model galileo_utc_model;
Galileo_Iono galileo_iono;
@@ -152,7 +157,10 @@ public:
Beidou_Dnav_Utc_Model beidou_dnav_utc_model;
Beidou_Dnav_Iono beidou_dnav_iono;
Beidou_Cnav1_Iono beidou_cnav1_iono;
Beidou_Cnav1_Utc_Model beidou_cnav1_utc_model;
std::map<int, Beidou_Dnav_Almanac> beidou_dnav_almanac_map;
std::map<int, Bds3_B1c_PageData> beidou_cnav1_page_data_map;
private:
friend class GalileoEphemerisSourceTest_E6SlotsFollowRtklibGalileoPolicy_Test;
@@ -36,6 +36,7 @@ uint32_t flags_from_config(const ConfigurationInterface* configuration)
{GLO_2G, "Channels_2G.count"},
{BDS_B1, "Channels_B1.count"},
{BDS_B3, "Channels_B3.count"},
{BDS_B1C, "Channels_1D.count"},
{QZS_J1, "Channels_J1.count"},
{QZS_J5, "Channels_J5.count"}};
@@ -66,7 +67,7 @@ Signal_Enabled_Flags::Signal_Enabled_Flags(uint32_t flags_)
has_gps(check_any_enabled(GPS_1C, GPS_2S, GPS_L5)),
has_galileo(check_any_enabled(GAL_1B, GAL_E5a, GAL_E5b, GAL_E6)),
has_glonass(check_any_enabled(GLO_1G, GLO_2G)),
has_beidou(check_any_enabled(BDS_B1, BDS_B3)),
has_beidou(check_any_enabled(BDS_B1, BDS_B3, BDS_B1C)),
has_qzss(check_any_enabled(QZS_J1, QZS_J5)),
only_gps(has_gps && !(has_galileo || has_glonass || has_beidou || has_qzss)),
only_galileo(has_galileo && !(has_gps || has_glonass || has_beidou || has_qzss)),
@@ -1,5 +1,5 @@
/*!
* \file base_ca_pcps_acquisition.h
* \file pcps_acquisition_adapter.cc
* \brief Adapts a PCPS acquisition block to an AcquisitionInterface
* \authors <ul>
* <li> Mathieu Favreau, 2025. favreau.mathieu(at)hotmail.com
@@ -17,6 +17,7 @@
*/
#include "pcps_acquisition_adapter.h"
#include "Beidou_B1C.h"
#include "Beidou_B1I.h"
#include "Beidou_B3I.h"
#include "GLONASS_L1_L2_CA.h"
@@ -28,6 +29,7 @@
#include "Galileo_E5b.h"
#include "Galileo_E6.h"
#include "acq_conf.h"
#include "beidou_b1c_signal_replica.h"
#include "beidou_b1i_signal_replica.h"
#include "beidou_b3i_signal_replica.h"
#include "configuration_interface.h"
@@ -96,6 +98,8 @@ signal_info get_signal_info(signal_flag sig_flag)
return {BEIDOU_B1I_CODE_RATE_CPS, BEIDOU_B1I_OPT_ACQ_FS_SPS, BEIDOU_B1I_CODE_LENGTH_CHIPS, BEIDOU_B1I_CODE_PERIOD_MS};
case BDS_B3:
return {BEIDOU_B3I_CODE_RATE_CPS, BEIDOU_B3I_OPT_ACQ_FS_SPS, BEIDOU_B3I_CODE_LENGTH_CHIPS, BEIDOU_B3I_CODE_PERIOD_MS};
case BDS_B1C:
return {BEIDOU_B1C_CODE_RATE_CPS, BEIDOU_B1C_OPT_ACQ_FS_SPS, BEIDOU_B1C_CODE_LENGTH_CHIPS, BEIDOU_B1C_CODE_PERIOD_MS};
case QZS_J1:
return {QZSS_L1_CHIP_RATE, QZSS_L1_OPT_ACQ_FS_SPS, QZSS_L1_CODE_LENGTH, QZSS_L1_PERIOD_MS};
case QZS_J5:
@@ -174,6 +178,12 @@ void code_gen_complex_sampled(signal_flag sig_flag, const Acq_Conf& conf, const
case BDS_B3:
beidou_b3i_code_gen_complex_sampled(dest, gnss_synchro.PRN, sampling_freq, 0);
break;
case BDS_B1C:
{
const auto sig = conf.acquire_pilot ? std::array<char, 3>{'1', 'P', '\0'} : std::array<char, 3>{gnss_synchro.Signal[0], gnss_synchro.Signal[1], '\0'};
beidou_b1c_code_gen_complex_sampled(dest, sig, conf.qmboc, gnss_synchro.PRN, sampling_freq, 0, false);
}
break;
case QZS_J1:
qzss_l1_code_gen_complex_sampled(dest, gnss_synchro.PRN, sampling_freq);
break;
@@ -207,6 +217,12 @@ Acq_Conf get_acq_conf(const ConfigurationInterface* configuration, const std::st
acq_parameters.acquire_iq = configuration->property(role + ".acquire_iq", acq_parameters.acquire_iq);
}
if (sig_flag == BDS_B1C)
{
acq_parameters.acquire_pilot = configuration->property(role + ".acquire_pilot", true);
acq_parameters.qmboc = configuration->property(role + ".qmboc", true);
}
#if USE_GLOG_AND_GFLAGS
if (FLAGS_doppler_max != 0)
{
@@ -79,6 +79,7 @@ public:
bool acquire_pilot{false};
bool acquire_iq{false};
bool cboc{false};
bool qmboc{false};
int zero_padding{0};
uint32_t folding_factor{0};
+2
View File
@@ -9,6 +9,7 @@ add_subdirectory(rtklib)
set(GNSS_SPLIBS_SOURCES
beidou_b1i_signal_replica.cc
beidou_b1c_signal_replica.cc
beidou_b3i_signal_replica.cc
galileo_e1_signal_replica.cc
galileo_e5_signal_replica.cc
@@ -37,6 +38,7 @@ set(GNSS_SPLIBS_SOURCES
set(GNSS_SPLIBS_HEADERS
beidou_b1i_signal_replica.h
beidou_b1c_signal_replica.h
beidou_b3i_signal_replica.h
galileo_e1_signal_replica.h
galileo_e5_signal_replica.h
@@ -0,0 +1,301 @@
/*!
* \file beidou_b1c_signal_replica.cc
* \brief Library for BeiDou B1C signal replica generation
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* Primary codes loaded from precomputed hex tables (Galileo E1 style).
* Pilot secondary codes from per-PRN binary strings (Galileo E5a-Q style).
* BDS-SIS-ICD-B1C-1.0 §5.2.
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_b1c_signal_replica.h"
#include "Beidou_B1C.h"
#include "Beidou_B1C_codes.h"
#include "gnss_signal_replica.h"
#include <cmath>
#include <string>
#include <vector>
namespace
{
void hex_primary_to_chip_int(own::span<int32_t> dest, const char* hex_code)
{
int32_t index = 0;
for (size_t i = 0; i < BEIDOU_B1C_PRIMARY_CODE_STR_LENGTH - 1; i++)
{
std::array<int32_t, 4> bipolar{};
hex_to_binary_converter(bipolar, hex_code[i]);
for (int32_t j = 0; j < 4; j++)
{
dest[index++] = (bipolar[j] == 1) ? 0 : 1;
}
}
std::array<int32_t, 4> last{};
hex_to_binary_converter(last, hex_code[BEIDOU_B1C_PRIMARY_CODE_STR_LENGTH - 1]);
dest[index++] = (last[0] == 1) ? 0 : 1;
dest[index++] = (last[1] == 1) ? 0 : 1;
}
} // namespace
void beidou_b1c_code_gen_int(own::span<int32_t> dest, const std::array<char, 3>& signal_id, int32_t prn)
{
if (prn < 1 || prn > BEIDOU_B1C_NUMBER_OF_PRNS)
{
return;
}
const int32_t prn_index = prn - 1;
const std::string sig = signal_id.data();
const char* hex_code = nullptr;
if (sig.rfind("1P") != std::string::npos)
{
hex_code = BEIDOU_B1C_PILOT_PRIMARY_CODE[prn_index];
}
else
{
hex_code = BEIDOU_B1C_DATA_PRIMARY_CODE[prn_index];
}
hex_primary_to_chip_int(dest, hex_code);
}
void beidou_b1c_sinboc_11_gen_int(own::span<int32_t> dest, own::span<const int32_t> prn)
{
constexpr auto length_in = static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
const auto period = static_cast<uint32_t>(dest.size() / length_in);
for (uint32_t i = 0; i < length_in; i++)
{
for (uint32_t j = 0; j < (period / 2); j++)
{
dest[i * period + j] = prn[i];
}
for (uint32_t j = (period / 2); j < period; j++)
{
dest[i * period + j] = -prn[i];
}
}
}
void beidou_b1c_sinboc_61_gen_int(own::span<int32_t> dest, own::span<const int32_t> prn)
{
constexpr auto length_in = static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
const auto period = static_cast<uint32_t>(dest.size() / length_in);
for (uint32_t i = 0; i < length_in; i++)
{
for (uint32_t j = 0; j < period; j += 2)
{
dest[i * period + j] = prn[i];
}
for (uint32_t j = 1; j < period; j += 2)
{
dest[i * period + j] = -prn[i];
}
}
}
namespace
{
/*!
* QMBOC(6,1,4/33) pilot subcarrier (ICD eqs. 4-10/4-11):
* Re = BOC(6,1), Im = BOC(1,1), power ratio 4:29.
*/
void beidou_b1c_qmboc_gen_complex(own::span<std::complex<float>> dest, own::span<const int32_t> prn)
{
const auto code_length = dest.size();
const float alpha = std::sqrt(29.0F / 33.0F); // BOC(1,1) weight in sc_B1C_pilot
const float beta = std::sqrt(4.0F / 33.0F); // BOC(6,1) weight in sc_B1C_pilot
std::vector<int32_t> sinboc_11(code_length);
std::vector<int32_t> sinboc_61(code_length);
beidou_b1c_sinboc_11_gen_int(sinboc_11, prn);
beidou_b1c_sinboc_61_gen_int(sinboc_61, prn);
for (size_t i = 0; i < code_length; i++)
{
dest[i] = std::complex<float>(
beta * static_cast<float>(sinboc_61[i]),
alpha * static_cast<float>(sinboc_11[i]));
}
}
} // namespace
void beidou_b1c_code_gen_sinboc11_float(own::span<float> dest, const std::array<char, 3>& signal_id, uint32_t prn)
{
std::array<int32_t, 10230> primary_code{};
beidou_b1c_code_gen_int(primary_code, signal_id, static_cast<int32_t>(prn));
const auto code_length = static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
for (uint32_t i = 0; i < code_length; i++)
{
const float chip = primary_code[i] == 0 ? 1.0F : -1.0F;
dest[2 * i] = chip;
dest[2 * i + 1] = -chip;
}
}
void beidou_b1c_code_gen_float_sampled(own::span<float> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift, bool secondary_flag)
{
constexpr auto code_freq_basis = static_cast<int32_t>(BEIDOU_B1C_CODE_RATE_CPS);
const int32_t samples_per_chip = qmboc ? 12 : 2;
const uint32_t code_length = samples_per_chip * static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
const std::string b1c_signal = signal_id.data();
const bool is_pilot = b1c_signal.rfind("1P") != std::string::npos;
auto samples_per_code = static_cast<uint32_t>(static_cast<double>(sampling_freq) /
(static_cast<double>(code_freq_basis) / BEIDOU_B1C_CODE_LENGTH_CHIPS));
const uint32_t delay = ((static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS) - static_cast<int32_t>(chip_shift)) %
static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS)) *
samples_per_code / static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
std::array<int32_t, 10230> primary_code{};
beidou_b1c_code_gen_int(primary_code, signal_id, static_cast<int32_t>(prn));
std::array<int32_t, 10230> primary_bipolar{};
for (uint32_t i = 0; i < static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS); i++)
{
primary_bipolar[i] = primary_code[i] == 0 ? 1 : -1;
}
// Tracking path: real BOC(1,1) only (full QMBOC is complex).
std::vector<float> signal_b1c(code_length);
std::vector<int32_t> signal_b1c_int(code_length);
beidou_b1c_sinboc_11_gen_int(signal_b1c_int, primary_bipolar);
for (uint32_t ii = 0; ii < code_length; ++ii)
{
signal_b1c[ii] = static_cast<float>(signal_b1c_int[ii]);
}
if (sampling_freq != samples_per_chip * code_freq_basis)
{
std::vector<float> resampled_signal(samples_per_code);
resampler(signal_b1c, resampled_signal, static_cast<float>(samples_per_chip * code_freq_basis), sampling_freq);
signal_b1c = std::move(resampled_signal);
}
else
{
samples_per_code = code_length;
}
if (is_pilot && secondary_flag)
{
if (prn >= 1 && prn <= static_cast<uint32_t>(BEIDOU_B1C_NUMBER_OF_PRNS))
{
const int32_t prn_index = static_cast<int32_t>(prn) - 1;
const char* sec_code = BEIDOU_B1C_PILOT_SECONDARY_CODE[prn_index];
std::vector<float> signal_with_secondary(BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH * samples_per_code);
for (int32_t i = 0; i < BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH; i++)
{
const float sec_chip = sec_code[i] == '0' ? 1.0F : -1.0F;
for (uint32_t k = 0; k < samples_per_code; k++)
{
signal_with_secondary[static_cast<size_t>(i) * samples_per_code + k] = signal_b1c[k] * sec_chip;
}
}
samples_per_code *= static_cast<uint32_t>(BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH);
signal_b1c = std::move(signal_with_secondary);
}
}
for (uint32_t i = 0; i < samples_per_code; i++)
{
dest[(i + delay) % samples_per_code] = signal_b1c[i];
}
}
void beidou_b1c_code_gen_complex_sampled(own::span<std::complex<float>> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift, bool secondary_flag)
{
constexpr auto code_freq_basis = static_cast<int32_t>(BEIDOU_B1C_CODE_RATE_CPS);
const int32_t samples_per_chip = qmboc ? 12 : 2;
const uint32_t code_length = samples_per_chip * static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
const std::string b1c_signal = signal_id.data();
const bool is_pilot = b1c_signal.rfind("1P") != std::string::npos;
auto samples_per_code = static_cast<uint32_t>(static_cast<double>(sampling_freq) /
(static_cast<double>(code_freq_basis) / BEIDOU_B1C_CODE_LENGTH_CHIPS));
const uint32_t delay = ((static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS) - static_cast<int32_t>(chip_shift)) %
static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS)) *
samples_per_code / static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
std::array<int32_t, 10230> primary_code{};
beidou_b1c_code_gen_int(primary_code, signal_id, static_cast<int32_t>(prn));
std::array<int32_t, 10230> primary_bipolar{};
for (uint32_t i = 0; i < static_cast<uint32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS); i++)
{
primary_bipolar[i] = primary_code[i] == 0 ? 1 : -1;
}
std::vector<std::complex<float>> signal_b1c(code_length);
if (qmboc && is_pilot)
{
beidou_b1c_qmboc_gen_complex(signal_b1c, primary_bipolar);
}
else
{
std::vector<int32_t> signal_b1c_int(code_length);
beidou_b1c_sinboc_11_gen_int(signal_b1c_int, primary_bipolar);
for (uint32_t ii = 0; ii < code_length; ++ii)
{
signal_b1c[ii] = std::complex<float>(static_cast<float>(signal_b1c_int[ii]), 0.0F);
}
}
if (sampling_freq != samples_per_chip * code_freq_basis)
{
std::vector<std::complex<float>> resampled_signal(samples_per_code);
resampler(signal_b1c, resampled_signal, static_cast<float>(samples_per_chip * code_freq_basis),
static_cast<float>(sampling_freq));
signal_b1c = std::move(resampled_signal);
}
else
{
samples_per_code = code_length;
}
if (is_pilot && secondary_flag)
{
if (prn >= 1 && prn <= static_cast<uint32_t>(BEIDOU_B1C_NUMBER_OF_PRNS))
{
const int32_t prn_index = static_cast<int32_t>(prn) - 1;
const char* sec_code = BEIDOU_B1C_PILOT_SECONDARY_CODE[prn_index];
std::vector<std::complex<float>> signal_with_secondary(
static_cast<size_t>(BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH) * samples_per_code);
for (int32_t i = 0; i < BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH; i++)
{
const float sec_chip = sec_code[i] == '0' ? 1.0F : -1.0F;
for (uint32_t k = 0; k < samples_per_code; k++)
{
signal_with_secondary[static_cast<size_t>(i) * samples_per_code + k] =
signal_b1c[k] * sec_chip;
}
}
samples_per_code *= static_cast<uint32_t>(BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH);
signal_b1c = std::move(signal_with_secondary);
}
}
for (uint32_t i = 0; i < samples_per_code; i++)
{
dest[(i + delay) % samples_per_code] = signal_b1c[i];
}
}
void beidou_b1c_code_gen_complex_sampled(own::span<std::complex<float>> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift)
{
beidou_b1c_code_gen_complex_sampled(dest, signal_id, qmboc, prn, sampling_freq, chip_shift, false);
}
@@ -0,0 +1,46 @@
/*!
* \file beidou_b1c_signal_replica.h
* \brief Library for BeiDou B1C signal replica generation
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_SIGNAL_REPLICA_H
#define GNSS_SDR_BEIDOU_B1C_SIGNAL_REPLICA_H
#include <array>
#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
* \{ */
void beidou_b1c_code_gen_int(own::span<int32_t> dest, const std::array<char, 3>& signal_id, int32_t prn);
void beidou_b1c_code_gen_sinboc11_float(own::span<float> dest, const std::array<char, 3>& signal_id, uint32_t prn);
void beidou_b1c_code_gen_float_sampled(own::span<float> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift, bool secondary_flag);
void beidou_b1c_code_gen_complex_sampled(own::span<std::complex<float>> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift, bool secondary_flag);
void beidou_b1c_code_gen_complex_sampled(own::span<std::complex<float>> dest, const std::array<char, 3>& signal_id,
bool qmboc, uint32_t prn, int32_t sampling_freq, uint32_t chip_shift);
/** \} */
/** \} */
#endif // GNSS_SDR_BEIDOU_B1C_SIGNAL_REPLICA_H
+3 -1
View File
@@ -786,7 +786,9 @@ typedef struct
double ion_gps[8]; /* GPS iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3} */
double ion_gal[4]; /* Galileo iono model parameters {ai0,ai1,ai2,0} */
double ion_qzs[8]; /* QZSS iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3} */
double ion_cmp[8]; /* BeiDou iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3} */
double ion_cmp[8]; /* BeiDou DNAV Klobuchar {a0,a1,a2,a3,b0,b1,b2,b3} */
double ion_bdgim[9]; /* BeiDou B-CNAV1 BDGIM alphas alpha1..alpha9 [TECu] */
int ion_bdgim_valid; /* 1 if ion_bdgim is usable */
double ion_irn[8]; /* IRNSS iono model parameters {a0,a1,a2,a3,b0,b1,b2,b3} */
int leaps; /* leap seconds (s) */
double lam[MAXSAT][NFREQ]; /* carrier wave lengths (m) */
@@ -17,6 +17,7 @@
#include "rtklib_conversions.h"
#include "Beidou_DNAV.h" // for BEIDOU_DNAV_BDT2GPST_WEEK_NUM_OFFSET
#include "MATH_CONSTANTS.h" // for GNSS_PI, TWO_PI
#include "beidou_cnav1_ephemeris.h" // for Beidou_Cnav1_Ephemeris
#include "beidou_dnav_almanac.h" // for Beidou_Dnav_Almanac
#include "beidou_dnav_ephemeris.h" // for Beidou_Dnav_Ephemeris
#include "galileo_almanac.h" // for Galileo_Almanac
@@ -229,6 +230,10 @@ obsd_t insert_obs_to_rtklib(obsd_t& rtklib_obs,
{
rtklib_obs.code[band] = static_cast<unsigned char>(CODE_L6I);
}
else if (sig_ == "1D")
{
rtklib_obs.code[band] = static_cast<unsigned char>(CODE_L1P);
}
break;
case 'J':
@@ -711,6 +716,73 @@ eph_t eph_to_rtklib(const Beidou_Dnav_Ephemeris& bei_eph)
}
eph_t eph_to_rtklib(const Beidou_Cnav1_Ephemeris& bei_eph)
{
eph_t rtklib_sat = {0, 0, 0, 0, 0, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, {}, {}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, 0, 0, 0, 0, 0.0, -1, 0};
rtklib_sat.sat = bei_eph.PRN + NSATGPS + NSATGLO + NSATGAL + NSATQZS;
rtklib_sat.A = bei_eph.A0;
rtklib_sat.M0 = bei_eph.M_0;
rtklib_sat.deln = bei_eph.delta_n;
rtklib_sat.OMG0 = bei_eph.OMEGA_0;
rtklib_sat.OMGd = bei_eph.OMEGAdot;
rtklib_sat.omg = bei_eph.omega;
rtklib_sat.i0 = bei_eph.i_0;
rtklib_sat.idot = bei_eph.idot;
rtklib_sat.e = bei_eph.ecc;
rtklib_sat.Adot = bei_eph.Adot;
rtklib_sat.ndot = bei_eph.delta_ndot;
rtklib_sat.svh = bei_eph.hs;
rtklib_sat.sva = 0;
rtklib_sat.code = bei_eph.sig_type;
rtklib_sat.flag = bei_eph.nav_type;
rtklib_sat.iode = static_cast<int32_t>(bei_eph.IODE);
rtklib_sat.iodc = static_cast<int32_t>(bei_eph.IODC);
rtklib_sat.week = bei_eph.WN;
rtklib_sat.cic = bei_eph.Cic;
rtklib_sat.cis = bei_eph.Cis;
rtklib_sat.cuc = bei_eph.Cuc;
rtklib_sat.cus = bei_eph.Cus;
rtklib_sat.crc = bei_eph.Crc;
rtklib_sat.crs = bei_eph.Crs;
rtklib_sat.f0 = bei_eph.af0;
rtklib_sat.f1 = bei_eph.af1;
rtklib_sat.f2 = bei_eph.af2;
/* ICD §7.6 → gettgd(): tgd[0]=TGD_B1Cp, tgd[1]=TGD_B2ap, tgd[2]=ISC_B1Cd */
rtklib_sat.tgd[0] = bei_eph.TGD_B1Cp;
rtklib_sat.tgd[1] = bei_eph.TGD_B2ap;
rtklib_sat.tgd[2] = bei_eph.ISC_B1Cd;
rtklib_sat.toes = bei_eph.toe;
rtklib_sat.toe = bdt2gpst(bdt2time(rtklib_sat.week, bei_eph.toe));
rtklib_sat.toc = bdt2gpst(bdt2time(rtklib_sat.week, bei_eph.toc));
rtklib_sat.ttr = bdt2gpst(bdt2time(rtklib_sat.week, bei_eph.tow));
double tow = time2gpst(rtklib_sat.ttr, &rtklib_sat.week);
const double toc = time2gpst(rtklib_sat.toc, nullptr);
const double toe = time2gpst(rtklib_sat.toe, nullptr);
if (rtklib_sat.toes < tow - 302400.0)
{
rtklib_sat.week++;
tow -= 604800.0;
}
else if (rtklib_sat.toes > tow + 302400.0)
{
rtklib_sat.week--;
tow += 604800.0;
}
rtklib_sat.toe = gpst2time(rtklib_sat.week, toe);
rtklib_sat.toc = gpst2time(rtklib_sat.week, toc);
rtklib_sat.ttr = gpst2time(rtklib_sat.week, tow);
rtklib_sat.has_orbit_radial_correction_m = 0.0;
rtklib_sat.has_orbit_in_track_correction_m = 0.0;
rtklib_sat.has_orbit_cross_track_correction_m = 0.0;
rtklib_sat.has_clock_correction_m = 0.0;
rtklib_sat.apply_has_corrections = false;
return rtklib_sat;
}
eph_t eph_to_rtklib(const Gps_CNAV_Ephemeris& gps_cnav_eph)
{
eph_t rtklib_sat = {0, 0, 0, 0, 0, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0.0, 0.0, 0.0, 0.0, 0.0,
@@ -28,6 +28,7 @@
* \{ */
class Beidou_Cnav1_Ephemeris;
class Beidou_Dnav_Almanac;
class Beidou_Dnav_Ephemeris;
class Galileo_Almanac;
@@ -103,6 +104,8 @@ eph_t eph_to_rtklib(const Gps_CNAV_Ephemeris& gps_cnav_eph);
eph_t eph_to_rtklib(const Beidou_Dnav_Ephemeris& bei_eph);
eph_t eph_to_rtklib(const Beidou_Cnav1_Ephemeris& bei_eph);
alm_t alm_to_rtklib(const Gps_Almanac& gps_alm);
alm_t alm_to_rtklib(const Galileo_Almanac& gal_alm);
alm_t alm_to_rtklib(const Beidou_Dnav_Almanac& bei_alm);
+48 -18
View File
@@ -31,6 +31,7 @@
*/
#include "rtklib_ephemeris.h"
#include "Beidou_CNAV1.h"
#include "rtklib_preceph.h"
#include "rtklib_rtkcmn.h"
#include "rtklib_sbas.h"
@@ -405,6 +406,7 @@ void eph2pos(gtime_t time, const eph_t *eph, double *rs, double *dts,
omge = GNSS_OMEGA_EARTH_DOT;
break;
}
const int is_bds_cnav1 = (sys == SYS_BDS && eph->code == BDS_EPH_SOURCE_CNAV1) ? 1 : 0;
Ak = eph->A + eph->Adot * tk;
delta_na = eph->deln + 0.5 * eph->ndot * tk;
na = sqrt(mu / (eph->A * eph->A * eph->A)) + delta_na;
@@ -437,8 +439,8 @@ void eph2pos(gtime_t time, const eph_t *eph, double *rs, double *dts,
y = r * sin(u);
cosi = cos(i);
/* beidou geo satellite (ref [9]) */
if (sys == SYS_BDS && (prn <= 5 || prn > 58))
/* BeiDou GEO 5° transform is DNAV-only (B1I ICD). B-CNAV1 Table 7-9 is MEO/IGSO only */
if (sys == SYS_BDS && !is_bds_cnav1 && (prn <= 5 || prn > 58))
{
O = eph->OMG0 + eph->OMGd * tk - omge * eph->toes;
sinO = sin(O);
@@ -760,18 +762,22 @@ void seph2pos(gtime_t time, const seph_t *seph, double *rs, double *dts,
}
/* select ephemeris --------------------------------------------------------*/
eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav)
/* select ephemeris --------------------------------------------------------
* bds_eph_sel: -1=any, 0=DNAV (eph.code!=BDS_EPH_SOURCE_CNAV1),
* BDS_EPH_SOURCE_CNAV1=B-CNAV1
*-----------------------------------------------------------------------------*/
eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav, int bds_eph_sel)
{
double t;
double tmax;
double tmin;
int i;
int j = -1;
const int sys = satsys(sat, nullptr);
trace(4, "seleph : time=%s sat=%2d iode=%d\n", time_str(time, 3), sat, iode);
trace(4, "seleph : time=%s sat=%2d iode=%d bds_sel=%d\n", time_str(time, 3), sat, iode, bds_eph_sel);
switch (satsys(sat, nullptr))
switch (sys)
{
case SYS_QZS:
tmax = MAXDTOE_QZS + 1.0;
@@ -794,6 +800,14 @@ eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav)
{
continue;
}
if (sys == SYS_BDS && bds_eph_sel == BDS_EPH_SOURCE_CNAV1 && nav->eph[i].code != BDS_EPH_SOURCE_CNAV1)
{
continue;
}
if (sys == SYS_BDS && bds_eph_sel == 0 && nav->eph[i].code == BDS_EPH_SOURCE_CNAV1)
{
continue;
}
if (iode >= 0 && nav->eph[i].iode != iode)
{
continue;
@@ -905,20 +919,20 @@ seph_t *selseph(gtime_t time, int sat, const nav_t *nav)
/* satellite clock with broadcast ephemeris ----------------------------------*/
int ephclk(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
double *dts)
double *dts, int bds_eph_sel)
{
eph_t *eph;
geph_t *geph;
seph_t *seph;
int sys;
trace(4, "ephclk : time=%s sat=%2d\n", time_str(time, 3), sat);
trace(4, "ephclk : time=%s sat=%2d bds_sel=%d\n", time_str(time, 3), sat, bds_eph_sel);
sys = satsys(sat, nullptr);
if (sys == SYS_GPS || sys == SYS_GAL || sys == SYS_QZS || sys == SYS_BDS)
{
if (!(eph = seleph(teph, sat, -1, nav)))
if (!(eph = seleph(teph, sat, -1, nav, bds_eph_sel)))
{
return 0;
}
@@ -951,7 +965,7 @@ int ephclk(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
/* satellite position and clock by broadcast ephemeris -----------------------*/
int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
int iode, double *rs, double *dts, double *var, int *svh)
int iode, double *rs, double *dts, double *var, int *svh, int bds_eph_sel)
{
eph_t *eph;
geph_t *geph;
@@ -962,7 +976,7 @@ int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
int i;
int sys;
trace(4, "ephpos : time=%s sat=%2d iode=%d\n", time_str(time, 3), sat, iode);
trace(4, "ephpos : time=%s sat=%2d iode=%d bds_sel=%d\n", time_str(time, 3), sat, iode, bds_eph_sel);
sys = satsys(sat, nullptr);
@@ -970,7 +984,7 @@ int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
if (sys == SYS_GPS || sys == SYS_GAL || sys == SYS_QZS || sys == SYS_BDS)
{
if (!(eph = seleph(teph, sat, iode, nav)))
if (!(eph = seleph(teph, sat, iode, nav, bds_eph_sel)))
{
return 0;
}
@@ -1217,16 +1231,16 @@ int satpos_ssr(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
*-----------------------------------------------------------------------------*/
int satpos(gtime_t time, gtime_t teph, int sat, int ephopt,
const nav_t *nav, double *rs, double *dts, double *var,
int *svh)
int *svh, int bds_eph_sel)
{
trace(4, "satpos : time=%s sat=%2d ephopt=%d\n", time_str(time, 3), sat, ephopt);
trace(4, "satpos : time=%s sat=%2d ephopt=%d bds_sel=%d\n", time_str(time, 3), sat, ephopt, bds_eph_sel);
*svh = 0;
switch (ephopt)
{
case EPHOPT_BRDC:
return ephpos(time, teph, sat, nav, -1, rs, dts, var, svh);
return ephpos(time, teph, sat, nav, -1, rs, dts, var, svh, bds_eph_sel);
case EPHOPT_SBAS:
return satpos_sbas(time, teph, sat, nav, rs, dts, var, svh);
case EPHOPT_SSRAPC:
@@ -1299,6 +1313,22 @@ void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
var[i] = 0.0;
svh[i] = 0;
/* BDS: prefer CNAV1 when any band carries B1C codes, DNAV otherwise */
int bds_eph_sel = -1;
if (satsys(obs[i].sat, nullptr) == SYS_BDS)
{
bds_eph_sel = 0;
for (j = 0; j < NFREQ; j++)
{
const unsigned char cj = obs[i].code[j];
if (cj == CODE_L1D || cj == CODE_L1P)
{
bds_eph_sel = BDS_EPH_SOURCE_CNAV1;
break;
}
}
}
/* search any pseudorange */
for (j = 0, pr = 0.0; j < NFREQ; j++)
{
@@ -1317,7 +1347,7 @@ void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
time[i] = timeadd(obs[i].time, -pr / SPEED_OF_LIGHT_M_S);
/* satellite clock bias by broadcast ephemeris */
if (!ephclk(time[i], teph, obs[i].sat, nav, &dt))
if (!ephclk(time[i], teph, obs[i].sat, nav, &dt, bds_eph_sel))
{
trace(3, "no broadcast clock %s sat=%2d\n", time_str(time[i], 3), obs[i].sat);
continue;
@@ -1326,7 +1356,7 @@ void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
/* satellite position and clock at transmission time */
if (!satpos(time[i], teph, obs[i].sat, ephopt, nav, rs + i * 6, dts + i * 2, var + i,
svh + i))
svh + i, bds_eph_sel))
{
trace(3, "no ephemeris %s sat=%2d\n", time_str(time[i], 3), obs[i].sat);
continue;
@@ -1334,7 +1364,7 @@ void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
/* if no precise clock available, use broadcast clock instead */
if (dts[i * 2] == 0.0)
{
if (!ephclk(time[i], teph, obs[i].sat, nav, dts + i * 2))
if (!ephclk(time[i], teph, obs[i].sat, nav, dts + i * 2, bds_eph_sel))
{
continue;
}
@@ -53,14 +53,15 @@ void geph2pos(gtime_t time, const geph_t *geph, double *rs, double *dts,
double seph2clk(gtime_t time, const seph_t *seph);
void seph2pos(gtime_t time, const seph_t *seph, double *rs, double *dts,
double *var);
eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav);
/* bds_eph_sel: -1=any, 0=DNAV (code!=BDS_EPH_SOURCE_CNAV1), BDS_EPH_SOURCE_CNAV1=B-CNAV1 */
eph_t *seleph(gtime_t time, int sat, int iode, const nav_t *nav, int bds_eph_sel = -1);
geph_t *selgeph(gtime_t time, int sat, int iode, const nav_t *nav);
seph_t *selseph(gtime_t time, int sat, const nav_t *nav);
int ephclk(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
double *dts);
double *dts, int bds_eph_sel = -1);
// satellite position and clock by broadcast ephemeris
int ephpos(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
int iode, double *rs, double *dts, double *var, int *svh);
int iode, double *rs, double *dts, double *var, int *svh, int bds_eph_sel = -1);
int satpos_sbas(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
double *rs, double *dts, double *var, int *svh);
int satpos_ssr(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
@@ -68,7 +69,7 @@ int satpos_ssr(gtime_t time, gtime_t teph, int sat, const nav_t *nav,
int satpos(gtime_t time, gtime_t teph, int sat, int ephopt,
const nav_t *nav, double *rs, double *dts, double *var,
int *svh);
int *svh, int bds_eph_sel = -1);
void satposs(gtime_t teph, const obsd_t *obs, int n, const nav_t *nav,
int ephopt, double *rs, double *dts, double *var, int *svh);
+152 -33
View File
@@ -34,6 +34,9 @@
#endif
#include "rtklib_pntpos.h"
#include "Beidou_CNAV1.h"
#include "beidou_bdgim.h"
#include "gnss_frequencies.h"
#include "rtklib_ephemeris.h"
#include "rtklib_ionex.h"
#include "rtklib_sbas.h"
@@ -111,7 +114,56 @@ double gettgd(int sat, const nav_t *nav, int tgd_index)
double gettgd(int sat, const nav_t *nav)
{
return gettgd(sat, nav, 0);
return gettgd(sat, nav, static_cast<int>(0));
}
/* get tgd parameter (m) -----------------------------------------------------
* GPS/QZS/GAL: return c·tgd[0] from the first matching ephemeris (classic RTKLIB).
* BDS DNAV (eph.code!=BDS_EPH_SOURCE_CNAV1): tgd[0]=TGD1 (B1I vs B3I timing reference)
* BDS CNAV1 (eph.code==BDS_EPH_SOURCE_CNAV1), stored by eph_to_rtklib(Beidou_Cnav1_Ephemeris):
* tgd[0]=TGD_B1Cp, tgd[1]=TGD_B2ap, tgd[2]=ISC_B1Cd (ICD B1C §7.6)
* BDS user algorithm (applied in prange as PC = P - c·Δt_TGD):
* B1C pilot CODE_L1P: (Δtsv)_B1Cp = Δtsv - TGD_B1Cp → (7-4)
* B1C data CODE_L1D: (Δtsv)_B1Cd = Δtsv - TGD_B1Cp - ISC_B1Cd → (7-5)
* B1I (CODE_L2I/L1I): use DNAV TGD1; B3I skips TGD in prange.
* CODE_L1P is also GPS/GLO L1P — CNAV1 matching is SYS_BDS only.
* No DNAV↔CNAV1 TGD fallback (TGD1 vs TGD_B1Cp).
*-----------------------------------------------------------------------------*/
double gettgd_bds_by_obs_code(int sat, const nav_t *nav, unsigned char obs_code)
{
int i;
int prn = 0;
const int sys = satsys(sat, &prn);
const int is_b1c_obs = (obs_code == CODE_L1D || obs_code == CODE_L1P) ? 1 : 0;
for (i = 0; i < nav->n; i++)
{
if (nav->eph[i].sat != sat)
{
continue;
}
if (sys == SYS_BDS)
{
const int is_cnav1 = (nav->eph[i].code == BDS_EPH_SOURCE_CNAV1) ? 1 : 0;
/* B1C obs ↔ CNAV1 eph; B1I/other ↔ DNAV eph */
if (is_b1c_obs != is_cnav1)
{
continue;
}
double tgd_s = nav->eph[i].tgd[0];
if (is_cnav1 && obs_code == CODE_L1D)
{
tgd_s += nav->eph[i].tgd[2];
}
return SPEED_OF_LIGHT_M_S * tgd_s;
}
/* GPS/GAL/QZS/... : first ephemeris for this sat */
return SPEED_OF_LIGHT_M_S * nav->eph[i].tgd[0];
}
return 0.0;
}
/* get isc parameter (m) -----------------------------------------------------*/
@@ -201,11 +253,38 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
return 0.0;
}
/* L1-L2 for GPS/GLO/QZS, L1-L5 for GAL/SBS/BDS */
if (sys == SYS_GAL || sys == SYS_SBS || sys == SYS_BDS)
/* L1-L2 for GPS/GLO/QZS, L1-L5 for GAL/SBS;
* BDS: B1I(band0)+B3I(band2). B1C (CODE_L1P/L1D) is single-frequency on
* slot 0 under GNSS-SDR prefer-B1C XOR (never paired as IF with B1I). */
if (sys == SYS_GAL || sys == SYS_SBS)
{
j = 2;
}
else if (sys == SYS_BDS)
{
const bool b1c0 = (obs->code[0] == CODE_L1D || obs->code[0] == CODE_L1P);
const bool b1c1 = (obs->code[1] == CODE_L1D || obs->code[1] == CODE_L1P);
if (b1c0 || b1c1)
{
i = b1c0 ? 0 : 1;
j = i; /* B1C single-frequency */
}
else if (obs->code[0] != CODE_NONE && obs->code[2] != CODE_NONE)
{
i = 0;
j = 2; /* B1I + B3I */
}
else if (obs->code[0] != CODE_NONE)
{
i = 0;
j = 2; /* B1I-only: keep legacy pair indices for lam check */
}
else if (obs->code[2] != CODE_NONE)
{
i = 2;
j = 2; /* B3I-only */
}
}
else if (sys == SYS_GPS || sys == SYS_GLO || sys == SYS_QZS)
{
if (obs->code[1] != CODE_NONE)
@@ -234,7 +313,7 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
time_str(obs->time, 0), obs->sat, azel[1] * R2D, obs->SNR[i] * 0.25);
return 0.0;
}
if (opt->ionoopt == IONOOPT_IFLC)
if (opt->ionoopt == IONOOPT_IFLC && i != j)
{
if (testsnr(0, j, azel[1], obs->SNR[j] * 0.25, &opt->snrmask))
{
@@ -246,10 +325,10 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
/* fL1^2 / fL2(orL5)^2 . See IS-GPS-200, p. 103 and Galileo ICD p. 48 */
if (sys == SYS_GPS || sys == SYS_GAL || sys == SYS_GLO || sys == SYS_BDS || sys == SYS_QZS)
{
gamma_ = std::pow(lam[j], 2.0) / std::pow(lam[i], 2.0);
gamma_ = (i == j) ? 1.0 : (std::pow(lam[j], 2.0) / std::pow(lam[i], 2.0));
}
P1 = obs->P[i];
P2 = obs->P[j];
P2 = (i == j) ? 0.0 : obs->P[j];
P1_P2 = nav->cbias[obs->sat - 1][0];
P1_C1 = nav->cbias[obs->sat - 1][1];
P2_C2 = nav->cbias[obs->sat - 1][2];
@@ -277,14 +356,21 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
/* if no P1-P2 DCB, use TGD instead */
if (P1_P2 == 0.0)
{
int tgd_index = 0;
if (sys == SYS_GAL)
if (sys == SYS_BDS)
{
const unsigned char observation_code = obs->code[j] != CODE_NONE ? obs->code[j] : obs->code[i];
tgd_index = galileo_bgd_index(observation_code, obs->sat, nav);
uses_galileo_bgd = tgd_index >= 0;
P1_P2 = gettgd_bds_by_obs_code(obs->sat, nav, obs->code[i]);
}
else
{
int tgd_index = 0;
if (sys == SYS_GAL)
{
const unsigned char observation_code = obs->code[j] != CODE_NONE ? obs->code[j] : obs->code[i];
tgd_index = galileo_bgd_index(observation_code, obs->sat, nav);
uses_galileo_bgd = tgd_index >= 0;
}
P1_P2 = gettgd(obs->sat, nav, tgd_index);
}
P1_P2 = gettgd(obs->sat, nav, tgd_index);
}
if (sys == SYS_GPS || sys == SYS_QZS)
@@ -299,7 +385,7 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
if (opt->ionoopt == IONOOPT_IFLC)
{
/* dual-frequency */
if (P1 == 0.0 || P2 == 0.0)
if (P1 == 0.0 || P2 == 0.0 || i == j)
{
return 0.0;
}
@@ -325,15 +411,23 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
////////////////////////////////////////////
else
{ /* single-frequency */
if (obs->code[i] == CODE_NONE && obs->code[j] == CODE_NONE)
if (obs->code[i] == CODE_NONE && (i == j || obs->code[j] == CODE_NONE))
{
return 0.0;
}
if (obs->code[i] != CODE_NONE && obs->code[j] == CODE_NONE)
if (obs->code[i] != CODE_NONE && (i == j || obs->code[j] == CODE_NONE))
{
P1 += P1_C1; /* C1->P1 */
PC = P1 - P1_P2;
if (sys == SYS_BDS && (obs->code[i] == CODE_L6I || obs->code[i] == CODE_L6Q))
{
/* B3I is the DNAV timing reference; no TGD1 applied */
PC = P1;
}
else
{
P1 += P1_C1; /* C1->P1 */
PC = P1 - P1_P2;
}
}
else if (obs->code[i] == CODE_NONE && obs->code[j] != CODE_NONE)
{
@@ -397,7 +491,7 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
*iono_scale = 0.0;
}
}
else if (sys == SYS_GAL || sys == SYS_GLO || sys == SYS_BDS) /* E1 + E5a */
else if (sys == SYS_GAL || sys == SYS_GLO || sys == SYS_BDS) /* E1 + E5a / B1I + B3I */
{
P1 += P1_C1;
P2 += P2_C2;
@@ -433,7 +527,7 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
* return : status(1:ok,0:error)
*-----------------------------------------------------------------------------*/
int ionocorr(gtime_t time, const nav_t *nav, int sat, const double *pos,
const double *azel, int ionoopt, double *ion, double *var)
const double *azel, int ionoopt, double *ion, double *var, unsigned char obs_code)
{
trace(4, "ionocorr: time=%s opt=%d sat=%2d pos=%.3f %.3f azel=%.3f %.3f\n",
time_str(time, 3), ionoopt, sat, pos[0] * R2D, pos[1] * R2D, azel[0] * R2D,
@@ -442,18 +536,32 @@ int ionocorr(gtime_t time, const nav_t *nav, int sat, const double *pos,
/* broadcast model */
if (ionoopt == IONOOPT_BRDC)
{
const double *ion_coeffs = nav->ion_gps;
double ref_freq_scale = 1.0;
if (satsys(sat, nullptr) == SYS_BDS && norm_rtk(nav->ion_cmp, 8) > 0.0)
const int sys = satsys(sat, nullptr);
if (sys == SYS_BDS)
{
/* prefer the BDS broadcast Klobuchar for BDS satellites.
Its coefficients are referenced to B1I: express the
result at the GPS L1 frequency, since callers rescale
the delay to the observed band */
ion_coeffs = nav->ion_cmp;
ref_freq_scale = std::pow(FREQ1_BDS / FREQ1, 2.0);
/* B1C (CODE_L1D/L1P) uses BDGIM at FREQ1; B1I/B3I use DNAV Klobuchar */
const bool is_b1c = (obs_code == CODE_L1D || obs_code == CODE_L1P);
if (is_b1c && nav->ion_bdgim_valid)
{
const double ep0[] = {2000, 1, 1, 12, 0, 0};
const double mjd = 51544.5 + timediff(gpst2utc(time), epoch2time(ep0)) / 86400.0;
*ion = beidou_bdgim_delay_m(mjd, pos[0], pos[1], azel[0], azel[1],
nav->ion_bdgim, FREQ1);
*var = std::pow(*ion * ERR_BRDCI, 2.0);
return 1;
}
if (norm_rtk(nav->ion_cmp, 8) > 0.0)
{
/* BDS Klobuchar coefficients are referenced to B1I.
Express the result at GPS L1 because rescode()
rescales the delay to the observed carrier. */
const double ref_freq_scale = std::pow(FREQ1_BDS / FREQ1, 2.0);
*ion = ionmodel(time, nav->ion_cmp, pos, azel) * ref_freq_scale;
*var = std::pow(*ion * ERR_BRDCI, 2.0);
return 1;
}
}
*ion = ionmodel(time, ion_coeffs, pos, azel) * ref_freq_scale;
*ion = ionmodel(time, nav->ion_gps, pos, azel);
*var = std::pow(*ion * ERR_BRDCI, 2.0);
return 1;
}
@@ -602,16 +710,27 @@ int rescode(int iter, const obsd_t *obs, int n, const double *rs,
continue;
}
/* ionospheric corrections */
/* ionospheric corrections — use first available observation code/band */
unsigned char iono_code = CODE_NONE;
int iono_band = 0;
for (j = 0; j < NFREQ; j++)
{
if (obs[i].code[j] != CODE_NONE && obs[i].P[j] != 0.0)
{
iono_code = obs[i].code[j];
iono_band = j;
break;
}
}
if (!ionocorr(obs[i].time, nav, obs[i].sat, pos, azel + i * 2,
iter > 0 ? opt->ionoopt : IONOOPT_BRDC, &dion, &vion))
iter > 0 ? opt->ionoopt : IONOOPT_BRDC, &dion, &vion, iono_code))
{
trace(4, "ionocorr error\n");
continue;
}
/* GPS-L1 -> L1/B1 */
if ((lam_L1 = nav->lam[obs[i].sat - 1][0]) > 0.0)
/* Scale broadcast iono (L1 reference) to the observed carrier. */
if ((lam_L1 = nav->lam[obs[i].sat - 1][iono_band]) > 0.0)
{
/* iono delay scales with f^-2, its variance with f^-4 */
const double freq_factor = std::pow(lam_L1 / LAM_CARR[0], 2.0);
+5 -1
View File
@@ -49,6 +49,8 @@ double varerr(const prcopt_t *opt, double el, int sys);
/* get tgd parameter (m) -----------------------------------------------------*/
double gettgd(int sat, const nav_t *nav);
double gettgd(int sat, const nav_t *nav, int tgd_index);
/* BDS DNAV/CNAV1 TGD selection by observation code (CODE_L1D/L1P/...) */
double gettgd_bds_by_obs_code(int sat, const nav_t *nav, unsigned char obs_code);
/* select Galileo BGD from observation and ephemeris provenance -------------*/
int galileo_bgd_index(unsigned char observation_code, int sat, const nav_t *nav);
@@ -77,10 +79,12 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
* int ionoopt I ionospheric correction option (IONOOPT_???)
* double *ion O ionospheric delay (L1) (m)
* double *var O ionospheric delay (L1) variance (m^2)
* unsigned char obs_code I optional observation code (CODE_???); for BDS
* CODE_L1D/L1P selects BDGIM at FREQ1 (B1C)
* return : status(1:ok,0:error)
*-----------------------------------------------------------------------------*/
int ionocorr(gtime_t time, const nav_t *nav, int sat, const double *pos,
const double *azel, int ionoopt, double *ion, double *var);
const double *azel, int ionoopt, double *ion, double *var, unsigned char obs_code = 0);
/* tropospheric correction -----------------------------------------------------
* compute tropospheric correction
* args : gtime_t time I time
+3 -2
View File
@@ -108,7 +108,7 @@ char obscodes[][3] = {
"2W", "2Y", "2M", "2N", "5I", "5Q", "5X", "7I", "7Q", "7X", /* 20-29 */
"6A", "6B", "6C", "6X", "6Z", "6S", "6L", "8L", "8Q", "8X", /* 30-39 */
"2I", "2Q", "6I", "6Q", "3I", "3Q", "3X", "1I", "1Q", "5A", /* 40-49 */
"5B", "5C", "9A", "9B", "9C", "9X", "", "", "", "" /* 50-59 */
"5B", "5C", "9A", "9B", "9C", "9X", "1D", "", "", "" /* 50-59 */
};
@@ -119,7 +119,7 @@ unsigned char obsfreqs[] = {
2, 2, 2, 2, 3, 3, 3, 5, 5, 5, /* 20-29 */
4, 4, 4, 4, 4, 4, 4, 6, 6, 6, /* 30-39 */
2, 2, 4, 4, 3, 3, 3, 1, 1, 3, /* 40-49 */
3, 3, 7, 7, 7, 7, 0, 0, 0, 0 /* 50-59 */
3, 3, 7, 7, 7, 7, 1, 0, 0, 0 /* 50-59 */
};
@@ -4283,6 +4283,7 @@ double satwavelen(int sat, int frq, const nav_t *nav)
}
else if (sys == SYS_BDS)
{
/* BDS frq: 0=B1I (FREQ1_BDS), 1=B2, 2=B3I. B1C uses FREQ1 via PVT lam[0] override. */
if (frq == 0)
{
return SPEED_OF_LIGHT_M_S / FREQ1_BDS; /* B1 */
@@ -14,6 +14,7 @@ set(TELEMETRY_DECODER_GR_BLOCKS_SOURCES
glonass_gnav_telemetry_decoder_gs.cc
galileo_telemetry_decoder_gs.cc
beidou_dnav_telemetry_decoder_gs.cc
beidou_b1c_telemetry_decoder_gs.cc
)
set(TELEMETRY_DECODER_GR_BLOCKS_HEADERS
@@ -25,6 +26,7 @@ set(TELEMETRY_DECODER_GR_BLOCKS_HEADERS
glonass_gnav_telemetry_decoder_gs.h
galileo_telemetry_decoder_gs.h
beidou_dnav_telemetry_decoder_gs.h
beidou_b1c_telemetry_decoder_gs.h
)
list(SORT TELEMETRY_DECODER_GR_BLOCKS_HEADERS)
@@ -0,0 +1,678 @@
/*!
* \file beidou_b1c_telemetry_decoder_gs.cc
* \brief BeiDou B1C B-CNAV1 telemetry decoder block
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_b1c_telemetry_decoder_gs.h"
#include "Beidou_B1C.h"
#include "Beidou_B1C_codes.h"
#include "Beidou_CNAV1.h"
#include "Beidou_DNAV.h"
#include "MATH_CONSTANTS.h"
#include "beidou_cnav1_ephemeris.h"
#include "beidou_cnav1_iono.h"
#include "beidou_cnav1_utc_model.h"
#include "display.h"
#include "dump_logger_helper.h"
#include "gnss_sdr_make_unique.h"
#include "gnss_synchro.h"
#include "tlm_crc_stats.h"
#include "tlm_utils.h"
#include "tow_to_trk.h"
#include <pmt/pmt.h>
#include <pmt/pmt_sugar.h>
#include <algorithm>
#include <cmath>
#include <complex>
#include <iomanip>
#include <iostream>
#include <utility>
#include <vector>
#if USE_GLOG_AND_GFLAGS
#include <glog/logging.h>
#else
#include <absl/log/log.h>
#endif
namespace
{
constexpr int32_t CRC_ERROR_LIMIT = 8;
constexpr uint64_t B1C_REACQ_INTERVAL_SYMBOLS = static_cast<uint64_t>(BEIDOU_CNAV1_FRAME_SYMBOLS);
constexpr int32_t B1C_SCAN_OFFSET_SPAN_LOOSE = 4;
constexpr int32_t B1C_SCAN_MAX_FULL_DECODES = 16;
uint32_t compute_b1c_tow_ms_at_current_symbol(
double tow_soh_bdt_s,
int32_t frame_soh_offset,
int32_t symbol_duration_ms)
{
const int32_t clamped_offset = std::max(0, std::min(frame_soh_offset, BEIDOU_CNAV1_FRAME_SYMBOLS - 1));
const int32_t symbols_since_soh = BEIDOU_CNAV1_FRAME_SYMBOLS - clamped_offset;
const double tow_gpst_s = tow_soh_bdt_s + static_cast<double>(BEIDOU_DNAV_BDT2GPST_LEAP_SEC_OFFSET) +
static_cast<double>(symbols_since_soh) * static_cast<double>(symbol_duration_ms) / 1000.0;
const double tow_ms = std::fmod(tow_gpst_s * 1000.0, 604800000.0);
if (tow_ms < 0.0)
{
return static_cast<uint32_t>(tow_ms + 604800000.0);
}
return static_cast<uint32_t>(tow_ms);
}
void extract_prompt_window(const Gnss_Synchro* in, std::vector<float>& i_out, std::vector<float>& q_out)
{
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
i_out.resize(static_cast<size_t>(n));
q_out.resize(static_cast<size_t>(n));
for (int32_t i = 0; i < n; i++)
{
i_out[static_cast<size_t>(i)] = in[i].Prompt_I;
q_out[static_cast<size_t>(i)] = in[i].Prompt_Q;
}
}
bool build_rotated_bpsk_frame_from_iq(
const std::vector<float>& history_i,
const std::vector<float>& history_q,
int32_t start_offset,
bool invert,
std::vector<float>& frame_out)
{
if (history_i.size() < static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS) ||
history_q.size() < static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS))
{
return false;
}
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
std::vector<std::complex<float> > z(static_cast<size_t>(n));
std::complex<float> sum_z2(0.0F, 0.0F);
for (int32_t i = 0; i < n; i++)
{
const auto idx = static_cast<size_t>((start_offset + i) % n);
const std::complex<float> zi(history_i[idx], history_q[idx]);
z[static_cast<size_t>(i)] = zi;
sum_z2 += zi * zi;
}
const float theta = 0.5F * std::arg(sum_z2);
const std::complex<float> rot(std::cos(theta), -std::sin(theta));
frame_out.resize(static_cast<size_t>(n));
for (int32_t i = 0; i < n; i++)
{
float sample = (z[static_cast<size_t>(i)] * rot).real();
if (invert)
{
sample = -sample;
}
frame_out[static_cast<size_t>(i)] = sample;
}
return true;
}
bool apply_llr_scale_and_decode(
std::vector<float>& frame,
Beidou_Cnav1_Navigation_Message& nav,
int32_t expected_prn,
float cn0_db_hz,
int32_t* fail_stage)
{
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
double rms_acc = 0.0;
for (float sample : frame)
{
const auto v = static_cast<double>(sample);
rms_acc += v * v;
}
const double rms = std::sqrt(rms_acc / static_cast<double>(n)) + 1e-6;
const double cn0_linear = std::pow(10.0, static_cast<double>(cn0_db_hz) / 10.0);
const double snr_sym = std::max(cn0_linear * (BEIDOU_B1C_CODE_PERIOD_MS * 1e-3), 1e-4);
const double llr_scale = std::min(std::max(std::sqrt(snr_sym) / rms, 0.05), 50.0);
for (float& sample : frame)
{
sample = static_cast<float>(static_cast<double>(sample) * llr_scale);
}
return nav.decode_frame_symbols(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, expected_prn, fail_stage);
}
bool probe_frame_from_iq_window(
const std::vector<float>& history_i,
const std::vector<float>& history_q,
const Beidou_Cnav1_Navigation_Message& nav,
int32_t expected_prn,
int32_t start_offset,
bool invert)
{
std::vector<float> frame;
if (!build_rotated_bpsk_frame_from_iq(history_i, history_q, start_offset, invert, frame))
{
return false;
}
return nav.probe_subframe1_prn(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, expected_prn);
}
bool decode_frame_from_iq_window(
const std::vector<float>& history_i,
const std::vector<float>& history_q,
Beidou_Cnav1_Navigation_Message& nav,
int32_t expected_prn,
int32_t start_offset,
bool invert,
float cn0_db_hz,
int32_t* fail_stage = nullptr)
{
std::vector<float> frame;
if (!build_rotated_bpsk_frame_from_iq(history_i, history_q, start_offset, invert, frame))
{
return false;
}
return apply_llr_scale_and_decode(frame, nav, expected_prn, cn0_db_hz, fail_stage);
}
std::pair<int32_t, std::vector<int32_t> > find_secondary_code_candidates(
const std::vector<float>& pilot_symbols,
int32_t prn)
{
if (pilot_symbols.size() < static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS) ||
prn < 1 || prn > static_cast<int32_t>(BEIDOU_B1C_NUMBER_OF_PRNS))
{
return std::make_pair(0, std::vector<int32_t>());
}
const char* sec_code = BEIDOU_B1C_PILOT_SECONDARY_CODE[static_cast<size_t>(prn - 1)];
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
int32_t best_corr = 0;
int32_t best_lag = 0;
std::vector<int32_t> candidates;
for (int32_t lag = 0; lag < n; lag++)
{
int32_t corr = 0;
for (int32_t i = 0; i < n; i++)
{
const float sym = pilot_symbols[static_cast<size_t>((lag + i) % n)];
const int32_t sym_sign = (sym >= 0.0F) ? 1 : -1;
const int32_t sec_sign = (sec_code[static_cast<size_t>(i)] == '0') ? 1 : -1;
corr += sym_sign * sec_sign;
}
if (std::abs(corr) > std::abs(best_corr))
{
best_corr = corr;
best_lag = lag;
}
if (std::abs(corr) >= 1799)
{
candidates.push_back(lag);
}
}
if (candidates.empty() && std::abs(best_corr) >= 1650)
{
candidates.push_back(best_lag);
}
return std::make_pair(best_corr, candidates);
}
} // namespace
beidou_b1c_telemetry_decoder_gs_sptr beidou_b1c_make_telemetry_decoder_gs(
const Gnss_Satellite& satellite,
const Tlm_Conf& conf)
{
return beidou_b1c_telemetry_decoder_gs_sptr(new beidou_b1c_telemetry_decoder_gs(satellite, conf));
}
beidou_b1c_telemetry_decoder_gs::beidou_b1c_telemetry_decoder_gs(
const Gnss_Satellite& satellite,
const Tlm_Conf& conf)
: telemetry_impl_interface("beidou_b1c_telemetry_decoder_gs",
gr::io_signature::make(1, 1, sizeof(Gnss_Synchro)),
gr::io_signature::make(1, 1, sizeof(Gnss_Synchro))),
d_dump_filename(conf.dump_filename),
d_symbol_duration_ms(BEIDOU_B1C_CODE_PERIOD_MS),
d_dump(conf.dump),
d_dump_mat(conf.dump_mat),
d_remove_dat(conf.remove_dat),
d_enable_navdata_monitor(conf.enable_navdata_monitor),
d_dump_crc_stats(conf.dump_crc_stats),
d_tow_to_trk(conf.tow_to_trk)
{
set_history(static_cast<unsigned int>(BEIDOU_CNAV1_FRAME_SYMBOLS));
set_output_multiple(1);
configure_basic_outputs();
d_satellite = Gnss_Satellite(satellite.get_system(), satellite.get_PRN());
DLOG(INFO) << "Initializing BeiDou B1C B-CNAV1 telemetry decoder for satellite " << d_satellite;
if (d_enable_navdata_monitor)
{
message_port_register_out(pmt::mp("Nav_msg_from_TLM"));
d_nav_msg_packet.system = std::string("C");
d_nav_msg_packet.signal = std::string("1D");
}
if (d_dump_crc_stats)
{
d_Tlm_CRC_Stats = std::make_unique<Tlm_CRC_Stats>();
d_Tlm_CRC_Stats->initialize(conf.dump_crc_stats_filename);
}
else
{
d_Tlm_CRC_Stats = nullptr;
}
configure_dump_file(d_channel, d_dump, d_dump_filename, d_dump_file);
configure_crc_stats_channel(d_channel, d_dump_crc_stats, d_Tlm_CRC_Stats);
}
beidou_b1c_telemetry_decoder_gs::~beidou_b1c_telemetry_decoder_gs()
{
tlm_cleanup_and_save_files(d_dump_file, d_dump_filename, d_dump, d_dump_mat, d_remove_dat);
}
void beidou_b1c_telemetry_decoder_gs::set_satellite(const Gnss_Satellite& satellite)
{
d_satellite = Gnss_Satellite(satellite.get_system(), satellite.get_PRN());
DLOG(INFO) << "BeiDou B1C telemetry decoder set to satellite " << d_satellite;
}
void beidou_b1c_telemetry_decoder_gs::set_channel(int channel)
{
d_channel = channel;
DLOG(INFO) << "BeiDou B1C B-CNAV1 telemetry channel set to " << channel;
configure_dump_file(d_channel, d_dump, d_dump_filename, d_dump_file);
configure_crc_stats_channel(d_channel, d_dump_crc_stats, d_Tlm_CRC_Stats);
}
void beidou_b1c_telemetry_decoder_gs::reset()
{
d_sample_counter = 0;
d_frame_sync_index = 0;
d_TOW_at_current_symbol_ms = 0;
d_flag_valid_word = false;
d_flag_frame_sync = false;
d_flag_PLL_180_deg_phase_locked = false;
d_stat = 0;
d_CRC_error_counter = 0;
d_frame_soh_offset = 0;
d_prev_valid_symbol_output = false;
d_await_post_lock_frame_decode = false;
d_post_lock_valid_symbols = 0U;
d_nav.clear_flags();
DLOG(INFO) << "BeiDou B1C telemetry decoder reset for satellite " << d_satellite;
}
void beidou_b1c_telemetry_decoder_gs::forecast(int noutput_items, gr_vector_int& ninput_items_required)
{
ninput_items_required[0] = BEIDOU_CNAV1_FRAME_SYMBOLS + std::max(noutput_items, 1) - 1;
}
bool beidou_b1c_telemetry_decoder_gs::decode_frame_from_window(
const Gnss_Synchro* window,
int32_t start_offset,
bool invert)
{
std::vector<float> frame(static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS));
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
const int32_t offset = ((start_offset % n) + n) % n;
for (int32_t i = 0; i < n; i++)
{
float sample = window[(offset + i) % n].Prompt_I;
if (invert)
{
sample = -sample;
}
frame[static_cast<size_t>(i)] = sample;
}
return d_nav.decode_frame_symbols(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, d_satellite.get_PRN());
}
void beidou_b1c_telemetry_decoder_gs::publish_navigation(double cn0_db_hz)
{
if (d_nav.have_new_ephemeris())
{
auto eph = std::make_shared<Beidou_Cnav1_Ephemeris>(d_nav.get_ephemeris());
eph->PRN = d_satellite.get_PRN();
message_port_pub(pmt::mp("telemetry"), pmt::make_any(eph));
#if __cplusplus == 201103L
const int default_precision = std::cout.precision();
#else
const auto default_precision{std::cout.precision()};
#endif
std::cout << TEXT_BLUE << "New BeiDou B-CNAV1 ephemeris in channel " << d_channel
<< " from satellite " << d_satellite
<< " with CN0=" << std::setprecision(2) << cn0_db_hz << std::setprecision(default_precision)
<< " dB-Hz" << TEXT_RESET << std::endl;
LOG(INFO) << "New BeiDou B-CNAV1 ephemeris from PRN " << d_satellite.get_PRN()
<< " in channel " << d_channel << " with CN0=" << cn0_db_hz << " dB-Hz";
}
if (d_nav.have_new_iono())
{
auto iono = std::make_shared<Beidou_Cnav1_Iono>(d_nav.get_iono());
iono->valid = true;
message_port_pub(pmt::mp("telemetry"), pmt::make_any(iono));
DLOG(INFO) << "New BeiDou B-CNAV1 iono model from PRN " << d_satellite.get_PRN()
<< " in channel " << d_channel;
}
if (d_nav.have_new_utc_model())
{
auto utc = std::make_shared<Beidou_Cnav1_Utc_Model>(d_nav.get_utc_model());
utc->valid = true;
message_port_pub(pmt::mp("telemetry"), pmt::make_any(utc));
DLOG(INFO) << "New BeiDou B-CNAV1 UTC model from PRN " << d_satellite.get_PRN()
<< " in channel " << d_channel;
}
if (d_nav.have_new_page_data())
{
Beidou_Cnav1_PageData_Message page_msg{};
page_msg.PRN = d_satellite.get_PRN();
page_msg.page_data = d_nav.get_page_data();
message_port_pub(pmt::mp("telemetry"), pmt::make_any(std::make_shared<Beidou_Cnav1_PageData_Message>(page_msg)));
DLOG(INFO) << "New BeiDou B-CNAV1 page data from PRN " << d_satellite.get_PRN()
<< " in channel " << d_channel
<< " PageID=" << d_nav.get_page_data().common.page_id;
}
// Same port as DNAV/GPS: bit chars for the nav-data monitor (not a placeholder label).
if (d_enable_navdata_monitor && !d_nav.get_last_nav_bits().empty())
{
d_nav_msg_packet.prn = static_cast<int32_t>(d_satellite.get_PRN());
d_nav_msg_packet.tow_at_current_symbol_ms = static_cast<int32_t>(d_TOW_at_current_symbol_ms);
d_nav_msg_packet.nav_message = d_nav.get_last_nav_bits();
const auto tmp_obj = std::make_shared<Nav_Message_Packet>(d_nav_msg_packet);
message_port_pub(pmt::mp("Nav_msg_from_TLM"), pmt::make_any(tmp_obj));
d_nav_msg_packet.nav_message.clear();
}
d_nav.clear_flags();
}
int beidou_b1c_telemetry_decoder_gs::general_work(
int noutput_items,
gr_vector_int& ninput_items,
gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items)
{
const auto* in = reinterpret_cast<const Gnss_Synchro*>(input_items[0]);
auto* out = reinterpret_cast<Gnss_Synchro*>(output_items[0]);
if (ninput_items[0] < BEIDOU_CNAV1_FRAME_SYMBOLS || noutput_items <= 0)
{
return 0;
}
const int32_t hist = BEIDOU_CNAV1_FRAME_SYMBOLS;
const Gnss_Synchro* frame_window = in;
Gnss_Synchro current_symbol = in[hist - 1];
current_symbol.PRN = d_satellite.get_PRN();
d_sample_counter++;
const bool just_locked_event = current_symbol.Flag_valid_symbol_output && !d_prev_valid_symbol_output;
d_prev_valid_symbol_output = current_symbol.Flag_valid_symbol_output;
if (current_symbol.Flag_valid_symbol_output && d_await_post_lock_frame_decode && !just_locked_event)
{
d_post_lock_valid_symbols++;
}
const bool at_frame_boundary =
d_flag_frame_sync &&
(d_sample_counter > d_frame_sync_index) &&
((d_sample_counter - d_frame_sync_index) % static_cast<uint64_t>(BEIDOU_CNAV1_FRAME_SYMBOLS) == 0U);
const bool post_lock_frame_ready =
d_await_post_lock_frame_decode &&
d_post_lock_valid_symbols >= static_cast<uint32_t>(BEIDOU_CNAV1_FRAME_SYMBOLS);
const bool periodic_reacq =
(d_stat == 0) &&
current_symbol.Flag_valid_symbol_output &&
!just_locked_event &&
((d_sample_counter % B1C_REACQ_INTERVAL_SYMBOLS) == 0U);
bool frame_decoded = false;
const bool history_ready = (d_sample_counter >= static_cast<uint64_t>(BEIDOU_CNAV1_FRAME_SYMBOLS));
if (history_ready)
{
const bool should_try_decode =
current_symbol.Flag_valid_symbol_output &&
((d_stat == 0 && just_locked_event) ||
(d_stat == 0 && periodic_reacq) ||
(d_stat == 0 && post_lock_frame_ready && !just_locked_event) ||
(d_stat == 2 && at_frame_boundary &&
d_sample_counter >= d_frame_sync_index + static_cast<uint64_t>(BEIDOU_CNAV1_FRAME_SYMBOLS)));
if (should_try_decode)
{
bool invert = false;
int32_t matched_offset = -1;
if (d_stat == 0 && (just_locked_event || periodic_reacq))
{
const int32_t expected_prn_scan = d_satellite.get_PRN();
std::vector<float> history_i;
std::vector<float> history_q;
extract_prompt_window(frame_window, history_i, history_q);
const auto sec_candidates =
find_secondary_code_candidates(history_q, expected_prn_scan);
const auto& candidate_offsets = sec_candidates.second;
// Exact secondary matches need no lag search; loose matches get a small span.
const int32_t local_offset_span =
(sec_candidates.first >= 1799.0F) ? 0 : B1C_SCAN_OFFSET_SPAN_LOOSE;
const auto cn0_db_hz = static_cast<float>(current_symbol.CN0_dB_hz);
int32_t full_decode_attempts = 0;
if (!candidate_offsets.empty())
{
for (int32_t candidate_offset : candidate_offsets)
{
for (int32_t delta = -local_offset_span; delta <= local_offset_span; delta++)
{
if (full_decode_attempts >= B1C_SCAN_MAX_FULL_DECODES)
{
break;
}
const int32_t start_offset =
(candidate_offset + delta + BEIDOU_CNAV1_FRAME_SYMBOLS) % BEIDOU_CNAV1_FRAME_SYMBOLS;
for (int inv_i = 0; inv_i < 2; ++inv_i)
{
if (full_decode_attempts >= B1C_SCAN_MAX_FULL_DECODES)
{
break;
}
const bool inv = (inv_i != 0);
if (!probe_frame_from_iq_window(
history_i, history_q, d_nav, expected_prn_scan, start_offset, inv))
{
continue;
}
int32_t fail_stage = -1;
++full_decode_attempts;
if (decode_frame_from_iq_window(
history_i, history_q, d_nav, expected_prn_scan, start_offset, inv,
cn0_db_hz, &fail_stage))
{
frame_decoded = true;
invert = inv;
matched_offset = start_offset;
break;
}
}
if (frame_decoded)
{
break;
}
}
if (frame_decoded || full_decode_attempts >= B1C_SCAN_MAX_FULL_DECODES)
{
break;
}
}
}
if (frame_decoded)
{
d_await_post_lock_frame_decode = false;
d_post_lock_valid_symbols = 0U;
}
else if (just_locked_event)
{
d_await_post_lock_frame_decode = true;
d_post_lock_valid_symbols = 1U;
}
}
else if (d_stat == 0 && post_lock_frame_ready)
{
frame_decoded = decode_frame_from_window(frame_window, 0, false);
if (!frame_decoded)
{
frame_decoded = decode_frame_from_window(frame_window, 0, true);
invert = frame_decoded;
}
if (frame_decoded)
{
matched_offset = 0;
d_await_post_lock_frame_decode = false;
d_post_lock_valid_symbols = 0U;
}
}
else
{
// Use polarity confirmed at first sync.
invert = d_flag_PLL_180_deg_phase_locked;
frame_decoded = decode_frame_from_window(frame_window, d_frame_soh_offset, invert);
if (frame_decoded)
{
matched_offset = d_frame_soh_offset;
}
}
if (frame_decoded)
{
d_CRC_error_counter = 0;
d_flag_PLL_180_deg_phase_locked = invert;
d_frame_sync_index = d_sample_counter;
const int32_t frame_soh_offset = (matched_offset >= 0) ? matched_offset : 0;
d_TOW_at_current_symbol_ms = compute_b1c_tow_ms_at_current_symbol(
d_nav.get_tow_s(), frame_soh_offset, d_symbol_duration_ms);
d_flag_valid_word = true;
if (d_dump_crc_stats && d_Tlm_CRC_Stats)
{
d_Tlm_CRC_Stats->update_CRC_stats(true);
}
publish_navigation(current_symbol.CN0_dB_hz);
if (!d_flag_frame_sync)
{
d_flag_frame_sync = true;
d_stat = 2;
d_frame_soh_offset = (matched_offset >= 0) ? matched_offset : 0;
LOG(INFO) << "Successful frame synchronization in channel " << d_channel
<< " for satellite " << d_satellite
<< " with CN0=" << current_symbol.CN0_dB_hz << " dB-Hz";
#if __cplusplus == 201103L
const int default_precision = std::cout.precision();
#else
const auto default_precision{std::cout.precision()};
#endif
std::cout << TEXT_GREEN << "B-CNAV1 frame sync on channel " << d_channel
<< ", satellite " << d_satellite
<< ", TOW=" << std::setprecision(3) << d_nav.get_tow_s()
<< std::setprecision(default_precision) << " s" << TEXT_RESET << std::endl;
}
}
else if (d_stat == 2)
{
d_CRC_error_counter++;
if (d_dump_crc_stats && d_Tlm_CRC_Stats)
{
d_Tlm_CRC_Stats->update_CRC_stats(false);
}
if (d_CRC_error_counter > CRC_ERROR_LIMIT)
{
DLOG(INFO) << "B-CNAV1 frame sync lost for satellite " << d_satellite
<< " in channel " << d_channel;
d_flag_frame_sync = false;
d_flag_valid_word = false;
d_frame_soh_offset = 0;
d_stat = 0;
d_CRC_error_counter = 0;
d_await_post_lock_frame_decode = false;
d_post_lock_valid_symbols = 0U;
}
}
}
}
if (d_flag_valid_word && !frame_decoded)
{
d_TOW_at_current_symbol_ms += d_symbol_duration_ms;
if (!current_symbol.Flag_valid_symbol_output)
{
d_flag_valid_word = false;
}
}
if (d_flag_PLL_180_deg_phase_locked)
{
current_symbol.Carrier_phase_rads += GNSS_PI;
current_symbol.Flag_PLL_180_deg_phase_locked = true;
}
else
{
current_symbol.Flag_PLL_180_deg_phase_locked = false;
}
consume_each(1);
if (d_flag_valid_word)
{
current_symbol.TOW_at_current_symbol_ms = d_TOW_at_current_symbol_ms;
current_symbol.Flag_valid_word = true;
if (d_dump)
{
try
{
write_value(d_dump_file, static_cast<double>(d_TOW_at_current_symbol_ms) / 1000.0);
write_value(d_dump_file, current_symbol.Tracking_sample_counter);
write_value(d_dump_file, static_cast<double>(d_TOW_at_current_symbol_ms) / 1000.0);
write_value(d_dump_file, current_symbol.Prompt_I > 0.0 ? 1 : -1);
write_value(d_dump_file, static_cast<int32_t>(current_symbol.PRN));
}
catch (const std::ofstream::failure& e)
{
LOG(WARNING) << "Exception writing Telemetry BeiDou B1C dump file " << e.what();
}
}
if (d_tow_to_trk)
{
const auto tow_obj = std::make_shared<TOW_to_trk>(TOW_to_trk(
std::string(current_symbol.Signal),
d_channel,
d_TOW_at_current_symbol_ms,
current_symbol.Tracking_sample_counter,
d_nav.get_ephemeris().WN,
d_satellite.get_PRN()));
message_port_pub(pmt::mp("telemetry_to_trk"), pmt::make_any(tow_obj));
}
out[0] = std::move(current_symbol);
return 1;
}
return 0;
}
@@ -0,0 +1,80 @@
/*!
* \file beidou_b1c_telemetry_decoder_gs.h
* \brief BeiDou B1C B-CNAV1 telemetry decoder block
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_TELEMETRY_DECODER_GS_H
#define GNSS_SDR_BEIDOU_B1C_TELEMETRY_DECODER_GS_H
#include "beidou_cnav1_navigation_message.h"
#include "nav_message_packet.h"
#include "telemetry_impl_interface.h"
#include "tlm_conf.h"
#include <vector>
class Gnss_Synchro;
class beidou_b1c_telemetry_decoder_gs;
using beidou_b1c_telemetry_decoder_gs_sptr = gnss_shared_ptr<beidou_b1c_telemetry_decoder_gs>;
beidou_b1c_telemetry_decoder_gs_sptr beidou_b1c_make_telemetry_decoder_gs(
const Gnss_Satellite& satellite,
const Tlm_Conf& conf);
class beidou_b1c_telemetry_decoder_gs : public telemetry_impl_interface
{
public:
~beidou_b1c_telemetry_decoder_gs() override;
void set_satellite(const Gnss_Satellite& satellite) override;
void set_channel(int channel) override;
void reset() override;
void forecast(int noutput_items, gr_vector_int& ninput_items_required) override;
int general_work(int noutput_items, gr_vector_int& ninput_items,
gr_vector_const_void_star& input_items, gr_vector_void_star& output_items) override;
private:
friend beidou_b1c_telemetry_decoder_gs_sptr beidou_b1c_make_telemetry_decoder_gs(
const Gnss_Satellite& satellite, const Tlm_Conf& conf);
beidou_b1c_telemetry_decoder_gs(const Gnss_Satellite& satellite, const Tlm_Conf& conf);
bool decode_frame_from_window(const Gnss_Synchro* window, int32_t start_offset, bool invert);
void publish_navigation(double cn0_db_hz);
Beidou_Cnav1_Navigation_Message d_nav;
Nav_Message_Packet d_nav_msg_packet;
Gnss_Satellite d_satellite;
std::string d_dump_filename;
std::ofstream d_dump_file;
std::unique_ptr<Tlm_CRC_Stats> d_Tlm_CRC_Stats;
uint64_t d_sample_counter{};
uint64_t d_frame_sync_index{};
int32_t d_channel{};
int32_t d_stat{};
int32_t d_CRC_error_counter{};
int32_t d_frame_soh_offset{0};
uint32_t d_TOW_at_current_symbol_ms{};
uint32_t d_symbol_duration_ms{};
bool d_flag_valid_word{false};
bool d_flag_frame_sync{false};
bool d_flag_PLL_180_deg_phase_locked{false};
bool d_dump{false};
bool d_dump_mat{false};
bool d_remove_dat{false};
bool d_enable_navdata_monitor{false};
bool d_dump_crc_stats{false};
bool d_tow_to_trk{false};
bool d_prev_valid_symbol_output{false};
bool d_await_post_lock_frame_decode{false};
uint32_t d_post_lock_valid_symbols{0U};
};
#endif
@@ -15,6 +15,7 @@
*/
#include "dll_pll_tracking_adapter.h"
#include "Beidou_B1C.h"
#include "Beidou_B1I.h"
#include "Beidou_B3I.h"
#include "GLONASS_L1_L2_CA.h"
@@ -28,6 +29,7 @@
#include "configuration_interface.h"
#include "display.h"
#include "qzss.h"
#include "signal_flag.h"
#if USE_GLOG_AND_GFLAGS
#include <glog/logging.h>
@@ -76,6 +78,8 @@ signal_info get_signal_info(signal_flag sig_flag)
return {'C', {'B', '1', '\0'}, "BEIDOU B1I", BEIDOU_B1I_CODE_RATE_CPS, BEIDOU_B1I_CODE_LENGTH_CHIPS, 20, false, true};
case BDS_B3:
return {'C', {'B', '3', '\0'}, "BEIDOU B3I", BEIDOU_B3I_CODE_RATE_CPS, BEIDOU_B3I_CODE_LENGTH_CHIPS, 20, false, false}; // Does false, false make sense?
case BDS_B1C:
return {'C', {'1', 'D', '\0'}, "BeiDou B1C", BEIDOU_B1C_CODE_RATE_CPS, BEIDOU_B1C_CODE_LENGTH_CHIPS, 1, true, false};
case QZS_J1:
return {'J', {'J', '1', '\0'}, "QZSS L1 C/A", QZSS_L1_CHIP_RATE, QZSS_L1_CODE_LENGTH, 20, false, true};
case QZS_J5:
@@ -87,7 +91,7 @@ signal_info get_signal_info(signal_flag sig_flag)
return {};
}
void check_and_configure_trk_params(const ConfigurationInterface* configuration, const std::string& role, const signal_info& sig_info, Dll_Pll_Conf& trk_params)
void check_and_configure_trk_params(const ConfigurationInterface* configuration, const std::string& role, signal_flag sig_flag, const signal_info& sig_info, Dll_Pll_Conf& trk_params)
{
trk_params.SetFromConfiguration(configuration, role);
trk_params.vector_length = static_cast<int>(std::round(trk_params.fs_in / (sig_info.code_chip_rate / sig_info.code_length_chips)));
@@ -145,6 +149,44 @@ void check_and_configure_trk_params(const ConfigurationInterface* configuration,
{
std::cout << TEXT_RED << "WARNING: " << sig_info.sig_name << ". PLL or DLL narrow tracking bandwidth is higher than wide tracking one" << TEXT_RESET << '\n';
}
if (sig_flag == BDS_B1C)
{
std::string acq_role = role;
const std::string tracking_prefix = "Tracking_";
if (acq_role.compare(0, tracking_prefix.size(), tracking_prefix) == 0)
{
acq_role.replace(0, tracking_prefix.size(), "Acquisition_");
}
// Tracking_*.qmboc overrides Acquisition_*.qmboc; else inherit / default.
const std::string acq_qmboc_key = acq_role + ".qmboc";
const std::string trk_qmboc_key = role + ".qmboc";
const bool acq_qmboc = configuration->property(acq_qmboc_key, trk_params.qmboc);
trk_params.qmboc = configuration->property(trk_qmboc_key, acq_qmboc);
if (configuration->is_present(acq_qmboc_key) && configuration->is_present(trk_qmboc_key) && acq_qmboc != trk_params.qmboc)
{
std::cout << TEXT_RED << "WARNING: BeiDou B1C: " << acq_qmboc_key << "=" << (acq_qmboc ? "true" : "false")
<< " differs from " << trk_qmboc_key << "=" << (trk_params.qmboc ? "true" : "false")
<< "; using tracking value." << TEXT_RESET << '\n';
}
// Pilot secondary sync needs one-symbol correlation.
if (trk_params.track_pilot && trk_params.extend_correlation_symbols != 1)
{
std::cout << TEXT_RED << "WARNING: BeiDou B1C: extend_correlation_symbols forced to 1 while tracking the pilot "
<< "(secondary-code sync requires one-symbol correlation). Configured value was "
<< trk_params.extend_correlation_symbols << TEXT_RESET << '\n';
trk_params.extend_correlation_symbols = 1;
}
if (trk_params.track_pilot)
{
const auto min_bit_sync_limit_s = static_cast<uint32_t>(BEIDOU_B1C_FRAME_PERIOD_S + 30);
if (trk_params.bit_synchronization_time_limit_s < min_bit_sync_limit_s)
{
trk_params.bit_synchronization_time_limit_s = min_bit_sync_limit_s;
}
}
}
}
} // namespace
@@ -166,7 +208,7 @@ DllPllTrackingAdapter::DllPllTrackingAdapter(
if (!sig_info.sig_name.empty())
{
check_and_configure_trk_params(configuration, role_, sig_info, trk_params_);
check_and_configure_trk_params(configuration, role_, sig_flag, sig_info, trk_params_);
if (trk_params_.item_type == "gr_complex")
{
@@ -22,6 +22,8 @@
*/
#include "dll_pll_veml_tracking.h"
#include "Beidou_B1C.h"
#include "Beidou_B1C_codes.h"
#include "Beidou_B1I.h"
#include "Beidou_B3I.h"
#include "GLONASS_L1_L2_CA.h"
@@ -33,6 +35,7 @@
#include "Galileo_E5b.h"
#include "Galileo_E6.h"
#include "MATH_CONSTANTS.h"
#include "beidou_b1c_signal_replica.h"
#include "beidou_b1i_signal_replica.h"
#include "beidou_b3i_signal_replica.h"
#include "galileo_e1_signal_replica.h"
@@ -180,6 +183,7 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_)
map_signal_pretty_name["7X"] = "E5b";
map_signal_pretty_name["L5"] = "L5";
map_signal_pretty_name["B1"] = "B1I";
map_signal_pretty_name["1D"] = "B1C";
map_signal_pretty_name["B3"] = "B3I";
map_signal_pretty_name["E6"] = "E6";
map_signal_pretty_name["J1"] = "L1 C/A";
@@ -449,6 +453,31 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_)
d_data_secondary_code_length = static_cast<uint32_t>(BEIDOU_B3I_SECONDARY_CODE_LENGTH);
d_data_secondary_code_string = BEIDOU_B3I_SECONDARY_CODE_STR;
}
else if (d_signal_type == "1D")
{
d_signal_carrier_freq = BEIDOU_B1C_FREQ_HZ;
d_code_period = BEIDOU_B1C_CODE_PERIOD_S;
d_code_chip_rate = BEIDOU_B1C_CODE_RATE_CPS;
d_code_length_chips = static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS);
d_symbols_per_bit = BEIDOU_B1C_SYMBOLS_PER_BIT;
d_correlation_length_ms = static_cast<int32_t>(BEIDOU_B1C_CODE_PERIOD_MS);
d_code_samples_per_chip = d_trk_parameters.qmboc ? 12 : 2;
d_veml = true;
d_trk_parameters.spc = d_trk_parameters.early_late_space_chips;
d_trk_parameters.slope = static_cast<float>(-CalculateSlopeAbs(&SinBocCorrelationFunction<1, 1>, d_trk_parameters.spc));
d_trk_parameters.y_intercept = static_cast<float>(GetYInterceptAbs(&SinBocCorrelationFunction<1, 1>, d_trk_parameters.spc));
if (d_trk_parameters.track_pilot)
{
d_secondary = true;
d_secondary_code_length = static_cast<uint32_t>(BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH);
d_signal_pretty_name = d_signal_pretty_name + "P";
}
else
{
d_secondary = false;
d_signal_pretty_name = d_signal_pretty_name + "D";
}
}
else
{
LOG(WARNING) << "Invalid Signal argument when instantiating tracking blocks";
@@ -609,8 +638,7 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_)
d_carrier_loop_filter.set_params(d_trk_parameters.fll_bw_hz, d_trk_parameters.pll_bw_hz, d_trk_parameters.pll_filter_order);
// Initialization of local code replica
// Get space for a vector with the sinboc(1,1) replica sampled 2x/chip
d_tracking_code.resize(2 * d_code_length_chips, 0.0);
d_tracking_code.resize(static_cast<size_t>(d_code_samples_per_chip) * static_cast<size_t>(d_code_length_chips), 0.0);
// correlator outputs (scalar)
if (d_veml)
{
@@ -668,7 +696,7 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_)
// Enable Data component prompt correlator (slave to Pilot prompt) if tracking uses Pilot signal
if (d_trk_parameters.track_pilot)
{
d_data_code.resize(2 * d_code_length_chips, 0.0);
d_data_code.resize(static_cast<size_t>(d_code_samples_per_chip) * static_cast<size_t>(d_code_length_chips), 0.0);
}
// --- Initializations ---
@@ -806,6 +834,7 @@ void dll_pll_veml_tracking::start_tracking()
d_carrier_doppler_hz = d_acq_carrier_doppler_hz;
d_carrier_phase_step_rad = TWO_PI * d_carrier_doppler_hz / d_trk_parameters.fs_in;
d_carrier_phase_rate_step_rad = 0.0;
d_b1c_prelock_output_pending = false;
d_carr_ph_history.clear();
d_code_ph_history.clear();
std::array<char, 3> Signal_{};
@@ -950,6 +979,32 @@ void dll_pll_veml_tracking::start_tracking()
d_Prompt_circular_buffer.set_capacity(d_secondary_code_length);
}
}
else if (d_systemName == "Beidou" && d_signal_type == "1D")
{
const int32_t prn_index = static_cast<int32_t>(d_acquisition_gnss_synchro->PRN) - 1;
const auto b1c_replica_fs = static_cast<int32_t>(d_code_samples_per_chip * BEIDOU_B1C_CODE_RATE_CPS);
if (d_trk_parameters.track_pilot)
{
const std::array<char, 3> pilot_signal = {{'1', 'P', '\0'}};
// Pilot BOC(1,1) for the real multicorrelator (QMBOC is acquisition-only).
beidou_b1c_code_gen_float_sampled(
d_tracking_code, pilot_signal, d_trk_parameters.qmboc,
d_acquisition_gnss_synchro->PRN, b1c_replica_fs, 0, false);
beidou_b1c_code_gen_float_sampled(
d_data_code, Signal_, d_trk_parameters.qmboc,
d_acquisition_gnss_synchro->PRN, b1c_replica_fs, 0, false);
d_Prompt_Data[0] = gr_complex(0.0, 0.0);
d_multicorrelator_cpu.set_data_code_and_prompt_tap(
d_code_samples_per_chip * d_code_length_chips, d_data_code.data(), d_prompt_data_shift);
d_secondary_code_string = BEIDOU_B1C_PILOT_SECONDARY_CODE[prn_index];
}
else
{
beidou_b1c_code_gen_float_sampled(
d_tracking_code, Signal_, d_trk_parameters.qmboc,
d_acquisition_gnss_synchro->PRN, b1c_replica_fs, 0, false);
}
}
else if (d_systemName == "Beidou" && d_signal_type == "B3")
{
@@ -1170,7 +1225,15 @@ bool dll_pll_veml_tracking::acquire_secondary()
}
}
if (abs(corr_value) == static_cast<int32_t>(d_secondary_code_length))
auto lock_threshold = static_cast<int32_t>(d_secondary_code_length);
if (d_systemName == "Beidou" && d_signal_type == "1D")
{
const float ratio = std::min(std::max(d_trk_parameters.b1c_secondary_lock_ratio, 0.5F), 1.0F);
lock_threshold = std::max(1, static_cast<int32_t>(std::lround(
ratio * static_cast<float>(d_secondary_code_length))));
}
if (abs(corr_value) >= lock_threshold)
{
if (corr_value < 0)
{
@@ -1397,6 +1460,7 @@ void dll_pll_veml_tracking::clear_tracking_vars()
d_code_error_filt_chips = 0.0;
d_current_symbol = 0;
d_current_data_symbol = 0;
d_b1c_prelock_output_pending = false;
d_Prompt_circular_buffer.clear();
d_carrier_phase_rate_step_rad = 0.0;
d_code_phase_rate_step_chips = 0.0;
@@ -1628,6 +1692,59 @@ void dll_pll_veml_tracking::save_correlation_results()
}
void dll_pll_veml_tracking::map_correlator_to_iq(const gr_complex &c, float &out_i, float &out_q) const
{
if (d_interchange_iq)
{
out_i = c.imag();
out_q = c.real();
}
else
{
out_i = c.real();
out_q = c.imag();
}
}
void dll_pll_veml_tracking::assign_correlators_to_synchro(Gnss_Synchro &synchro) const
{
if (d_systemName == "Beidou" && d_signal_type == "1D" && d_trk_parameters.track_pilot)
{
const gr_complex pilot_raw = *d_Prompt;
gr_complex pilot_ref = (d_state == 2) ? pilot_raw : d_P_accu;
const float pilot_abs = std::abs(pilot_ref);
gr_complex data_aligned = d_P_data_accu;
if (pilot_abs > 1e-6F)
{
data_aligned = d_P_data_accu * std::conj(pilot_ref) / pilot_abs;
}
float data_prompt = d_trk_parameters.b1c_prompt_use_data_q ? data_aligned.imag() : data_aligned.real();
float pilot_i_for_sync = 0.0F;
float pilot_q_unused = 0.0F;
map_correlator_to_iq(pilot_raw, pilot_i_for_sync, pilot_q_unused);
float pilot_prompt = pilot_i_for_sync;
if (d_trk_parameters.b1c_prompt_normalize_power)
{
data_prompt *= d_trk_parameters.b1c_data_prompt_scale;
pilot_prompt *= d_trk_parameters.b1c_pilot_prompt_scale;
}
synchro.Prompt_I = static_cast<double>(data_prompt);
synchro.Prompt_Q = static_cast<double>(pilot_prompt);
return;
}
float prompt_i = 0.0F;
float prompt_q = 0.0F;
map_correlator_to_iq(d_P_data_accu, prompt_i, prompt_q);
synchro.Prompt_I = static_cast<double>(prompt_i);
synchro.Prompt_Q = static_cast<double>(prompt_q);
}
void dll_pll_veml_tracking::log_data()
{
if (d_dump)
@@ -2055,6 +2172,24 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused)
// enable write dump file this cycle (valid DLL/PLL cycle)
log_data();
// Prefill telemetry history with 10 ms B1C symbols before secondary lock.
if (d_systemName == "Beidou" && d_signal_type == "1D" && d_trk_parameters.track_pilot &&
!d_pull_in_transitory && d_current_data_symbol == 0)
{
d_P_data_accu = d_Prompt_Data[0];
current_synchro_data = *d_acquisition_gnss_synchro;
assign_correlators_to_synchro(current_synchro_data);
current_synchro_data.Code_phase_samples = d_rem_code_phase_samples;
current_synchro_data.Carrier_phase_rads = d_acc_carrier_phase_rad;
current_synchro_data.Carrier_Doppler_hz = d_carrier_doppler_hz;
current_synchro_data.CN0_dB_hz = d_CN0_SNV_dB_Hz;
current_synchro_data.correlation_length_ms = d_correlation_length_ms;
// Invalid until secondary lock (no BCH/LDPC yet).
current_synchro_data.Flag_valid_symbol_output = false;
d_b1c_prelock_output_pending = true;
d_P_data_accu = gr_complex(0.0, 0.0);
}
if (!d_pull_in_transitory)
{
if (d_secondary)
@@ -2203,16 +2338,7 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused)
// ########### Output the tracking results to Telemetry block ##########
// Fill the acquisition data
current_synchro_data = *d_acquisition_gnss_synchro;
if (d_interchange_iq)
{
current_synchro_data.Prompt_I = static_cast<double>(d_P_data_accu.imag());
current_synchro_data.Prompt_Q = static_cast<double>(d_P_data_accu.real());
}
else
{
current_synchro_data.Prompt_I = static_cast<double>(d_P_data_accu.real());
current_synchro_data.Prompt_Q = static_cast<double>(d_P_data_accu.imag());
}
assign_correlators_to_synchro(current_synchro_data);
current_synchro_data.Code_phase_samples = d_rem_code_phase_samples;
current_synchro_data.Carrier_phase_rads = d_acc_carrier_phase_rad;
current_synchro_data.Carrier_Doppler_hz = d_carrier_doppler_hz;
@@ -2255,16 +2381,7 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused)
// ########### Output the tracking results to Telemetry block ##########
// Fill the acquisition data
current_synchro_data = *d_acquisition_gnss_synchro;
if (d_interchange_iq)
{
current_synchro_data.Prompt_I = static_cast<double>(d_P_data_accu.imag());
current_synchro_data.Prompt_Q = static_cast<double>(d_P_data_accu.real());
}
else
{
current_synchro_data.Prompt_I = static_cast<double>(d_P_data_accu.real());
current_synchro_data.Prompt_Q = static_cast<double>(d_P_data_accu.imag());
}
assign_correlators_to_synchro(current_synchro_data);
current_synchro_data.Code_phase_samples = d_rem_code_phase_samples;
current_synchro_data.Carrier_phase_rads = d_acc_carrier_phase_rad;
current_synchro_data.Carrier_Doppler_hz = d_carrier_doppler_hz;
@@ -2322,11 +2439,24 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused)
consume_each(d_current_prn_length_samples);
// d_sample_counter += static_cast<uint64_t>(d_current_prn_length_samples);
if (current_synchro_data.Flag_valid_symbol_output || loss_of_lock)
if (current_synchro_data.Flag_valid_symbol_output || loss_of_lock || d_b1c_prelock_output_pending)
{
current_synchro_data.fs = static_cast<int64_t>(d_trk_parameters.fs_in);
current_synchro_data.Tracking_sample_counter = this->nitems_read(0);
current_synchro_data.Flag_valid_symbol_output = !loss_of_lock;
if (loss_of_lock)
{
current_synchro_data.Flag_valid_symbol_output = false;
d_b1c_prelock_output_pending = false;
}
else if (d_b1c_prelock_output_pending)
{
current_synchro_data.Flag_valid_symbol_output = false;
d_b1c_prelock_output_pending = false;
}
else
{
current_synchro_data.Flag_valid_symbol_output = true;
}
current_synchro_data.Flag_PLL_180_deg_phase_locked = d_Flag_PLL_180_deg_phase_locked;
// generate new tag associated with gnss-synchro object
@@ -85,6 +85,8 @@ private:
void clear_tracking_vars();
void save_correlation_results();
void log_data();
void map_correlator_to_iq(const gr_complex &c, float &out_i, float &out_q) const;
void assign_correlators_to_synchro(Gnss_Synchro &synchro) const;
void configure_bit_synchronizer();
bool cn0_and_tracking_lock_status(double coh_integration_time_s);
bool acquire_secondary();
@@ -219,6 +221,7 @@ private:
bool d_Flag_PLL_180_deg_phase_locked;
bool d_use_histogram_bit_sync;
bool d_wait_for_bit_edge{false};
bool d_b1c_prelock_output_pending{false};
};
@@ -159,4 +159,11 @@ void Dll_Pll_Conf::SetFromConfiguration(const ConfigurationInterface *configurat
bs_transition_window_epochs = configuration->property(role + ".bs_transition_window_epochs", bs_transition_window_epochs);
bs_tentative_events_required = configuration->property(role + ".bs_tentative_events_required", bs_tentative_events_required);
bs_use_phase_dot_detector = configuration->property(role + ".bs_use_phase_dot_detector", bs_use_phase_dot_detector);
qmboc = configuration->property(role + ".qmboc", qmboc);
b1c_prompt_use_data_q = configuration->property(role + ".b1c_prompt_use_data_q", b1c_prompt_use_data_q);
b1c_prompt_normalize_power = configuration->property(role + ".b1c_prompt_normalize_power", b1c_prompt_normalize_power);
b1c_data_prompt_scale = configuration->property(role + ".b1c_data_prompt_scale", b1c_data_prompt_scale);
b1c_pilot_prompt_scale = configuration->property(role + ".b1c_pilot_prompt_scale", b1c_pilot_prompt_scale);
b1c_secondary_lock_ratio = configuration->property(role + ".b1c_secondary_lock_ratio", b1c_secondary_lock_ratio);
}
@@ -90,6 +90,13 @@ public:
bool dump_mat{true};
bool tow_to_trk{false};
bool bs_use_phase_dot_detector{true};
// BeiDou B1C-specific options.
bool qmboc{true}; //!< QMBOC local replica (same key as Acquisition_*.qmboc).
bool b1c_prompt_use_data_q{true};
bool b1c_prompt_normalize_power{true};
float b1c_data_prompt_scale{1.7320508F}; // sqrt(3), compensates 1:3 data:pilot power.
float b1c_pilot_prompt_scale{1.0F};
float b1c_secondary_lock_ratio{0.88F};
};
+15 -1
View File
@@ -28,6 +28,7 @@
#include "acquisition_interface.h"
#include "array_signal_conditioner.h"
#include "beamformer_filter.h"
#include "beidou_b1c_telemetry_decoder_gs.h"
#include "beidou_dnav_telemetry_decoder_gs.h"
#include "byte_to_short.h"
#include "channel.h"
@@ -209,6 +210,7 @@ const auto signal_mapping = std::vector<std::pair<std::string, std::string>>{
{"1G", "GLONASS L1 C/A"},
{"2G", "GLONASS L2 C/A"},
{"B1", "BEIDOU B1I"},
{"1D", "BEIDOU B1C"},
{"B3", "BEIDOU B3I"},
{"7X", "GALILEO E5b I (I/NAV OS)"},
{"J1", "QZSS L1 C/A"},
@@ -492,6 +494,10 @@ std::unique_ptr<AcquisitionInterface> get_acq_block(
{
return std::make_unique<PcpsAcquisitionAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B1);
}
else if (implementation == "BEIDOU_B1C_PCPS_Ambiguous_Acquisition")
{
return std::make_unique<PcpsAcquisitionAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B1C);
}
else if (implementation == "BEIDOU_B3I_PCPS_Acquisition")
{
return std::make_unique<PcpsAcquisitionAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B3);
@@ -604,6 +610,10 @@ std::unique_ptr<TrackingInterface> get_trk_block(
{
return std::make_unique<DllPllTrackingAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B1);
}
else if (implementation == "BEIDOU_B1C_DLL_PLL_VEML_Tracking")
{
return std::make_unique<DllPllTrackingAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B1C);
}
else if (implementation == "BEIDOU_B3I_DLL_PLL_Tracking")
{
return std::make_unique<DllPllTrackingAdapter>(configuration, role, implementation, in_streams, out_streams, BDS_B3);
@@ -685,7 +695,11 @@ std::unique_ptr<TelemetryDecoderInterface> get_tlm_block(
{
telemetry_impl_interface_sptr telemetry;
if (implementation == "GPS_L1_CA_Telemetry_Decoder")
if (implementation == "BEIDOU_B1C_Telemetry_Decoder")
{
telemetry = beidou_b1c_make_telemetry_decoder_gs(Gnss_Satellite{}, get_tlm_conf(configuration, role));
}
else if (implementation == "GPS_L1_CA_Telemetry_Decoder")
{
telemetry = gps_l1_ca_make_telemetry_decoder_gs(get_tlm_conf(configuration, role));
}
+13
View File
@@ -20,6 +20,7 @@
*/
#include "gnss_flowgraph.h"
#include "Beidou_B1C.h"
#include "Beidou_B1I.h"
#include "GLONASS_L1_L2_CA.h"
#include "GPS_L1_CA.h"
@@ -91,6 +92,7 @@ const auto signal_mapping = std::unordered_map<std::string, std::pair<std::strin
{"7X", {"Galileo", "E5b"}},
{"E6", {"Galileo", "E6"}},
{"B1", {"Beidou", "B1"}},
{"1D", {"Beidou", "B1C"}},
{"B3", {"Beidou", "B3"}},
{"1G", {"Glonass", "L1"}},
{"2G", {"Glonass", "L2"}},
@@ -258,6 +260,7 @@ void GNSSFlowgraph::init()
mapStringValues_["1G"] = evGLO_1G;
mapStringValues_["2G"] = evGLO_2G;
mapStringValues_["B1"] = evBDS_B1;
mapStringValues_["1D"] = evBDS_B1C;
mapStringValues_["B3"] = evBDS_B3;
mapStringValues_["J1"] = evQZS_J1;
mapStringValues_["J5"] = evQZS_J5;
@@ -1176,6 +1179,9 @@ int GNSSFlowgraph::connect_signal_conditioners_to_channels()
case evBDS_B1:
acq_fs = BEIDOU_B1I_OPT_ACQ_FS_SPS;
break;
case evBDS_B1C:
acq_fs = BEIDOU_B1C_OPT_ACQ_FS_SPS;
break;
case evGLO_1G:
case evGLO_2G:
case evBDS_B3:
@@ -2182,6 +2188,13 @@ void GNSSFlowgraph::set_signals_list()
for (const auto& prn : available_prn_map.at(gnss_system_str))
{
if (signal_str == "1D")
{
if ((prn >= 1U && prn <= 5U) || (prn >= 59U && prn <= 63U))
{
continue; // GEO satellites do not broadcast B1C (ICD section 3.1)
}
}
if (signal_str == "J5" && prn > QZSS_L5_MAX_PRN)
{
// QZSS L1 C/B PRNs (203-206) do not transmit an L5 signal
+1
View File
@@ -264,6 +264,7 @@ private:
evGLO_1G,
evGLO_2G,
evBDS_B1,
evBDS_B1C,
evBDS_B3,
evQZS_J1,
evQZS_J5
+44
View File
@@ -0,0 +1,44 @@
/*!
* \file Beidou_B1C.h
* \brief Defines system parameters for BeiDou B1C open service signal
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* Parameter values from BDS-SIS-ICD-B1C-1.0 (2017-12).
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_H
#define GNSS_SDR_BEIDOU_B1C_H
#include "gnss_frequencies.h"
#include <cstdint>
/** \addtogroup Core
* \{ */
/** \addtogroup System_Parameters
* \{ */
constexpr double BEIDOU_B1C_FREQ_HZ = FREQ1; //!< §4.2.2, Table 4-1 [Hz]
constexpr double BEIDOU_B1C_CODE_RATE_CPS = 1.023e6; //!< §5.2.1 [chips/s]
constexpr double BEIDOU_B1C_CODE_LENGTH_CHIPS = 10230.0; //!< §5.2.1 [chips]
constexpr double BEIDOU_B1C_CODE_PERIOD_S = 0.01; //!< §5.2.1, 10 ms [s]
constexpr uint32_t BEIDOU_B1C_CODE_PERIOD_MS = 10; //!< §5.2.1 [ms]
constexpr uint32_t BEIDOU_B1C_OPT_ACQ_FS_SPS = 4000000; //!< 4 samples/chip at 1.023 Mcps
constexpr int32_t BEIDOU_B1C_PILOT_SECONDARY_CODE_LENGTH = 1800; //!< §5.2.2 [chips]
constexpr int32_t BEIDOU_B1C_TELEMETRY_RATE_SPS = 100; //!< §6.2 [symbols/s]
constexpr int32_t BEIDOU_B1C_SYMBOLS_PER_BIT = 1; //!< 100 sps, 10 ms/code period
constexpr int32_t BEIDOU_B1C_FRAME_LENGTH_SYMBOLS = 1800; //!< §6.2 [symbols]
constexpr int32_t BEIDOU_B1C_FRAME_PERIOD_S = 18; //!< §6.2 [s]
constexpr int32_t BEIDOU_B1C_NUMBER_OF_PRNS = 63; //!< §5.2.1
/** \} */
/** \} */
#endif // GNSS_SDR_BEIDOU_B1C_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
/*!
* \file Beidou_B1C_codes.h
* \brief Precomputed BeiDou B1C primary and secondary codes (declarations)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* Auto-generated by generate_beidou_b1c_codes.py. Do not edit manually.
* Definitions live in Beidou_B1C_codes.cc.
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_CODES_H
#define GNSS_SDR_BEIDOU_B1C_CODES_H
#include <cstddef>
constexpr size_t BEIDOU_B1C_PRIMARY_CODE_STR_LENGTH = 2558;
constexpr size_t BEIDOU_B1C_PILOT_SECONDARY_CODE_STR_LENGTH = 1800;
extern const char BEIDOU_B1C_DATA_PRIMARY_CODE[63][2559];
extern const char BEIDOU_B1C_PILOT_PRIMARY_CODE[63][2559];
extern const char BEIDOU_B1C_PILOT_SECONDARY_CODE[63][1801];
#endif
+409
View File
@@ -0,0 +1,409 @@
/*!
* \file Beidou_B1C_prn.h
* \brief B1C PRN Weil code parameters from BDS-SIS-ICD-B1C-1.0 Table 5-2/5-3/5-4
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_PRN_H
#define GNSS_SDR_BEIDOU_B1C_PRN_H
#include <cstdint>
constexpr int32_t BEIDOU_B1C_DATA_PRN_W[63] = {
2678,
4802,
958,
859,
3843,
2232,
124,
4352,
1816,
1126,
1860,
4800,
2267,
424,
4192,
4333,
2656,
4148,
243,
1330,
1593,
1470,
882,
3202,
5095,
2546,
1733,
4795,
4577,
1627,
3638,
2553,
3646,
1087,
1843,
216,
2245,
726,
1966,
670,
4130,
53,
4830,
182,
2181,
2006,
1080,
2288,
2027,
271,
915,
497,
139,
3693,
2054,
4342,
3342,
2592,
1007,
310,
4203,
455,
4318,
};
constexpr int32_t BEIDOU_B1C_DATA_PRN_P[63] = {
699,
694,
7318,
2127,
715,
6682,
7850,
5495,
1162,
7682,
6792,
9973,
6596,
2092,
19,
10151,
6297,
5766,
2359,
7136,
1706,
2128,
6827,
693,
9729,
1620,
6805,
534,
712,
1929,
5355,
6139,
6339,
1470,
6867,
7851,
1162,
7659,
1156,
2672,
6043,
2862,
180,
2663,
6940,
1645,
1582,
951,
6878,
7701,
1823,
2391,
2606,
822,
6403,
239,
442,
6769,
2560,
2502,
5072,
7268,
341,
};
constexpr int32_t BEIDOU_B1C_PILOT_PRN_W[63] = {
796,
156,
4198,
3941,
1374,
1338,
1833,
2521,
3175,
168,
2715,
4408,
3160,
2796,
459,
3594,
4813,
586,
1428,
2371,
2285,
3377,
4965,
3779,
4547,
1646,
1430,
607,
2118,
4709,
1149,
3283,
2473,
1006,
3670,
1817,
771,
2173,
740,
1433,
2458,
3459,
2155,
1205,
413,
874,
2463,
1106,
1590,
3873,
4026,
4272,
3556,
128,
1200,
130,
4494,
1871,
3073,
4386,
4098,
1923,
1176,
};
constexpr int32_t BEIDOU_B1C_PILOT_PRN_P[63] = {
7575,
2369,
5688,
539,
2270,
7306,
6457,
6254,
5644,
7119,
1402,
5557,
5764,
1073,
7001,
5910,
10060,
2710,
1546,
6887,
1883,
5613,
5062,
1038,
10170,
6484,
1718,
2535,
1158,
526,
7331,
5844,
6423,
6968,
1280,
1838,
1989,
6468,
2091,
1581,
1453,
6252,
7122,
7711,
7216,
2113,
1095,
1628,
1713,
6102,
6123,
6070,
1115,
8047,
6795,
2575,
53,
1729,
6388,
682,
5565,
7160,
2277,
};
constexpr int32_t BEIDOU_B1C_PILOT_SECONDARY_W[63] = {
269,
1448,
1028,
1324,
822,
5,
155,
458,
310,
959,
1238,
1180,
1288,
334,
885,
1362,
181,
1648,
838,
313,
750,
225,
1477,
309,
108,
1457,
149,
322,
271,
576,
1103,
450,
399,
241,
1045,
164,
513,
687,
422,
303,
324,
495,
725,
780,
367,
882,
631,
37,
647,
1043,
24,
120,
134,
136,
158,
214,
335,
340,
661,
889,
929,
1002,
1149,
};
constexpr int32_t BEIDOU_B1C_PILOT_SECONDARY_P[63] = {
1889,
1268,
1593,
1186,
1239,
1930,
176,
1696,
26,
1344,
1271,
1182,
1381,
1604,
1333,
1185,
31,
704,
1190,
1646,
1385,
113,
860,
1656,
1921,
1173,
1928,
57,
150,
1214,
1148,
1458,
1519,
1635,
1257,
1687,
1382,
1514,
1,
1583,
1806,
1664,
1338,
1111,
1706,
1543,
1813,
228,
2871,
2884,
1823,
75,
11,
63,
1937,
22,
1768,
1526,
1402,
1445,
1680,
1290,
1245,
};
#endif
+66
View File
@@ -0,0 +1,66 @@
/*!
* \file Beidou_CNAV1.h
* \brief B-CNAV1 navigation message constants (BDS-SIS-ICD-B1C-1.0 §6.2, §7)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_H
#define GNSS_SDR_BEIDOU_CNAV1_H
#include "MATH_CONSTANTS.h"
#include <cstdint>
constexpr int32_t BEIDOU_CNAV1_FRAME_SYMBOLS = 1800;
constexpr int32_t BEIDOU_CNAV1_FRAME_PERIOD_S = 18;
constexpr int32_t BEIDOU_CNAV1_SYMBOL_RATE_SPS = 100;
constexpr int32_t BEIDOU_CNAV1_SUBFRAME1_SYMBOLS = 72;
constexpr int32_t BEIDOU_CNAV1_SUBFRAME2_SYMBOLS = 1200;
constexpr int32_t BEIDOU_CNAV1_SUBFRAME3_SYMBOLS = 528;
constexpr int32_t BEIDOU_CNAV1_SOH_LSB_S = 18;
constexpr int32_t BEIDOU_CNAV1_INTERLEAVED_SYMBOLS = 1728;
constexpr int32_t BEIDOU_CNAV1_INTERLEAVE_ROWS = 36;
constexpr int32_t BEIDOU_CNAV1_INTERLEAVE_COLS = 48;
constexpr int32_t BEIDOU_CNAV1_SF2_DATA_BITS = 600;
constexpr int32_t BEIDOU_CNAV1_SF3_DATA_BITS = 264;
constexpr int32_t BEIDOU_CNAV1_CRC_BITS = 24;
constexpr int32_t BEIDOU_CNAV1_PAGE_IONO_UTC = 1;
//! RTKLIB eph_t::code / Beidou_Cnav1_Ephemeris::sig_type for B-CNAV1 (DNAV uses 0–6).
constexpr int32_t BDS_EPH_SOURCE_CNAV1 = 7;
constexpr double BEIDOU_CNAV1_A_REF_MEO = 27906100.0;
constexpr double BEIDOU_CNAV1_A_REF_IGSO = 42162200.0;
constexpr double BEIDOU_CNAV1_TOE_TOC_LSB = 300.0;
constexpr double BEIDOU_CNAV1_DELTA_A_LSB = TWO_N9;
constexpr double BEIDOU_CNAV1_A_DOT_LSB = TWO_N21; //!< 2^-21 m/s, Table 7-8
constexpr double BEIDOU_CNAV1_DELTA_N0_LSB = TWO_N44 * GNSS_PI;
constexpr double BEIDOU_CNAV1_DELTA_N0_DOT_LSB = TWO_N57 * GNSS_PI;
constexpr double BEIDOU_CNAV1_M0_LSB = TWO_N32 * GNSS_PI;
constexpr double BEIDOU_CNAV1_E_LSB = TWO_N34;
constexpr double BEIDOU_CNAV1_OMEGA_LSB = TWO_N32 * GNSS_PI;
constexpr double BEIDOU_CNAV1_I0_LSB = TWO_N32 * GNSS_PI;
constexpr double BEIDOU_CNAV1_OMEGADOT_LSB = TWO_N44 * GNSS_PI;
constexpr double BEIDOU_CNAV1_IDOT_LSB = TWO_N44 * GNSS_PI;
constexpr double BEIDOU_CNAV1_CIS_LSB = TWO_N30;
constexpr double BEIDOU_CNAV1_CIC_LSB = TWO_N30;
constexpr double BEIDOU_CNAV1_CRS_LSB = TWO_N8;
constexpr double BEIDOU_CNAV1_CRC_LSB = TWO_N8;
constexpr double BEIDOU_CNAV1_CUS_LSB = TWO_N30;
constexpr double BEIDOU_CNAV1_CUC_LSB = TWO_N30;
constexpr double BEIDOU_CNAV1_AF0_LSB = TWO_N34;
constexpr double BEIDOU_CNAV1_AF1_LSB = TWO_N50;
constexpr double BEIDOU_CNAV1_AF2_LSB = TWO_N66;
constexpr double BEIDOU_CNAV1_TGD_LSB = TWO_N34;
constexpr double BEIDOU_CNAV1_ISC_LSB = TWO_N34;
#endif // GNSS_SDR_BEIDOU_CNAV1_H
+15
View File
@@ -24,6 +24,11 @@ set(SYSTEM_PARAMETERS_SOURCES
galileo_reduced_ced.cc
beidou_dnav_navigation_message.cc
beidou_dnav_ephemeris.cc
beidou_cnav1_ephemeris.cc
beidou_cnav1_navigation_message.cc
beidou_cnav1_ldpc.cc
beidou_bdgim.cc
Beidou_B1C_codes.cc
sbas_ephemeris.cc
gps_cnav_navigation_message.cc
glonass_gnav_ephemeris.cc
@@ -92,6 +97,16 @@ set(SYSTEM_PARAMETERS_HEADERS
GPS_L2C.h
GPS_L5.h
Beidou_B1I.h
Beidou_B1C.h
Beidou_B1C_codes.h
Beidou_B1C_prn.h
Beidou_CNAV1.h
beidou_cnav1_ldpc.h
beidou_cnav1_ephemeris.h
beidou_cnav1_navigation_message.h
beidou_cnav1_iono.h
beidou_cnav1_utc_model.h
beidou_bdgim.h
Beidou_B3I.h
Beidou_DNAV.h
MATH_CONSTANTS.h
+234
View File
@@ -0,0 +1,234 @@
/*!
* \file beidou_bdgim.cc
* \brief BeiDou Global Ionospheric delay Model (BDGIM), BDS-SIS-ICD-B1C-1.0 §7.8
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_bdgim.h"
#include "MATH_CONSTANTS.h"
#include <cmath>
namespace
{
constexpr double K_BDGIM_HION_M = 400000.0;
constexpr double K_BDGIM_RE_M = 6378000.0;
constexpr double K_BDGIM_LAT_M_RAD = 80.27 * GNSS_PI / 180.0;
constexpr double K_BDGIM_LON_M_RAD = -72.58 * GNSS_PI / 180.0;
constexpr double K_BDGIM_TEC_FACTOR = 40.28e16;
/* Table 7-11: (n,m) for broadcast Ai, i=1..9 */
const int K_AI_N[9] = {0, 1, 1, 1, 2, 2, 2, 2, 2};
const int K_AI_M[9] = {0, 0, 1, -1, 0, 1, -1, 2, -2};
/* Table 7-12: (n,m) for non-broadcast Bj, j=1..17 */
const int K_BJ_N[17] = {3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5};
const int K_BJ_M[17] = {0, 1, -1, 2, -2, 3, -3, 0, 1, -1, 2, -2, 0, 1, -1, 2, -2};
/* Periods Tk [days] for k=1..12 (k=0 is a0 constant) */
const double K_PERIOD_DAYS[13] = {
0.0, 1.0, 0.5, 0.33, 14.6, 27.0, 121.6, 182.51, 365.25, 4028.71, 2014.35, 1342.90, 1007.18};
/* a0,j for j=1..17 */
const double K_A0[17] = {
-0.61, -1.31, -2.00, -0.03, 0.15, -0.48, -0.40, 2.28, -0.16, -0.21, -0.10, -0.13, 0.21, 0.68, 1.06, 0.0, -0.12};
/* ak,j and bk,j for k=1..12, j=1..17 (row-major: [k-1][j-1]) */
const double K_AK[12][17] = {
{-0.51, -0.43, 0.34, -0.01, 0.17, 0.02, -0.06, 0.30, 0.44, -0.28, -0.31, -0.17, 0.04, 0.39, -0.12, 0.12, 0.0},
{-0.06, -0.05, 0.06, 0.17, 0.15, 0.0, 0.11, -0.05, -0.16, 0.02, 0.11, 0.04, 0.12, 0.07, 0.02, -0.14, -0.14},
{0.01, -0.03, 0.01, -0.01, 0.05, -0.03, 0.05, -0.03, -0.01, 0.0, -0.08, -0.04, 0.0, -0.02, -0.03, 0.0, -0.03},
{-0.01, 0.0, 0.01, 0.0, 0.01, 0.0, -0.01, -0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{0.0, 0.0, 0.03, 0.01, 0.02, 0.01, 0.0, -0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{-0.19, -0.02, 0.12, -0.10, 0.06, 0.0, -0.02, -0.08, -0.02, -0.07, 0.01, 0.03, 0.15, 0.06, -0.05, -0.03, -0.10},
{-0.18, 0.06, -0.55, -0.02, 0.09, -0.08, 0.0, 0.86, -0.18, -0.05, -0.07, 0.04, 0.14, -0.03, 0.37, -0.11, -0.12},
{1.09, -0.14, -0.21, 0.52, 0.27, 0.0, 0.11, 0.17, 0.23, 0.35, -0.05, 0.02, -0.60, 0.02, 0.01, 0.27, 0.32},
{-0.34, -0.09, -1.22, 0.05, 0.15, -0.29, -0.17, 1.58, -0.06, -0.15, 0.0, 0.13, 0.28, -0.08, 0.62, -0.01, -0.04},
{-0.13, 0.07, -0.37, 0.05, 0.06, -0.11, -0.07, 0.46, 0.0, -0.04, 0.01, 0.07, 0.09, -0.05, 0.15, -0.01, 0.01},
{-0.06, 0.13, -0.07, 0.03, 0.02, -0.05, -0.05, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{-0.03, 0.08, -0.01, 0.04, 0.01, -0.02, -0.02, -0.04, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
const double K_BK[12][17] = {
{0.23, -0.20, -0.31, 0.16, -0.03, 0.02, 0.04, 0.18, 0.34, 0.45, 0.19, -0.25, -0.12, 0.18, 0.40, -0.09, 0.21},
{0.02, -0.08, -0.06, -0.11, 0.15, -0.14, 0.01, 0.01, 0.04, -0.14, -0.05, 0.08, 0.08, -0.01, 0.01, 0.11, -0.12},
{0.0, -0.02, -0.03, -0.05, -0.01, -0.07, -0.03, -0.01, 0.02, -0.01, 0.03, -0.10, 0.01, 0.05, -0.01, 0.04, 0.0},
{0.0, -0.02, 0.01, 0.0, -0.01, 0.01, 0.0, -0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{0.01, 0.0, 0.0, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{-0.09, 0.07, 0.03, 0.06, 0.09, 0.01, 0.02, 0.0, -0.04, -0.02, -0.01, 0.01, -0.10, 0.0, -0.01, 0.02, 0.05},
{0.15, -0.31, 0.13, 0.05, -0.09, -0.03, 0.06, -0.36, 0.08, 0.05, 0.06, -0.02, -0.05, 0.06, -0.20, 0.04, 0.07},
{0.50, -0.08, -0.38, 0.36, 0.14, 0.04, 0.0, 0.25, 0.17, 0.27, -0.03, -0.03, -0.32, -0.10, 0.20, 0.10, 0.30},
{0.0, -0.11, -0.22, 0.01, 0.02, -0.03, -0.01, 0.49, -0.03, -0.02, 0.01, 0.02, 0.04, -0.04, 0.16, -0.02, -0.01},
{0.05, 0.03, 0.07, 0.02, -0.01, 0.03, 0.02, -0.04, -0.01, -0.01, 0.02, 0.03, 0.02, -0.04, -0.04, -0.01, 0.0},
{0.03, -0.02, 0.04, -0.01, -0.03, 0.02, 0.01, 0.04, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{0.04, -0.02, -0.04, 0.0, -0.01, 0.0, 0.01, 0.07, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
double factorial_d(int n)
{
double f = 1.0;
for (int i = 2; i <= n; i++)
{
f *= static_cast<double>(i);
}
return f;
}
double double_factorial_odd(int n)
{
/* (2n-1)!! */
double f = 1.0;
for (int k = 1; k <= n; k++)
{
f *= static_cast<double>(2 * k - 1);
}
return f;
}
double norm_factor(int n, int m)
{
const int am = (m < 0) ? -m : m;
const double delta = (am == 0) ? 1.0 : 0.0;
const double num = factorial_d(n - am) * (2.0 * static_cast<double>(n) + 1.0) * (2.0 - delta);
const double den = factorial_d(n + am);
return std::sqrt(num / den);
}
/* Associated Legendre P_n,m(x) for m>=0 via recurrence (ICD 7-13) */
double legendre_p(int n, int m, double x)
{
if (n < 0 || m < 0 || m > n)
{
return 0.0;
}
if (n == 0 && m == 0)
{
return 1.0;
}
if (n == m)
{
const double s = 1.0 - x * x;
if (s < 0.0)
{
return 0.0;
}
return double_factorial_odd(n) * std::pow(s, 0.5 * static_cast<double>(n));
}
if (n == m + 1)
{
return x * (2.0 * static_cast<double>(m) + 1.0) * legendre_p(m, m, x);
}
return ((2.0 * static_cast<double>(n) - 1.0) * x * legendre_p(n - 1, m, x) -
(static_cast<double>(n + m) - 1.0) * legendre_p(n - 2, m, x)) /
static_cast<double>(n - m);
}
double spherical_harmonic(int n, int m, double phi_p, double lam_p)
{
const int am = (m < 0) ? -m : m;
const double pnm = norm_factor(n, am) * legendre_p(n, am, std::sin(phi_p));
if (m >= 0)
{
return pnm * std::cos(static_cast<double>(m) * lam_p);
}
return pnm * std::sin(static_cast<double>(-m) * lam_p);
}
double mapping_function(double el_rad)
{
const double sin_arg = (K_BDGIM_RE_M / (K_BDGIM_RE_M + K_BDGIM_HION_M)) * std::cos(el_rad);
if (sin_arg <= -1.0 || sin_arg >= 1.0)
{
return 1.0;
}
return 1.0 / std::sqrt(1.0 - sin_arg * sin_arg);
}
} // namespace
double beidou_bdgim_nearest_odd_hour_mjd(double time_mjd)
{
/* ICD §7.8.2: t_p ∈ {01:00, 03:00, …, 23:00} nearest the epoch [MJD days]. */
const double hours = time_mjd * 24.0;
const double odd_hour = 2.0 * std::round((hours - 1.0) / 2.0) + 1.0;
return odd_hour / 24.0;
}
double beidou_bdgim_delay_m(
double time_mjd,
double lat_rad,
double lon_rad,
double az_rad,
double el_rad,
const double alpha[9],
double freq_hz)
{
if (el_rad <= 0.0 || freq_hz <= 0.0)
{
return 0.0;
}
/* IPP earth-centered angle (7-7) */
const double sin_arg = (K_BDGIM_RE_M / (K_BDGIM_RE_M + K_BDGIM_HION_M)) * std::cos(el_rad);
if (sin_arg <= -1.0 || sin_arg >= 1.0)
{
return 0.0;
}
const double psi = GNSS_PI / 2.0 - el_rad - std::asin(sin_arg);
/* Geographic lat/lon of IPP (7-8) */
const double sin_phi_g = std::sin(lat_rad) * std::cos(psi) +
std::cos(lat_rad) * std::sin(psi) * std::cos(az_rad);
double phi_g = std::asin(sin_phi_g);
const double lam_g = lon_rad + std::atan2(
std::sin(psi) * std::sin(az_rad) * std::cos(lat_rad),
std::cos(psi) - std::sin(lat_rad) * std::sin(phi_g));
/* Geomagnetic lat/lon (7-9) */
const double sin_phi_m = std::sin(K_BDGIM_LAT_M_RAD) * std::sin(phi_g) +
std::cos(K_BDGIM_LAT_M_RAD) * std::cos(phi_g) * std::cos(lam_g - K_BDGIM_LON_M_RAD);
double phi_m = std::asin(sin_phi_m);
const double lam_m = std::atan2(
std::cos(phi_g) * std::sin(lam_g - K_BDGIM_LON_M_RAD) * std::cos(K_BDGIM_LAT_M_RAD),
std::sin(K_BDGIM_LAT_M_RAD) * std::sin(phi_m) - std::sin(phi_g));
/* Sun-fixed geomagnetic (7-10) */
const double frac_day = time_mjd - std::floor(time_mjd);
const double slon = GNSS_PI * (1.0 - 2.0 * frac_day);
const double phi_p = phi_m;
const double lam_p = lam_m - std::atan2(
std::sin(slon - K_BDGIM_LON_M_RAD),
std::sin(K_BDGIM_LAT_M_RAD) * std::cos(slon - K_BDGIM_LON_M_RAD));
/* Ai from broadcast alphas (7-11) */
double sum_alpha_a = 0.0;
for (int i = 0; i < 9; i++)
{
sum_alpha_a += alpha[i] * spherical_harmonic(K_AI_N[i], K_AI_M[i], phi_p, lam_p);
}
/* A0 from non-broadcast betas (7-14)(7-15); t_p is quantized, Slon is not. */
double a0 = 0.0;
const double tp = beidou_bdgim_nearest_odd_hour_mjd(time_mjd);
for (int j = 0; j < 17; j++)
{
double beta = K_A0[j];
for (int k = 1; k <= 12; k++)
{
const double omega = 2.0 * GNSS_PI / K_PERIOD_DAYS[k];
beta += K_AK[k - 1][j] * std::cos(omega * tp) + K_BK[k - 1][j] * std::sin(omega * tp);
}
a0 += beta * spherical_harmonic(K_BJ_N[j], K_BJ_M[j], phi_p, lam_p);
}
const double vtec = a0 + sum_alpha_a; /* TECu */
const double mf = mapping_function(el_rad);
return mf * (K_BDGIM_TEC_FACTOR / (freq_hz * freq_hz)) * vtec;
}
+53
View File
@@ -0,0 +1,53 @@
/*!
* \file beidou_bdgim.h
* \brief BeiDou Global Ionospheric delay Model (BDGIM), BDS-SIS-ICD-B1C-1.0 §7.8
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_BDGIM_H
#define GNSS_SDR_BEIDOU_BDGIM_H
#include <cstdint>
/** \addtogroup Core
* \{ */
/** \addtogroup System_Parameters
* \{ */
/*!
* \brief Compute BDGIM slant ionospheric delay [m] at carrier frequency freq_hz.
* \param time_mjd Modified Julian Date (days) of the computation epoch
* \param lat_rad User geodetic latitude [rad]
* \param lon_rad User geodetic longitude [rad]
* \param az_rad Satellite azimuth [rad]
* \param el_rad Satellite elevation [rad]
* \param alpha Broadcast BDGIM parameters alpha1..alpha9 [TECu]
* \param freq_hz Carrier frequency [Hz]
* \return Slant ionospheric delay [m], or 0 if inputs are invalid
*/
double beidou_bdgim_delay_m(
double time_mjd,
double lat_rad,
double lon_rad,
double az_rad,
double el_rad,
const double alpha[9],
double freq_hz);
/*!
* \brief ICD §7.8.2: odd hour of day (01:00 … 23:00) nearest to time_mjd [MJD days].
*/
double beidou_bdgim_nearest_odd_hour_mjd(double time_mjd);
/** \} */
/** \} */
#endif // GNSS_SDR_BEIDOU_BDGIM_H
@@ -0,0 +1,16 @@
/*!
* \file beidou_cnav1_ephemeris.cc
* \brief BeiDou B-CNAV1 ephemeris storage (BDS-SIS-ICD-B1C-1.0 §7)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_cnav1_ephemeris.h"
@@ -0,0 +1,84 @@
/*!
* \file beidou_cnav1_ephemeris.h
* \brief BeiDou B-CNAV1 ephemeris storage (BDS-SIS-ICD-B1C-1.0 §7)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_EPHEMERIS_H
#define GNSS_SDR_BEIDOU_CNAV1_EPHEMERIS_H
#include "Beidou_CNAV1.h"
#include "gnss_ephemeris.h"
#include <boost/serialization/nvp.hpp>
class Beidou_Cnav1_Ephemeris : public Gnss_Ephemeris
{
public:
Beidou_Cnav1_Ephemeris()
{
this->System = 'C';
}
double TGD_B1Cp{}; //!< §7.6 B1C pilot group delay
double TGD_B2ap{}; //!< §7.6 B2a pilot group delay
double ISC_B1Cd{}; //!< §7.6 B1C data-to-pilot intra-frequency delay correction
double IODC{}; //!< §7.4.2
double IODE{}; //!< §7.4.1
int32_t hs{}; //!< SF3 satellite health (2 bits, ICD §6.2.3)
int32_t sig_type{BDS_EPH_SOURCE_CNAV1}; //!< RTKLIB eph.code for B-CNAV1
int32_t nav_type{1}; //!< 0: GEO, 1: MEO/IGSO
int32_t sat_type{}; //!< SF2 SatType (2 bits, ICD §7.5): 1=GEO, 2=IGSO, 3=MEO. Same encoding as RINEX 4 CNV1
template <class Archive>
void serialize(Archive& archive, const unsigned int version)
{
if (version)
{
};
archive& BOOST_SERIALIZATION_NVP(PRN);
archive& BOOST_SERIALIZATION_NVP(M_0);
archive& BOOST_SERIALIZATION_NVP(delta_n);
archive& BOOST_SERIALIZATION_NVP(delta_ndot);
archive& BOOST_SERIALIZATION_NVP(ecc);
archive& BOOST_SERIALIZATION_NVP(A0);
archive& BOOST_SERIALIZATION_NVP(Adot); // Gnss_Ephemeris::Adot
archive& BOOST_SERIALIZATION_NVP(OMEGA_0);
archive& BOOST_SERIALIZATION_NVP(i_0);
archive& BOOST_SERIALIZATION_NVP(omega);
archive& BOOST_SERIALIZATION_NVP(OMEGAdot);
archive& BOOST_SERIALIZATION_NVP(idot);
archive& BOOST_SERIALIZATION_NVP(Cuc);
archive& BOOST_SERIALIZATION_NVP(Cus);
archive& BOOST_SERIALIZATION_NVP(Crc);
archive& BOOST_SERIALIZATION_NVP(Crs);
archive& BOOST_SERIALIZATION_NVP(Cic);
archive& BOOST_SERIALIZATION_NVP(Cis);
archive& BOOST_SERIALIZATION_NVP(toe);
archive& BOOST_SERIALIZATION_NVP(toc);
archive& BOOST_SERIALIZATION_NVP(af0);
archive& BOOST_SERIALIZATION_NVP(af1);
archive& BOOST_SERIALIZATION_NVP(af2);
archive& BOOST_SERIALIZATION_NVP(WN);
archive& BOOST_SERIALIZATION_NVP(tow);
archive& BOOST_SERIALIZATION_NVP(TGD_B1Cp);
archive& BOOST_SERIALIZATION_NVP(TGD_B2ap);
archive& BOOST_SERIALIZATION_NVP(ISC_B1Cd);
archive& BOOST_SERIALIZATION_NVP(IODC);
archive& BOOST_SERIALIZATION_NVP(IODE);
archive& BOOST_SERIALIZATION_NVP(hs);
archive& BOOST_SERIALIZATION_NVP(sig_type);
archive& BOOST_SERIALIZATION_NVP(nav_type);
archive& BOOST_SERIALIZATION_NVP(sat_type);
}
};
#endif // GNSS_SDR_BEIDOU_CNAV1_EPHEMERIS_H
@@ -0,0 +1,34 @@
/*!
* \file beidou_cnav1_iono.h
* \brief BeiDou B-CNAV1 ionospheric model (§7.8)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_IONO_H
#define GNSS_SDR_BEIDOU_CNAV1_IONO_H
class Beidou_Cnav1_Iono
{
public:
double alpha1{};
double alpha2{};
double alpha3{};
double alpha4{};
double alpha5{};
double alpha6{};
double alpha7{};
double alpha8{};
double alpha9{};
bool valid{};
};
#endif
@@ -0,0 +1,713 @@
/*!
* \file beidou_cnav1_ldpc.cc
* \brief B-CNAV1 LDPC graph and non-binary BP decoder over GF(64)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_cnav1_ldpc.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
namespace
{
constexpr int32_t BITS_PER_SYMBOL = 6;
constexpr int32_t Q = 64;
constexpr int32_t NM = BEIDOU_CNAV1_LDPC_NM;
constexpr int32_t FP_CANDIDATES = 8 + 2 * NM;
constexpr float LLR_FALLBACK_GAP = 25.0F;
struct TruncMessage
{
std::array<uint8_t, NM> sym{};
std::array<float, NM> llr{};
};
struct FixedPathCandidate
{
FixedPathCandidate() = default;
FixedPathCandidate(uint8_t sym_, float llr_) : sym(sym_), llr(llr_) {}
uint8_t sym = 0;
float llr = std::numeric_limits<float>::max();
};
uint8_t bits_to_symbol_msb_first(const float* bit_llr, int32_t bit_offset)
{
uint8_t symbol = 0;
for (int32_t bit = 0; bit < BITS_PER_SYMBOL; bit++)
{
symbol = static_cast<uint8_t>((symbol << 1U) | (bit_llr[bit_offset + bit] >= 0.0F ? 1U : 0U));
}
return symbol;
}
void normalize_message(TruncMessage& msg)
{
const float min_llr = msg.llr[0];
for (int32_t i = 0; i < NM; i++)
{
msg.llr[i] -= min_llr;
}
}
float lookup_message_llr_var_domain(const TruncMessage& c2v_msg, uint8_t h_inv, uint8_t x_var)
{
for (int32_t i = 0; i < NM; i++)
{
const uint8_t mapped = GaloisField64::mul(h_inv, c2v_msg.sym[i]);
if (mapped == x_var)
{
return c2v_msg.llr[i];
}
}
return c2v_msg.llr[NM - 1] + LLR_FALLBACK_GAP;
}
void truncate_metric_vector(const std::array<float, Q>& metric, TruncMessage& out)
{
std::array<bool, Q> used{};
for (int32_t kept = 0; kept < NM; kept++)
{
float best_llr = std::numeric_limits<float>::max();
int32_t best_sym = 0;
for (int32_t x = 0; x < Q; x++)
{
if (!used[static_cast<size_t>(x)] && metric[static_cast<size_t>(x)] < best_llr)
{
best_llr = metric[static_cast<size_t>(x)];
best_sym = x;
}
}
used[static_cast<size_t>(best_sym)] = true;
out.sym[kept] = static_cast<uint8_t>(best_sym);
out.llr[kept] = best_llr;
}
normalize_message(out);
}
uint8_t row_syndrome(const BeidouCnav1LdpcGraph& graph, int32_t check_index, const std::vector<uint8_t>& codeword)
{
uint8_t syndrome = GaloisField64::kZero;
const uint32_t begin = graph.check_offsets[static_cast<size_t>(check_index)];
const uint32_t end = graph.check_offsets[static_cast<size_t>(check_index + 1)];
for (uint32_t edge = begin; edge < end; edge++)
{
const uint16_t var = graph.check_to_var[edge];
const uint8_t h_ij = graph.check_to_h[edge];
syndrome = GaloisField64::add(syndrome, GaloisField64::mul(h_ij, codeword[var]));
}
return syndrome;
}
bool syndrome_is_zero(const BeidouCnav1LdpcGraph& graph, const std::vector<uint8_t>& codeword)
{
for (int32_t row = 0; row < graph.num_checks; row++)
{
if (row_syndrome(graph, row, codeword) != GaloisField64::kZero)
{
return false;
}
}
return true;
}
void symbols_to_bits(const std::vector<uint8_t>& symbols, int32_t num_info_symbols, uint8_t* info_bits)
{
for (int32_t symbol_index = 0; symbol_index < num_info_symbols; symbol_index++)
{
const uint8_t symbol = symbols[static_cast<size_t>(symbol_index)];
for (int32_t bit = 0; bit < BITS_PER_SYMBOL; bit++)
{
info_bits[symbol_index * BITS_PER_SYMBOL + bit] = static_cast<uint8_t>((symbol >> (5 - bit)) & 1U);
}
}
}
bool graph_is_valid(const BeidouCnav1LdpcGraph& graph)
{
if (graph.num_checks <= 0 || graph.num_variables <= 0 || graph.row_weight <= 0)
{
return false;
}
const int32_t expected_edges = graph.num_checks * graph.row_weight;
return graph.check_offsets.size() == static_cast<size_t>(graph.num_checks + 1) &&
graph.var_offsets.size() == static_cast<size_t>(graph.num_variables + 1) &&
graph.check_to_var.size() == static_cast<size_t>(expected_edges) &&
graph.check_to_h.size() == static_cast<size_t>(expected_edges) &&
graph.var_to_check.size() == static_cast<size_t>(expected_edges) &&
graph.var_to_edge.size() == static_cast<size_t>(expected_edges) &&
graph.var_to_h.size() == static_cast<size_t>(expected_edges) &&
graph.var_to_h_inv.size() == static_cast<size_t>(expected_edges);
}
void initialize_channel_llr(
const float* bit_llr,
int32_t num_symbols,
std::vector<uint8_t>& hard_symbol,
std::vector<float>& channel_llr)
{
hard_symbol.assign(static_cast<size_t>(num_symbols), 0U);
channel_llr.assign(static_cast<size_t>(num_symbols) * Q, 0.0F);
for (int32_t j = 0; j < num_symbols; j++)
{
const int32_t bit_offset = j * BITS_PER_SYMBOL;
const uint8_t hard = bits_to_symbol_msb_first(bit_llr, bit_offset);
hard_symbol[static_cast<size_t>(j)] = hard;
std::array<float, BITS_PER_SYMBOL> abs_bit_llr{};
for (int32_t b = 0; b < BITS_PER_SYMBOL; b++)
{
abs_bit_llr[b] = std::fabs(bit_llr[bit_offset + b]);
}
for (int32_t x = 0; x < Q; x++)
{
float metric = 0.0F;
const auto diff = static_cast<uint8_t>(x ^ hard);
for (int32_t b = 0; b < BITS_PER_SYMBOL; b++)
{
const auto bit_mask = static_cast<uint8_t>(1U << (BITS_PER_SYMBOL - 1 - b));
if ((diff & bit_mask) != 0U)
{
metric += abs_bit_llr[b];
}
}
channel_llr[static_cast<size_t>(j) * Q + static_cast<size_t>(x)] = metric;
}
}
}
void initialize_v2c_messages(
const BeidouCnav1LdpcGraph& graph,
const std::vector<float>& channel_llr,
std::vector<TruncMessage>& v2c)
{
const int32_t edge_count = graph.num_checks * graph.row_weight;
for (int32_t edge = 0; edge < edge_count; edge++)
{
const uint16_t var = graph.check_to_var[static_cast<size_t>(edge)];
const uint8_t h_ij = graph.check_to_h[static_cast<size_t>(edge)];
std::array<float, Q> mapped_metric{};
for (int32_t x = 0; x < Q; x++)
{
const uint8_t y = GaloisField64::mul(h_ij, static_cast<uint8_t>(x));
mapped_metric[static_cast<size_t>(y)] =
channel_llr[static_cast<size_t>(var) * Q + static_cast<size_t>(x)];
}
truncate_metric_vector(mapped_metric, v2c[static_cast<size_t>(edge)]);
}
}
void variable_node_update(
const BeidouCnav1LdpcGraph& graph,
const std::vector<float>& channel_llr,
std::vector<TruncMessage>& v2c,
const std::vector<TruncMessage>& c2v,
std::vector<uint8_t>& hard_codeword)
{
std::array<float, Q> posterior{};
std::array<float, Q> outgoing_metric{};
for (int32_t var = 0; var < graph.num_variables; var++)
{
const uint32_t begin = graph.var_offsets[static_cast<size_t>(var)];
const uint32_t end = graph.var_offsets[static_cast<size_t>(var + 1)];
for (int32_t x = 0; x < Q; x++)
{
float metric = channel_llr[static_cast<size_t>(var) * Q + static_cast<size_t>(x)];
for (uint32_t p = begin; p < end; p++)
{
const uint32_t edge = graph.var_to_edge[p];
const uint8_t h_fj_inv = graph.var_to_h_inv[p];
metric += lookup_message_llr_var_domain(c2v[edge], h_fj_inv, static_cast<uint8_t>(x));
}
posterior[static_cast<size_t>(x)] = metric;
}
int32_t hard_symbol = 0;
float best_hard_llr = posterior[0];
for (int32_t x = 1; x < Q; x++)
{
if (posterior[static_cast<size_t>(x)] < best_hard_llr)
{
best_hard_llr = posterior[static_cast<size_t>(x)];
hard_symbol = x;
}
}
hard_codeword[static_cast<size_t>(var)] = static_cast<uint8_t>(hard_symbol);
for (uint32_t p = begin; p < end; p++)
{
const uint32_t edge_out = graph.var_to_edge[p];
const uint8_t h_ij = graph.var_to_h[p];
for (int32_t y = 0; y < Q; y++)
{
outgoing_metric[static_cast<size_t>(y)] = std::numeric_limits<float>::max();
}
for (int32_t x = 0; x < Q; x++)
{
float metric = channel_llr[static_cast<size_t>(var) * Q + static_cast<size_t>(x)];
for (uint32_t q = begin; q < end; q++)
{
const uint32_t edge_in = graph.var_to_edge[q];
if (edge_in == edge_out)
{
continue;
}
const uint8_t h_fj_inv = graph.var_to_h_inv[q];
metric += lookup_message_llr_var_domain(c2v[edge_in], h_fj_inv, static_cast<uint8_t>(x));
}
const uint8_t y_out = GaloisField64::mul(h_ij, static_cast<uint8_t>(x));
outgoing_metric[static_cast<size_t>(y_out)] = metric;
}
truncate_metric_vector(outgoing_metric, v2c[edge_out]);
}
}
}
void build_fixed_path_candidates(
const std::array<TruncMessage, 4>& in,
std::array<FixedPathCandidate, FP_CANDIDATES>& cand)
{
for (auto& c : cand)
{
c.sym = 0U;
c.llr = std::numeric_limits<float>::max();
}
const uint8_t s0 = in[0].sym[0];
const uint8_t s1 = in[1].sym[0];
const uint8_t s2 = in[2].sym[0];
const uint8_t s3 = in[3].sym[0];
const float r0 = in[0].llr[0];
const float r1 = in[1].llr[0];
const float r2 = in[2].llr[0];
const float r3 = in[3].llr[0];
const uint8_t base_sym = GaloisField64::add(GaloisField64::add(s0, s1), GaloisField64::add(s2, s3));
const float base_llr = r0 + r1 + r2 + r3;
int32_t idx = 0;
cand[idx++] = {base_sym, base_llr};
for (int32_t l = 0; l < 4; l++)
{
const uint8_t sym = GaloisField64::add(base_sym, GaloisField64::add(in[l].sym[0], in[l].sym[1]));
const float llr = base_llr - in[l].llr[0] + in[l].llr[1];
cand[idx++] = {sym, llr};
}
static const std::array<std::pair<int32_t, int32_t>, 6> pairs = {
std::pair<int32_t, int32_t>{0, 1},
{0, 2},
{0, 3},
{1, 2},
{1, 3},
{2, 3},
};
for (const auto& pair : pairs)
{
const int32_t a = pair.first;
const int32_t b = pair.second;
const uint8_t sym = GaloisField64::add(
base_sym,
GaloisField64::add(
GaloisField64::add(in[a].sym[0], in[a].sym[1]),
GaloisField64::add(in[b].sym[0], in[b].sym[1])));
const float llr = base_llr - in[a].llr[0] - in[b].llr[0] + in[a].llr[1] + in[b].llr[1];
cand[idx++] = {sym, llr};
}
for (int32_t rank = 2; rank < NM && idx < FP_CANDIDATES; rank++)
{
for (int32_t l = 0; l < 4 && idx < FP_CANDIDATES; l++)
{
const uint8_t sym = GaloisField64::add(base_sym, GaloisField64::add(in[l].sym[0], in[l].sym[rank]));
const float llr = base_llr - in[l].llr[0] + in[l].llr[rank];
cand[idx++] = {sym, llr};
}
}
}
void fixed_path_check_update(
const BeidouCnav1LdpcGraph& graph,
const std::vector<TruncMessage>& v2c,
std::vector<TruncMessage>& c2v)
{
std::array<int32_t, 4> edge_idx{};
std::array<int32_t, 4> order{};
std::array<TruncMessage, 4> in_sorted{};
std::array<FixedPathCandidate, FP_CANDIDATES> candidates{};
std::array<uint8_t, FP_CANDIDATES> T{};
std::array<uint8_t, FP_CANDIDATES> Tbar{};
for (int32_t check = 0; check < graph.num_checks; check++)
{
const uint32_t begin = graph.check_offsets[static_cast<size_t>(check)];
const uint32_t end = graph.check_offsets[static_cast<size_t>(check + 1)];
if (static_cast<int32_t>(end - begin) != 4)
{
continue;
}
for (int32_t l = 0; l < 4; l++)
{
edge_idx[l] = static_cast<int32_t>(begin) + l;
order[l] = l;
}
std::sort(order.begin(), order.end(), [&](int32_t a, int32_t b) {
return v2c[static_cast<size_t>(edge_idx[a])].llr[1] < v2c[static_cast<size_t>(edge_idx[b])].llr[1];
});
for (int32_t l = 0; l < 4; l++)
{
in_sorted[l] = v2c[static_cast<size_t>(edge_idx[order[l]])];
}
build_fixed_path_candidates(in_sorted, candidates);
int32_t theta = 0;
int32_t beta = 1;
float min2 = in_sorted[0].llr[1];
float second2 = in_sorted[1].llr[1];
if (second2 < min2)
{
std::swap(min2, second2);
std::swap(theta, beta);
}
for (int32_t l = 2; l < 4; l++)
{
const float v = in_sorted[l].llr[1];
if (v < min2)
{
second2 = min2;
beta = theta;
min2 = v;
theta = l;
}
else if (v < second2)
{
second2 = v;
beta = l;
}
}
const int32_t ref_idx = NM / 2;
const float theta_ref = in_sorted[theta].llr[ref_idx];
const float beta_ref = in_sorted[beta].llr[ref_idx];
for (int32_t k = 0; k < FP_CANDIDATES; k++)
{
T[k] = (candidates[k].llr <= theta_ref) ? 1U : 0U;
Tbar[k] = (candidates[k].llr <= beta_ref) ? 1U : 0U;
}
for (int32_t l = 0; l < 4; l++)
{
TruncMessage out{};
int32_t filled = 0;
for (int32_t k = 0; k < FP_CANDIDATES && filled < NM; k++)
{
bool pass_gate = false;
if (l == 0)
{
pass_gate = (T[k] != 0U);
}
else if (l == theta)
{
pass_gate = (Tbar[k] != 0U);
}
else
{
pass_gate = (T[k] != 0U) || (Tbar[k] != 0U);
}
if (!pass_gate)
{
continue;
}
out.sym[filled] = GaloisField64::add(in_sorted[l].sym[0], candidates[k].sym);
out.llr[filled] = candidates[k].llr - in_sorted[l].llr[0];
filled++;
}
while (filled < NM)
{
out.sym[filled] = out.sym[filled - 1];
out.llr[filled] = out.llr[filled - 1] + 1.0F;
filled++;
}
normalize_message(out);
c2v[static_cast<size_t>(edge_idx[order[l]])] = out;
}
}
}
void reorder_icd_column_bands_to_check_rows(
int32_t num_checks,
int32_t row_weight,
int32_t num_entries,
const uint16_t* icd_index,
const uint8_t* icd_element,
std::vector<uint16_t>& reordered_index,
std::vector<uint8_t>& reordered_element)
{
// ICD matrix is stored in 4-column bands: read each band top-to-bottom, then next band.
const int32_t base_rows = num_checks / row_weight;
const int32_t cols_per_base_row = row_weight * row_weight;
reordered_index.assign(static_cast<size_t>(num_entries), 0U);
reordered_element.assign(static_cast<size_t>(num_entries), 0U);
for (int32_t band = 0; band < row_weight; band++)
{
for (int32_t row = 0; row < base_rows; row++)
{
const int32_t src_offset = row * cols_per_base_row + band * row_weight;
const int32_t check_row = band * base_rows + row;
const int32_t dst_offset = check_row * row_weight;
for (int32_t k = 0; k < row_weight; k++)
{
reordered_index[static_cast<size_t>(dst_offset + k)] =
icd_index[static_cast<size_t>(src_offset + k)];
reordered_element[static_cast<size_t>(dst_offset + k)] =
icd_element[static_cast<size_t>(src_offset + k)];
}
}
}
}
bool decode_block(
const BeidouCnav1LdpcGraph& graph,
const float* bit_llr,
int32_t num_bits,
uint8_t* info_bits,
uint8_t* codeword_bits)
{
if (!graph_is_valid(graph))
{
return false;
}
const int32_t n = graph.num_variables;
const int32_t k = n - graph.num_checks;
if (num_bits < n * BITS_PER_SYMBOL)
{
return false;
}
std::vector<uint8_t> hard_codeword(static_cast<size_t>(n), 0U);
std::vector<uint8_t> hard_symbol;
std::vector<float> channel_llr;
initialize_channel_llr(bit_llr, n, hard_symbol, channel_llr);
hard_codeword = hard_symbol;
const int32_t edge_count = graph.num_checks * graph.row_weight;
std::vector<TruncMessage> v2c(static_cast<size_t>(edge_count));
std::vector<TruncMessage> c2v(static_cast<size_t>(edge_count));
initialize_v2c_messages(graph, channel_llr, v2c);
c2v = v2c;
for (int32_t iteration = 0; iteration < BEIDOU_CNAV1_LDPC_MAX_ITER; iteration++)
{
variable_node_update(graph, channel_llr, v2c, c2v, hard_codeword);
if (syndrome_is_zero(graph, hard_codeword))
{
if (info_bits != nullptr)
{
symbols_to_bits(hard_codeword, k, info_bits);
}
if (codeword_bits != nullptr)
{
symbols_to_bits(hard_codeword, n, codeword_bits);
}
return true;
}
fixed_path_check_update(graph, v2c, c2v);
}
return false;
}
bool init_graph_impl(
int32_t num_checks,
int32_t num_variables,
int32_t row_weight,
const uint16_t* icd_index,
const uint8_t* icd_element,
int32_t num_entries,
BeidouCnav1LdpcGraph& graph)
{
if (num_checks <= 0 || num_variables <= 0 || row_weight <= 0 || icd_index == nullptr || icd_element == nullptr)
{
return false;
}
if (num_entries != num_checks * row_weight)
{
return false;
}
if (num_checks % row_weight != 0)
{
return false;
}
const int32_t base_rows = num_checks / row_weight;
const int32_t cols_per_base_row = row_weight * row_weight;
if (base_rows * cols_per_base_row != num_entries)
{
return false;
}
std::vector<uint16_t> reordered_index;
std::vector<uint8_t> reordered_element;
reorder_icd_column_bands_to_check_rows(
num_checks,
row_weight,
num_entries,
icd_index,
icd_element,
reordered_index,
reordered_element);
const uint16_t* check_index = reordered_index.data();
const uint8_t* check_element = reordered_element.data();
graph.num_checks = num_checks;
graph.num_variables = num_variables;
graph.row_weight = row_weight;
graph.check_offsets.assign(static_cast<size_t>(num_checks + 1), 0U);
graph.check_to_var.assign(static_cast<size_t>(num_entries), 0U);
graph.check_to_h.assign(static_cast<size_t>(num_entries), 0U);
graph.var_offsets.assign(static_cast<size_t>(num_variables + 1), 0U);
graph.var_to_check.assign(static_cast<size_t>(num_entries), 0U);
graph.var_to_edge.assign(static_cast<size_t>(num_entries), 0U);
graph.var_to_h.assign(static_cast<size_t>(num_entries), 0U);
graph.var_to_h_inv.assign(static_cast<size_t>(num_entries), 0U);
for (int32_t i = 0; i < num_checks; i++)
{
graph.check_offsets[static_cast<size_t>(i + 1)] =
graph.check_offsets[static_cast<size_t>(i)] + static_cast<uint32_t>(row_weight);
}
std::vector<uint32_t> var_degree(static_cast<size_t>(num_variables), 0U);
for (int32_t edge = 0; edge < num_entries; edge++)
{
const uint16_t var = check_index[edge];
const uint8_t h_ij = check_element[edge];
if (var >= static_cast<uint16_t>(num_variables) ||
!GaloisField64::valid_symbol(h_ij) ||
h_ij == GaloisField64::kZero)
{
return false;
}
graph.check_to_var[static_cast<size_t>(edge)] = var;
graph.check_to_h[static_cast<size_t>(edge)] = h_ij;
var_degree[static_cast<size_t>(var)]++;
}
for (int32_t j = 0; j < num_variables; j++)
{
graph.var_offsets[static_cast<size_t>(j + 1)] =
graph.var_offsets[static_cast<size_t>(j)] + var_degree[static_cast<size_t>(j)];
}
std::vector<uint32_t> cursor = graph.var_offsets;
for (int32_t i = 0; i < num_checks; i++)
{
const uint32_t begin = graph.check_offsets[static_cast<size_t>(i)];
const uint32_t end = graph.check_offsets[static_cast<size_t>(i + 1)];
for (uint32_t edge = begin; edge < end; edge++)
{
const uint16_t var = graph.check_to_var[edge];
const uint8_t h_ij = graph.check_to_h[edge];
const uint32_t pos = cursor[static_cast<size_t>(var)]++;
graph.var_to_check[pos] = static_cast<uint16_t>(i);
graph.var_to_edge[pos] = edge;
graph.var_to_h[pos] = h_ij;
graph.var_to_h_inv[pos] = GaloisField64::inv(h_ij);
}
}
return true;
}
} // namespace
bool beidou_cnav1_ldpc_init_graph(
int32_t num_checks,
int32_t num_variables,
int32_t row_weight,
const uint16_t* icd_index,
const uint8_t* icd_element,
int32_t num_entries,
BeidouCnav1LdpcGraph& graph)
{
return init_graph_impl(num_checks, num_variables, row_weight, icd_index, icd_element, num_entries, graph);
}
const BeidouCnav1LdpcGraph& beidou_cnav1_ldpc_graph_200_100()
{
static const BeidouCnav1LdpcGraph graph = []() {
BeidouCnav1LdpcGraph out;
const bool ok = beidou_cnav1_ldpc_init_graph(
BEIDOU_CNAV1_LDPC_M200,
BEIDOU_CNAV1_LDPC_N200,
BEIDOU_CNAV1_LDPC_DC200,
BEIDOU_CNAV1_H100_200_INDEX,
BEIDOU_CNAV1_H100_200_ELEMENT,
BEIDOU_CNAV1_LDPC_M200 * BEIDOU_CNAV1_LDPC_DC200,
out);
if (!ok)
{
return BeidouCnav1LdpcGraph{};
}
return out;
}();
return graph;
}
const BeidouCnav1LdpcGraph& beidou_cnav1_ldpc_graph_88_44()
{
static const BeidouCnav1LdpcGraph graph = []() {
BeidouCnav1LdpcGraph out;
const bool ok = beidou_cnav1_ldpc_init_graph(
BEIDOU_CNAV1_LDPC_M88,
BEIDOU_CNAV1_LDPC_N88,
BEIDOU_CNAV1_LDPC_DC88,
BEIDOU_CNAV1_H44_88_INDEX,
BEIDOU_CNAV1_H44_88_ELEMENT,
BEIDOU_CNAV1_LDPC_M88 * BEIDOU_CNAV1_LDPC_DC88,
out);
if (!ok)
{
return BeidouCnav1LdpcGraph{};
}
return out;
}();
return graph;
}
bool beidou_cnav1_ldpc_decode_200_100(const float* symbol_llr, int32_t num_bits, uint8_t* info_bits600)
{
return decode_block(beidou_cnav1_ldpc_graph_200_100(), symbol_llr, num_bits, info_bits600, nullptr);
}
bool beidou_cnav1_ldpc_decode_88_44(const float* symbol_llr, int32_t num_bits, uint8_t* info_bits264)
{
return decode_block(beidou_cnav1_ldpc_graph_88_44(), symbol_llr, num_bits, info_bits264, nullptr);
}
bool beidou_cnav1_ldpc_decode_200_100_codeword(const float* symbol_llr, int32_t num_bits, uint8_t* codeword_bits1200)
{
return decode_block(beidou_cnav1_ldpc_graph_200_100(), symbol_llr, num_bits, nullptr, codeword_bits1200);
}
bool beidou_cnav1_ldpc_decode_88_44_codeword(const float* symbol_llr, int32_t num_bits, uint8_t* codeword_bits528)
{
return decode_block(beidou_cnav1_ldpc_graph_88_44(), symbol_llr, num_bits, nullptr, codeword_bits528);
}
@@ -0,0 +1,192 @@
/*!
* \file beidou_cnav1_ldpc.h
* \brief B-CNAV1 NB-LDPC decoder (BDS-SIS-ICD-B1C-1.0 §6.2.2, Appendix)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_LDPC_DECODER_H
#define GNSS_SDR_BEIDOU_CNAV1_LDPC_DECODER_H
#include <cstddef>
#include <cstdint>
#include <vector>
constexpr int32_t BEIDOU_CNAV1_LDPC_N200 = 200;
constexpr int32_t BEIDOU_CNAV1_LDPC_K200 = 100;
constexpr int32_t BEIDOU_CNAV1_LDPC_M200 = 100;
constexpr int32_t BEIDOU_CNAV1_LDPC_DC200 = 4;
constexpr int32_t BEIDOU_CNAV1_LDPC_N88 = 88;
constexpr int32_t BEIDOU_CNAV1_LDPC_K88 = 44;
constexpr int32_t BEIDOU_CNAV1_LDPC_M88 = 44;
constexpr int32_t BEIDOU_CNAV1_LDPC_DC88 = 4;
constexpr int32_t BEIDOU_CNAV1_LDPC_NM = 8;
constexpr int32_t BEIDOU_CNAV1_LDPC_MAX_ITER = 15;
/*
* GF(2^6) LUTs follow BDS-SIS-ICD-B1C-1.0 Appendix mapping tables.
* Primitive polynomial: p(x)=x^6+x+1.
*/
constexpr uint16_t BEIDOU_CNAV1_H100_200_INDEX[400] = {
11, 62, 102, 150, 9, 60, 100, 148, 0, 51, 142, 197, 22, 80, 116, 154, 4, 90,
131, 177, 47, 95, 138, 191, 51, 79, 146, 195, 44, 75, 142, 190, 13, 57, 135,
198, 24, 65, 120, 173, 6, 88, 129, 179, 7, 89, 130, 176, 6, 58, 106, 158, 8,
60, 108, 160, 44, 92, 139, 188, 4, 56, 104, 156, 10, 61, 101, 149, 39, 87, 123,
168, 15, 67, 105, 167, 50, 78, 145, 194, 17, 98, 151, 187, 46, 94, 137, 190,
14, 66, 104, 166, 7, 59, 107, 159, 21, 83, 119, 153, 31, 87, 114, 167, 2, 49,
140, 199, 12, 64, 106, 164, 40, 53, 132, 159, 19, 96, 149, 185, 16, 68, 112,
168, 14, 58, 132, 199, 34, 69, 125, 162, 23, 75, 119, 175, 42, 96, 144, 192,
8, 63, 103, 151, 23, 81, 117, 155, 24, 93, 111, 182, 20, 72, 116, 172, 17, 69,
113, 169, 34, 82, 130, 182, 1, 53, 101, 153, 46, 73, 140, 188, 13, 65, 107,
165, 2, 54, 102, 154, 18, 70, 114, 170, 26, 67, 122, 175, 29, 77, 125, 177,
36, 84, 120, 169, 25, 94, 108, 183, 39, 89, 137, 185, 21, 73, 117, 173, 28,
76, 124, 176, 36, 90, 138, 186, 33, 68, 124, 161, 12, 56, 134, 197, 29, 85,
112, 165, 45, 93, 136, 189, 27, 64, 123, 172, 28, 84, 115, 164, 25, 66, 121,
174, 37, 85, 121, 170, 3, 50, 141, 196, 48, 76, 147, 192, 35, 70, 126, 163,
32, 80, 128, 180, 0, 52, 100, 152, 43, 52, 135, 158, 35, 83, 131, 183, 10,
62, 110, 162, 19, 71, 115, 171, 15, 59, 133, 196, 33, 81, 129, 181, 41, 54,
133, 156, 20, 82, 118, 152, 38, 86, 122, 171, 30, 78, 126, 178, 9, 61, 109,
161, 26, 95, 109, 180, 45, 72, 143, 191, 1, 48, 143, 198, 40, 98, 146, 194,
18, 99, 148, 184, 5, 57, 105, 157, 41, 99, 147, 195, 31, 79, 127, 179, 3, 55,
103, 155, 22, 74, 118, 174, 37, 91, 139, 187, 5, 91, 128, 178, 30, 86, 113,
166, 43, 97, 145, 193, 16, 97, 150, 186, 11, 63, 111, 163, 32, 71, 127, 160,
42, 55, 134, 157, 38, 88, 136, 184, 47, 74, 141, 189, 49, 77, 144, 193, 27,
92, 110, 181};
constexpr uint8_t BEIDOU_CNAV1_H100_200_ELEMENT[400] = {
35, 13, 51, 60, 1, 44, 53, 24, 1, 45, 15, 6, 45, 15, 6, 1, 1, 44, 53, 24, 1,
45, 15, 6, 35, 46, 56, 15, 6, 1, 45, 15, 15, 6, 1, 45, 44, 53, 24, 1, 24, 1,
44, 30, 1, 45, 15, 6, 30, 24, 1, 44, 24, 1, 44, 30, 45, 15, 6, 1, 17, 38, 49,
11, 24, 1, 44, 30, 24, 1, 44, 53, 24, 1, 44, 53, 30, 24, 1, 44, 33, 42, 14,
24, 33, 42, 14, 24, 45, 15, 6, 1, 1, 45, 15, 6, 30, 24, 1, 44, 24, 1, 44, 53,
1, 44, 30, 24, 57, 25, 9, 41, 1, 45, 15, 6, 1, 45, 15, 6, 42, 36, 12, 57, 6,
1, 45, 15, 24, 1, 44, 53, 24, 1, 44, 30, 1, 45, 15, 6, 1, 45, 15, 6, 44, 53,
24, 1, 30, 24, 1, 44, 1, 44, 30, 24, 53, 24, 1, 44, 1, 44, 53, 24, 27, 28,
30, 31, 53, 24, 1, 44, 24, 1, 44, 30, 45, 15, 6, 1, 30, 24, 1, 44, 1, 45, 15,
6, 26, 22, 14, 2, 35, 13, 18, 60, 45, 15, 6, 1, 30, 1, 44, 7, 6, 1, 45, 15,
6, 1, 45, 15, 53, 24, 1, 44, 24, 1, 44, 53, 30, 24, 1, 44, 1, 44, 30, 24, 44,
53, 24, 1, 53, 24, 1, 44, 44, 30, 24, 1, 30, 24, 1, 44, 1, 44, 30, 24, 1, 44,
30, 24, 41, 16, 29, 51, 1, 44, 30, 24, 38, 23, 22, 7, 44, 53, 24, 1, 1, 45,
15, 6, 30, 24, 1, 44, 53, 24, 1, 44, 6, 1, 45, 15, 24, 1, 44, 53, 35, 46, 56,
15, 5, 33, 42, 14, 54, 7, 38, 23, 1, 45, 15, 6, 44, 30, 24, 1, 6, 1, 45, 15,
53, 24, 1, 44, 44, 53, 24, 1, 1, 44, 53, 24, 1, 44, 30, 24, 44, 30, 24, 1, 1,
44, 53, 24, 45, 15, 6, 1, 6, 1, 45, 15, 1, 44, 53, 24, 42, 47, 37, 32, 51, 60,
35, 13, 29, 28, 30, 31, 6, 1, 45, 15, 24, 1, 44, 53, 44, 53, 24, 1, 44, 30,
24, 1, 38, 49, 11, 17, 44, 30, 24, 1, 24, 1, 44, 30, 24, 1, 44, 30, 1, 44, 53,
24, 53, 24, 1, 44};
constexpr uint16_t BEIDOU_CNAV1_H44_88_INDEX[176] = {
14, 35, 56, 70, 11, 29, 55, 73, 13, 39, 53, 69, 15, 34, 57, 71, 1, 27, 45, 54,
23, 41, 63, 87, 2, 20, 46, 68, 6, 24, 50, 61, 2, 26, 61, 79, 9, 33, 59, 77, 4,
30, 48, 74, 22, 42, 59, 76, 12, 38, 52, 68, 23, 43, 58, 77, 19, 21, 63, 64, 11,
25, 65, 82, 17, 39, 44, 75, 9, 35, 49, 72, 19, 29, 66, 84, 13, 36, 56, 82, 17,
43, 67, 81, 22, 40, 62, 86, 3, 21, 47, 69, 10, 24, 64, 83, 0, 37, 70, 86, 5, 31,
49, 75, 4, 40, 53, 84, 5, 41, 52, 85, 18, 28, 67, 85, 0, 26, 44, 55, 10, 28, 54,
72, 7, 30, 50, 81, 1, 36, 71, 87, 16, 38, 45, 74, 8, 34, 48, 73, 8, 32, 58, 76,
12, 37, 57, 83, 6, 31, 51, 80, 15, 33, 47, 79, 16, 42, 66, 80, 7, 25, 51, 60, 3,
27, 60, 78, 14, 32, 46, 78, 18, 20, 62, 65};
constexpr uint8_t BEIDOU_CNAV1_H44_88_ELEMENT[176] = {
30, 24, 1, 44, 24, 1, 44, 30, 40, 32, 61, 18, 53, 24, 1, 44, 51, 60, 35, 13, 18,
15, 32, 61, 15, 6, 1, 45, 30, 24, 1, 44, 6, 1, 45, 15, 45, 15, 6, 1, 1, 45, 15,
6, 1, 44, 53, 24, 24, 1, 44, 53, 44, 30, 24, 1, 34, 33, 45, 36, 55, 9, 34, 3,
1, 44, 53, 24, 61, 47, 20, 8, 53, 24, 1, 44, 15, 6, 1, 45, 13, 18, 60, 35, 45,
15, 6, 1, 24, 1, 44, 53, 37, 32, 52, 47, 44, 53, 24, 1, 39, 36, 34, 33, 44, 35,
31, 50, 12, 25, 36, 14, 15, 35, 46, 56, 53, 24, 1, 44, 1, 44, 53, 24, 24, 1, 44,
30, 44, 30, 24, 1, 15, 6, 1, 45, 30, 24, 1, 44, 2, 50, 22, 14, 33, 42, 14, 5, 34,
3, 55, 9, 44, 35, 61, 50, 15, 6, 1, 45, 45, 15, 6, 1, 1, 44, 30, 24, 6, 1, 45,
15, 1, 44, 53, 24};
constexpr uint8_t BEIDOU_CNAV1_GF64_EXP[64] = {
1, 2, 4, 8, 16, 32, 3, 6, 12, 24, 48, 35, 5, 10, 20, 40, 19, 38, 15, 30, 60, 59, 53,
41, 17, 34, 7, 14, 28, 56, 51, 37, 9, 18, 36, 11, 22, 44, 27, 54, 47, 29, 58, 55,
45, 25, 50, 39, 13, 26, 52, 43, 21, 42, 23, 46, 31, 62, 63, 61, 57, 49, 33, 1};
constexpr int8_t BEIDOU_CNAV1_GF64_LOG[64] = {
0, 0, 1, 6, 2, 12, 7, 26, 3, 32, 13, 35, 8, 48, 27, 18, 4, 24, 33, 16, 14, 52, 36,
54, 9, 45, 49, 38, 28, 41, 19, 56, 5, 62, 25, 11, 34, 31, 17, 47, 15, 23, 53, 51,
37, 44, 55, 40, 10, 61, 46, 30, 50, 22, 39, 43, 29, 60, 42, 21, 20, 59, 57, 58};
bool beidou_cnav1_ldpc_decode_200_100(const float* symbol_llr, int32_t num_bits, uint8_t* info_bits600);
bool beidou_cnav1_ldpc_decode_88_44(const float* symbol_llr, int32_t num_bits, uint8_t* info_bits264);
bool beidou_cnav1_ldpc_decode_200_100_codeword(const float* symbol_llr, int32_t num_bits, uint8_t* codeword_bits1200);
bool beidou_cnav1_ldpc_decode_88_44_codeword(const float* symbol_llr, int32_t num_bits, uint8_t* codeword_bits528);
namespace GaloisField64
{
constexpr uint8_t kZero = 0U;
constexpr uint8_t kOrder = 63U;
constexpr uint8_t kFieldSize = 64U;
// LUT values come from BDS-SIS-ICD-B1C-1.0 Appendix GF(2^6) mapping rules.
inline bool valid_symbol(uint8_t x)
{
return x < kFieldSize;
}
inline uint8_t add(uint8_t a, uint8_t b)
{
if (!valid_symbol(a) || !valid_symbol(b))
{
return kZero;
}
return static_cast<uint8_t>(a ^ b);
}
inline uint8_t mul(uint8_t a, uint8_t b)
{
if (!valid_symbol(a) || !valid_symbol(b) || a == kZero || b == kZero)
{
return kZero;
}
const int32_t log_sum = static_cast<int32_t>(BEIDOU_CNAV1_GF64_LOG[a]) +
static_cast<int32_t>(BEIDOU_CNAV1_GF64_LOG[b]);
return BEIDOU_CNAV1_GF64_EXP[log_sum % kOrder];
}
inline uint8_t inv(uint8_t a)
{
if (!valid_symbol(a) || a == kZero)
{
return kZero;
}
const int32_t exponent = static_cast<int32_t>(kOrder) - static_cast<int32_t>(BEIDOU_CNAV1_GF64_LOG[a]);
return BEIDOU_CNAV1_GF64_EXP[exponent % kOrder];
}
} // namespace GaloisField64
struct BeidouCnav1LdpcGraph
{
int32_t num_checks = 0;
int32_t num_variables = 0;
int32_t row_weight = 0;
// Check-node (CSR-like) adjacency: [check_offsets[i], check_offsets[i+1]).
std::vector<uint32_t> check_offsets;
std::vector<uint16_t> check_to_var;
std::vector<uint8_t> check_to_h;
// Variable-node (CSC-like) adjacency: [var_offsets[j], var_offsets[j+1]).
std::vector<uint32_t> var_offsets;
std::vector<uint16_t> var_to_check;
std::vector<uint32_t> var_to_edge;
std::vector<uint8_t> var_to_h;
std::vector<uint8_t> var_to_h_inv;
};
bool beidou_cnav1_ldpc_init_graph(
int32_t num_checks,
int32_t num_variables,
int32_t row_weight,
const uint16_t* icd_index,
const uint8_t* icd_element,
int32_t num_entries,
BeidouCnav1LdpcGraph& graph);
const BeidouCnav1LdpcGraph& beidou_cnav1_ldpc_graph_200_100();
const BeidouCnav1LdpcGraph& beidou_cnav1_ldpc_graph_88_44();
#endif // GNSS_SDR_BEIDOU_CNAV1_LDPC_DECODER_H
@@ -0,0 +1,853 @@
/*!
* \file beidou_cnav1_navigation_message.cc
* \brief B-CNAV1 navigation message parser (BDS-SIS-ICD-B1C-1.0 §6.2)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "beidou_cnav1_navigation_message.h"
#include "Beidou_B1C.h"
#include "Beidou_CNAV1.h"
#include "beidou_cnav1_ldpc.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
namespace
{
void append_bits_as_chars(std::string& out, const int32_t* bits, int32_t n)
{
out.reserve(out.size() + static_cast<size_t>(n));
for (int32_t i = 0; i < n; i++)
{
out.push_back(bits[i] != 0 ? '1' : '0');
}
}
void append_bits_as_chars(std::string& out, const uint8_t* bits, int32_t n)
{
out.reserve(out.size() + static_cast<size_t>(n));
for (int32_t i = 0; i < n; i++)
{
out.push_back(bits[i] != 0U ? '1' : '0');
}
}
struct BchCodebook
{
int32_t n = 0;
int32_t k = 0;
int32_t corr_threshold = 0;
std::vector<int8_t> bipolar_codewords;
std::vector<uint16_t> messages;
};
std::vector<int8_t> encode_bch_hypothesis_bipolar(
uint16_t msg_bits,
int32_t n,
int32_t k,
const std::vector<int32_t>& feedback_pos_1based)
{
std::vector<int8_t> register_state(static_cast<size_t>(k), 1);
std::vector<int8_t> encoded(static_cast<size_t>(n), 1);
for (int32_t i = 0; i < k; i++)
{
const auto bit = static_cast<int32_t>((msg_bits >> static_cast<uint32_t>(k - 1 - i)) & 1U);
register_state[static_cast<size_t>(k - 1 - i)] = (bit == 0) ? 1 : -1;
}
for (int32_t ind = 0; ind < n; ind++)
{
encoded[static_cast<size_t>(ind)] = register_state.back();
int8_t feedback = 1;
for (const int32_t pos : feedback_pos_1based)
{
feedback = static_cast<int8_t>(feedback * register_state[static_cast<size_t>(pos - 1)]);
}
for (int32_t j = k - 1; j > 0; j--)
{
register_state[static_cast<size_t>(j)] = register_state[static_cast<size_t>(j - 1)];
}
register_state[0] = feedback;
}
return encoded;
}
BchCodebook build_bch_codebook(
int32_t n,
int32_t k,
const std::vector<int32_t>& feedback_pos_1based,
int32_t corr_threshold)
{
BchCodebook cb{};
cb.n = n;
cb.k = k;
cb.corr_threshold = corr_threshold;
const uint32_t num_messages = 1U << static_cast<uint32_t>(k);
cb.bipolar_codewords.resize(static_cast<size_t>(num_messages) * static_cast<size_t>(n));
cb.messages.resize(num_messages);
for (uint32_t m = 0; m < num_messages; m++)
{
const auto encoded = encode_bch_hypothesis_bipolar(static_cast<uint16_t>(m), n, k, feedback_pos_1based);
std::copy(encoded.begin(), encoded.end(),
cb.bipolar_codewords.begin() + static_cast<ptrdiff_t>(m) * static_cast<ptrdiff_t>(n));
cb.messages[m] = static_cast<uint16_t>(m);
}
return cb;
}
bool decode_bch_codeword(const int32_t* in, int32_t* out_msg, const BchCodebook& cb)
{
int32_t best_corr = std::numeric_limits<int32_t>::min();
uint16_t best_message = 0U;
for (size_t i = 0; i < cb.messages.size(); i++)
{
int32_t corr = 0;
const auto* cw = cb.bipolar_codewords.data() + static_cast<ptrdiff_t>(i) * cb.n;
for (int32_t j = 0; j < cb.n; j++)
{
const int32_t bit = in[j] > 0 ? 1 : 0;
const int32_t bipolar = (bit == 0) ? 1 : -1;
corr += static_cast<int32_t>(cw[j]) * bipolar;
}
if (corr > best_corr)
{
best_corr = corr;
best_message = cb.messages[i];
}
}
if (best_corr < cb.corr_threshold)
{
return false;
}
for (int32_t bit = 0; bit < cb.k; bit++)
{
out_msg[bit] = static_cast<int32_t>((best_message >> static_cast<uint32_t>(cb.k - 1 - bit)) & 1U);
}
return true;
}
static bool decode_bch_21_6(const int32_t* in, int32_t* out_msg)
{
static const std::vector<int32_t> feedback_pos = {2, 4, 5, 6};
static const BchCodebook cb = build_bch_codebook(21, 6, feedback_pos, 15);
return decode_bch_codeword(in, out_msg, cb);
}
static bool decode_bch_51_8(const int32_t* in, int32_t* out_msg)
{
static const std::vector<int32_t> feedback_pos = {1, 4, 5, 6, 7, 8};
static const BchCodebook cb = build_bch_codebook(51, 8, feedback_pos, 29);
return decode_bch_codeword(in, out_msg, cb);
}
uint32_t bits_to_unsigned(const int32_t* bits, int32_t length)
{
uint32_t value = 0;
for (int32_t i = 0; i < length; i++)
{
value = (value << 1) | static_cast<uint32_t>(bits[i] & 1);
}
return value;
}
template <typename UIntT>
UIntT read_bits_be(const uint8_t* bits, int32_t offset, int32_t length)
{
UIntT value = 0;
for (int32_t i = 0; i < length; i++)
{
value = static_cast<UIntT>((value << 1U) | static_cast<UIntT>(bits[offset + i] & 0x1U));
}
return value;
}
template <typename IntT>
IntT sign_extend(uint64_t raw, int32_t bit_width)
{
const uint64_t sign_mask = 1ULL << static_cast<uint64_t>(bit_width - 1);
if ((raw & sign_mask) == 0ULL)
{
return static_cast<IntT>(raw);
}
const uint64_t extend_mask = ~((1ULL << static_cast<uint64_t>(bit_width)) - 1ULL);
return static_cast<IntT>(raw | extend_mask);
}
double twos_pow(int32_t exponent)
{
return std::ldexp(1.0, exponent);
}
void deinterleave_sf2_sf3(const float* interleaved, float* sf2, float* sf3)
{
int32_t sf2_idx = 0;
int32_t sf3_idx = 0;
for (int32_t in_idx = 0; in_idx < BEIDOU_CNAV1_INTERLEAVED_SYMBOLS; in_idx++)
{
// Received stream is column-major readout of a 36x48 block.
const int32_t row = in_idx % BEIDOU_CNAV1_INTERLEAVE_ROWS;
const int32_t col = in_idx / BEIDOU_CNAV1_INTERLEAVE_ROWS;
if (row < 33)
{
const int32_t group = row / 3; // 0..10
const int32_t rem = row % 3; // 0,1 => SF2 rows ; 2 => SF3 row
if (rem < 2)
{
const int32_t sf2_row = group * 2 + rem; // 0..21
sf2[sf2_row * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = interleaved[in_idx];
sf2_idx++;
}
else
{
const int32_t sf3_row = group; // 0..10
sf3[sf3_row * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = interleaved[in_idx];
sf3_idx++;
}
}
else
{
const int32_t sf2_row = 22 + (row - 33); // 22..24
sf2[sf2_row * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = interleaved[in_idx];
sf2_idx++;
}
}
(void)sf2_idx;
(void)sf3_idx;
}
uint64_t read_unsigned(const uint8_t* bits, int32_t offset, int32_t length)
{
return read_bits_be<uint64_t>(bits, offset, length);
}
int64_t read_signed(const uint8_t* bits, int32_t offset, int32_t length)
{
const uint64_t raw = read_unsigned(bits, offset, length);
return sign_extend<int64_t>(raw, length);
}
uint32_t crc24q(const uint8_t* bits, int32_t num_bits)
{
// CRC-24Q (poly 0x864CFB)
uint32_t crc = 0;
const uint32_t POLY = 0x864CFBU;
for (int32_t i = 0; i < num_bits; i++)
{
uint32_t msb = (crc >> 23) & 1U;
uint32_t bit = (bits[i] != 0U) ? 1U : 0U;
crc = (crc << 1) & 0xFFFFFFU;
if (msb ^ bit)
{
crc ^= POLY;
}
}
return crc;
}
bool verify_crc24q(const uint8_t* bits, int32_t data_bits)
{
const uint32_t computed = crc24q(bits, data_bits);
const auto received = static_cast<uint32_t>(read_unsigned(bits, data_bits, BEIDOU_CNAV1_CRC_BITS));
return computed == received;
}
double a_ref_from_sat_type(uint32_t sat_type)
{
if (sat_type == 0b01U || sat_type == 0b10U)
{
return BEIDOU_CNAV1_A_REF_IGSO;
}
return BEIDOU_CNAV1_A_REF_MEO;
}
int32_t nav_type_from_sat_type(uint32_t sat_type)
{
if (sat_type == 0b01U)
{
return 0;
}
return 1;
}
void parse_subframe2(const uint8_t* bits, Beidou_Cnav1_Ephemeris& eph, double soh_seconds)
{
int32_t offset = 0;
eph.WN = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
const auto how = static_cast<int32_t>(read_unsigned(bits, offset, 8));
offset += 8;
eph.IODC = static_cast<double>(read_unsigned(bits, offset, 10));
offset += 10;
eph.IODE = static_cast<double>(read_unsigned(bits, offset, 8));
offset += 8;
const auto toe_raw = static_cast<int32_t>(read_unsigned(bits, offset, 11));
offset += 11;
const auto sat_type = static_cast<uint32_t>(read_unsigned(bits, offset, 2));
offset += 2;
const auto delta_a = static_cast<double>(read_signed(bits, offset, 26)) * BEIDOU_CNAV1_DELTA_A_LSB;
offset += 26;
eph.Adot = static_cast<double>(read_signed(bits, offset, 25)) * BEIDOU_CNAV1_A_DOT_LSB;
offset += 25;
eph.delta_n = static_cast<double>(read_signed(bits, offset, 17)) * BEIDOU_CNAV1_DELTA_N0_LSB;
offset += 17;
eph.delta_ndot = static_cast<double>(read_signed(bits, offset, 23)) * BEIDOU_CNAV1_DELTA_N0_DOT_LSB;
offset += 23;
eph.M_0 = static_cast<double>(read_signed(bits, offset, 33)) * BEIDOU_CNAV1_M0_LSB;
offset += 33;
eph.ecc = static_cast<double>(read_unsigned(bits, offset, 33)) * BEIDOU_CNAV1_E_LSB;
offset += 33;
eph.omega = static_cast<double>(read_signed(bits, offset, 33)) * BEIDOU_CNAV1_OMEGA_LSB;
offset += 33;
eph.OMEGA_0 = static_cast<double>(read_signed(bits, offset, 33)) * BEIDOU_CNAV1_OMEGA_LSB;
offset += 33;
eph.i_0 = static_cast<double>(read_signed(bits, offset, 33)) * BEIDOU_CNAV1_I0_LSB;
offset += 33;
eph.OMEGAdot = static_cast<double>(read_signed(bits, offset, 19)) * BEIDOU_CNAV1_OMEGADOT_LSB;
offset += 19;
eph.idot = static_cast<double>(read_signed(bits, offset, 15)) * BEIDOU_CNAV1_IDOT_LSB;
offset += 15;
eph.Cis = static_cast<double>(read_signed(bits, offset, 16)) * BEIDOU_CNAV1_CIS_LSB;
offset += 16;
eph.Cic = static_cast<double>(read_signed(bits, offset, 16)) * BEIDOU_CNAV1_CIC_LSB;
offset += 16;
eph.Crs = static_cast<double>(read_signed(bits, offset, 24)) * BEIDOU_CNAV1_CRS_LSB;
offset += 24;
eph.Crc = static_cast<double>(read_signed(bits, offset, 24)) * BEIDOU_CNAV1_CRC_LSB;
offset += 24;
eph.Cus = static_cast<double>(read_signed(bits, offset, 21)) * BEIDOU_CNAV1_CUS_LSB;
offset += 21;
eph.Cuc = static_cast<double>(read_signed(bits, offset, 21)) * BEIDOU_CNAV1_CUC_LSB;
offset += 21;
eph.toc = static_cast<int32_t>(read_unsigned(bits, offset, 11));
offset += 11;
eph.af0 = static_cast<double>(read_signed(bits, offset, 25)) * BEIDOU_CNAV1_AF0_LSB;
offset += 25;
eph.af1 = static_cast<double>(read_signed(bits, offset, 22)) * BEIDOU_CNAV1_AF1_LSB;
offset += 22;
eph.af2 = static_cast<double>(read_signed(bits, offset, 11)) * BEIDOU_CNAV1_AF2_LSB;
offset += 11;
eph.TGD_B2ap = static_cast<double>(read_signed(bits, offset, 12)) * BEIDOU_CNAV1_TGD_LSB;
offset += 12;
eph.ISC_B1Cd = static_cast<double>(read_signed(bits, offset, 12)) * BEIDOU_CNAV1_ISC_LSB;
offset += 12;
eph.TGD_B1Cp = static_cast<double>(read_signed(bits, offset, 12)) * BEIDOU_CNAV1_TGD_LSB;
offset += 12;
const double a_ref = a_ref_from_sat_type(sat_type);
eph.A0 = a_ref + delta_a;
eph.toe = static_cast<int32_t>(toe_raw * BEIDOU_CNAV1_TOE_TOC_LSB);
eph.toc = static_cast<int32_t>(eph.toc * BEIDOU_CNAV1_TOE_TOC_LSB);
eph.tow = static_cast<int32_t>(how * 3600 + soh_seconds);
eph.nav_type = nav_type_from_sat_type(sat_type);
eph.sat_type = static_cast<int32_t>(sat_type);
eph.sig_type = BDS_EPH_SOURCE_CNAV1;
}
void parse_page_common(
const uint8_t* bits,
bool has_sisaioe,
bool has_sisaioc,
Bds3_B1c_PageData& page_data,
int32_t& offset)
{
offset = 0;
page_data.common.page_id = static_cast<int32_t>(read_unsigned(bits, offset, 6));
offset += 6;
page_data.common.hs = static_cast<int32_t>(read_unsigned(bits, offset, 2));
offset += 2;
page_data.common.dif = read_unsigned(bits, offset, 1) != 0U;
offset += 1;
page_data.common.sif = read_unsigned(bits, offset, 1) != 0U;
offset += 1;
page_data.common.aif = read_unsigned(bits, offset, 1) != 0U;
offset += 1;
page_data.common.sismai = static_cast<int32_t>(read_unsigned(bits, offset, 4));
offset += 4;
page_data.sisaioe = 0;
page_data.sisaioc = 0;
if (has_sisaioe)
{
page_data.sisaioe = static_cast<int32_t>(read_unsigned(bits, offset, 5));
offset += 5;
}
if (has_sisaioc)
{
page_data.sisaioc = static_cast<int32_t>(read_unsigned(bits, offset, 22));
offset += 22;
}
}
void parse_reduced_almanac(const uint8_t* bits, int32_t& offset, Bds3_B1c_AlmanacReduced& almanac)
{
almanac.prn = static_cast<int32_t>(read_unsigned(bits, offset, 6));
offset += 6;
almanac.sat_type = static_cast<int32_t>(read_unsigned(bits, offset, 2));
offset += 2;
almanac.delta_a_m = static_cast<double>(read_signed(bits, offset, 8)) * twos_pow(9);
offset += 8;
almanac.omega0_rad = static_cast<double>(read_signed(bits, offset, 7)) * twos_pow(-6) * GNSS_PI;
offset += 7;
almanac.phi0_rad = static_cast<double>(read_signed(bits, offset, 7)) * twos_pow(-6) * GNSS_PI;
offset += 7;
almanac.health = static_cast<int32_t>(read_unsigned(bits, offset, 8));
offset += 8;
}
void parse_medium_almanac(const uint8_t* bits, int32_t& offset, Bds3_B1c_AlmanacMedium& almanac)
{
almanac.prn = static_cast<int32_t>(read_unsigned(bits, offset, 6));
offset += 6;
almanac.sat_type = static_cast<int32_t>(read_unsigned(bits, offset, 2));
offset += 2;
almanac.wna = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
almanac.toa_s = static_cast<int32_t>(read_unsigned(bits, offset, 8)) * 4096;
offset += 8;
almanac.eccentricity = static_cast<double>(read_unsigned(bits, offset, 11)) * twos_pow(-16);
offset += 11;
almanac.delta_i_rad = static_cast<double>(read_signed(bits, offset, 11)) * twos_pow(-14) * GNSS_PI;
offset += 11;
almanac.sqrt_a_m_sqrt = static_cast<double>(read_unsigned(bits, offset, 17)) * twos_pow(-4);
offset += 17;
almanac.omega0_rad = static_cast<double>(read_signed(bits, offset, 16)) * twos_pow(-15) * GNSS_PI;
offset += 16;
almanac.omega_dot_rad_s = static_cast<double>(read_signed(bits, offset, 11)) * twos_pow(-33) * GNSS_PI;
offset += 11;
almanac.omega_rad = static_cast<double>(read_signed(bits, offset, 16)) * twos_pow(-15) * GNSS_PI;
offset += 16;
almanac.m0_rad = static_cast<double>(read_signed(bits, offset, 16)) * twos_pow(-15) * GNSS_PI;
offset += 16;
almanac.af0_s = static_cast<double>(read_signed(bits, offset, 11)) * twos_pow(-20);
offset += 11;
almanac.af1_s_s = static_cast<double>(read_signed(bits, offset, 10)) * twos_pow(-37);
offset += 10;
almanac.health = static_cast<int32_t>(read_unsigned(bits, offset, 8));
offset += 8;
}
void parse_eop(const uint8_t* bits, int32_t& offset, Bds3_B1c_Eop& eop)
{
eop.t_eop_s = static_cast<int32_t>(read_unsigned(bits, offset, 16)) * 16;
offset += 16;
eop.pm_x_arcsec = static_cast<double>(read_signed(bits, offset, 21)) * twos_pow(-20);
offset += 21;
eop.pm_x_dot_arcsec_day = static_cast<double>(read_signed(bits, offset, 15)) * twos_pow(-21);
offset += 15;
eop.pm_y_arcsec = static_cast<double>(read_signed(bits, offset, 21)) * twos_pow(-20);
offset += 21;
eop.pm_y_dot_arcsec_day = static_cast<double>(read_signed(bits, offset, 15)) * twos_pow(-21);
offset += 15;
eop.delta_ut1_s = static_cast<double>(read_signed(bits, offset, 31)) * twos_pow(-24);
offset += 31;
eop.delta_ut1_dot_s_day = static_cast<double>(read_signed(bits, offset, 19)) * twos_pow(-25);
offset += 19;
}
void parse_bgto(const uint8_t* bits, int32_t& offset, Bds3_B1c_Bgto& bgto)
{
bgto.gnss_id = static_cast<int32_t>(read_unsigned(bits, offset, 3));
offset += 3;
bgto.wn0_bgto = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
bgto.t0_bgto_s = static_cast<int32_t>(read_unsigned(bits, offset, 16)) * 16;
offset += 16;
bgto.a0_bgto_s = static_cast<double>(read_signed(bits, offset, 16)) * twos_pow(-35);
offset += 16;
bgto.a1_bgto_s_s = static_cast<double>(read_signed(bits, offset, 13)) * twos_pow(-51);
offset += 13;
bgto.a2_bgto_s_s2 = static_cast<double>(read_signed(bits, offset, 7)) * twos_pow(-68);
offset += 7;
}
void parse_page1(
const uint8_t* bits,
Beidou_Cnav1_Iono& iono,
Beidou_Cnav1_Utc_Model& utc,
Bds3_B1c_PageData& page_data)
{
int32_t offset = 0;
parse_page_common(bits, true, true, page_data, offset);
// ICD Table 7-10
iono.alpha1 = static_cast<double>(read_unsigned(bits, offset, 10)) * 0.125;
offset += 10;
iono.alpha2 = static_cast<double>(read_signed(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha3 = static_cast<double>(read_unsigned(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha4 = static_cast<double>(read_unsigned(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha5 = static_cast<double>(read_unsigned(bits, offset, 8)) * -0.125;
offset += 8;
iono.alpha6 = static_cast<double>(read_signed(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha7 = static_cast<double>(read_signed(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha8 = static_cast<double>(read_signed(bits, offset, 8)) * 0.125;
offset += 8;
iono.alpha9 = static_cast<double>(read_signed(bits, offset, 8)) * 0.125;
offset += 8;
// ICD Table 7-20 / Figure 6-17
utc.A0 = static_cast<double>(read_signed(bits, offset, 16)) * TWO_N35;
offset += 16;
utc.A1 = static_cast<double>(read_signed(bits, offset, 13)) * TWO_N51;
offset += 13;
utc.A2 = static_cast<double>(read_signed(bits, offset, 7)) * TWO_N68;
offset += 7;
utc.delta_t_LS = static_cast<int32_t>(read_signed(bits, offset, 8));
offset += 8;
utc.tot = static_cast<int32_t>(read_unsigned(bits, offset, 16)) * 16; // scale 2^4
offset += 16;
utc.WN_t = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
utc.WN_LSF = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
utc.DN = static_cast<int32_t>(read_unsigned(bits, offset, 3));
offset += 3;
utc.delta_t_LSF = static_cast<int32_t>(read_signed(bits, offset, 8));
}
void parse_page2(const uint8_t* bits, Bds3_B1c_PageData& page_data)
{
int32_t offset = 0;
parse_page_common(bits, false, true, page_data, offset);
page_data.wna = static_cast<int32_t>(read_unsigned(bits, offset, 13));
offset += 13;
page_data.toa_s = static_cast<int32_t>(read_unsigned(bits, offset, 8)) * 4096;
offset += 8;
for (auto& alm : page_data.reduced_almanac)
{
parse_reduced_almanac(bits, offset, alm);
}
}
void parse_page3(const uint8_t* bits, Bds3_B1c_PageData& page_data)
{
int32_t offset = 0;
parse_page_common(bits, true, false, page_data, offset);
parse_eop(bits, offset, page_data.eop);
parse_bgto(bits, offset, page_data.bgto);
offset += 14; // reserved
offset += 24; // CRC
}
void parse_page4(const uint8_t* bits, Bds3_B1c_PageData& page_data)
{
int32_t offset = 0;
parse_page_common(bits, false, true, page_data, offset);
parse_medium_almanac(bits, offset, page_data.medium_almanac);
offset += 47; // reserved
offset += 24; // CRC
}
} // namespace
bool Beidou_Cnav1_Navigation_Message::decode_frame(
const float* symbols,
int32_t num_symbols,
int32_t expected_prn,
int32_t* fail_stage)
{
auto set_fail = [&](int32_t stage) {
if (fail_stage != nullptr)
{
*fail_stage = stage;
}
};
if (fail_stage != nullptr)
{
*fail_stage = -1;
}
if (num_symbols < BEIDOU_CNAV1_FRAME_SYMBOLS)
{
set_fail(0);
return false;
}
std::array<float, BEIDOU_CNAV1_FRAME_SYMBOLS> bit_llr{};
for (int32_t i = 0; i < BEIDOU_CNAV1_FRAME_SYMBOLS; i++)
{
bit_llr[i] = symbols[i];
}
std::array<int32_t, BEIDOU_CNAV1_SUBFRAME1_SYMBOLS> hard_bits{};
for (int32_t i = 0; i < BEIDOU_CNAV1_SUBFRAME1_SYMBOLS; i++)
{
hard_bits[i] = (symbols[i] >= 0.0F) ? 1 : 0;
}
int32_t prn_bits[6];
int32_t soh_bits[8];
if (!decode_bch_21_6(hard_bits.data(), prn_bits))
{
set_fail(1);
return false;
}
if (!decode_bch_51_8(hard_bits.data() + 21, soh_bits))
{
set_fail(2);
return false;
}
const uint32_t prn = bits_to_unsigned(prn_bits, 6);
if (prn < 1U || prn > static_cast<uint32_t>(BEIDOU_B1C_NUMBER_OF_PRNS))
{
set_fail(3);
return false;
}
if (expected_prn > 0 && static_cast<uint32_t>(expected_prn) != prn)
{
set_fail(4);
return false;
}
const uint32_t soh = bits_to_unsigned(soh_bits, 8);
const auto soh_seconds = static_cast<double>(soh * BEIDOU_CNAV1_SOH_LSB_S);
ephemeris_.PRN = static_cast<int32_t>(prn);
std::array<float, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> sf2_llr{};
std::array<float, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> sf3_llr{};
deinterleave_sf2_sf3(bit_llr.data() + BEIDOU_CNAV1_SUBFRAME1_SYMBOLS, sf2_llr.data(), sf3_llr.data());
std::array<uint8_t, BEIDOU_CNAV1_SF2_DATA_BITS> sf2_data{};
std::array<uint8_t, BEIDOU_CNAV1_SF3_DATA_BITS> sf3_data{};
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> sf2_codeword_bits{};
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> sf3_codeword_bits{};
// One LLR polarity: PLL 180° is handled by the telemetry invert path upstream.
bool sf2_ldpc_ok = beidou_cnav1_ldpc_decode_200_100_codeword(
sf2_llr.data(), BEIDOU_CNAV1_SUBFRAME2_SYMBOLS, sf2_codeword_bits.data());
if (!sf2_ldpc_ok)
{
// Hard-slice LLRs if LDPC fails.
for (int32_t i = 0; i < BEIDOU_CNAV1_SF2_DATA_BITS; i++)
{
sf2_data[static_cast<size_t>(i)] = (sf2_llr[static_cast<size_t>(i)] >= 0.0F) ? 1U : 0U;
}
}
else
{
// Prefer the codeword half that passes CRC (info may be first or second K symbols).
std::copy_n(sf2_codeword_bits.begin(), BEIDOU_CNAV1_SF2_DATA_BITS, sf2_data.begin());
if (!verify_crc24q(sf2_data.data(), BEIDOU_CNAV1_SF2_DATA_BITS - BEIDOU_CNAV1_CRC_BITS))
{
std::copy_n(
sf2_codeword_bits.begin() + BEIDOU_CNAV1_SF2_DATA_BITS,
BEIDOU_CNAV1_SF2_DATA_BITS,
sf2_data.begin());
}
}
bool sf3_ldpc_ok = beidou_cnav1_ldpc_decode_88_44_codeword(
sf3_llr.data(), BEIDOU_CNAV1_SUBFRAME3_SYMBOLS, sf3_codeword_bits.data());
if (!sf3_ldpc_ok)
{
for (int32_t i = 0; i < BEIDOU_CNAV1_SF3_DATA_BITS; i++)
{
sf3_data[static_cast<size_t>(i)] = (sf3_llr[static_cast<size_t>(i)] >= 0.0F) ? 1U : 0U;
}
}
else
{
std::copy_n(sf3_codeword_bits.begin(), BEIDOU_CNAV1_SF3_DATA_BITS, sf3_data.begin());
if (!verify_crc24q(sf3_data.data(), BEIDOU_CNAV1_SF3_DATA_BITS - BEIDOU_CNAV1_CRC_BITS))
{
std::copy_n(
sf3_codeword_bits.begin() + BEIDOU_CNAV1_SF3_DATA_BITS,
BEIDOU_CNAV1_SF3_DATA_BITS,
sf3_data.begin());
}
}
const bool sf2_crc_ok = verify_crc24q(sf2_data.data(), BEIDOU_CNAV1_SF2_DATA_BITS - BEIDOU_CNAV1_CRC_BITS);
const bool sf3_crc_ok = verify_crc24q(sf3_data.data(), BEIDOU_CNAV1_SF3_DATA_BITS - BEIDOU_CNAV1_CRC_BITS);
if (!sf2_crc_ok)
{
set_fail(sf2_ldpc_ok ? 6 : 5);
return false;
}
// Nav monitor string (same role as DNAV subframe bit chars): SF1 BCH info + SF2 data [+ SF3 if CRC OK].
last_nav_bits_.clear();
append_bits_as_chars(last_nav_bits_, prn_bits, 6);
append_bits_as_chars(last_nav_bits_, soh_bits, 8);
append_bits_as_chars(last_nav_bits_, sf2_data.data(), BEIDOU_CNAV1_SF2_DATA_BITS);
if (sf3_crc_ok)
{
append_bits_as_chars(last_nav_bits_, sf3_data.data(), BEIDOU_CNAV1_SF3_DATA_BITS);
}
Beidou_Cnav1_Ephemeris candidate{};
parse_subframe2(sf2_data.data(), candidate, soh_seconds);
candidate.PRN = static_cast<int32_t>(prn);
// ICD §7.4.3: publish only when IODE matches IODC low 8 bits.
const auto iode = static_cast<uint32_t>(candidate.IODE);
const auto iodc_lo = static_cast<uint32_t>(candidate.IODC) & 0xFFU;
tow_s_ = static_cast<double>(candidate.tow);
if (iode == iodc_lo)
{
ephemeris_ = candidate;
flag_new_ephemeris_ = true;
}
if (sf3_crc_ok)
{
const auto page_id = static_cast<int32_t>(read_unsigned(sf3_data.data(), 0, 6));
switch (page_id)
{
case 1:
parse_page1(sf3_data.data(), iono_, utc_model_, page_data_);
flag_new_iono_ = true;
flag_new_utc_ = true;
flag_new_page_data_ = true;
break;
case 2:
parse_page2(sf3_data.data(), page_data_);
flag_new_page_data_ = true;
break;
case 3:
parse_page3(sf3_data.data(), page_data_);
flag_new_page_data_ = true;
break;
case 4:
parse_page4(sf3_data.data(), page_data_);
flag_new_page_data_ = true;
break;
default:
break;
}
if (flag_new_page_data_)
{
// Copy SF3 HS into the stored ephemeris.
ephemeris_.hs = page_data_.common.hs;
}
}
return true;
}
bool Beidou_Cnav1_Navigation_Message::decode_frame_symbols(
const float* symbols,
int32_t num_symbols,
int32_t expected_prn,
int32_t* fail_stage)
{
return decode_frame(symbols, num_symbols, expected_prn, fail_stage);
}
bool Beidou_Cnav1_Navigation_Message::probe_subframe1_prn(const float* symbols, int32_t num_symbols, int32_t expected_prn) const
{
if (num_symbols < BEIDOU_CNAV1_SUBFRAME1_SYMBOLS)
{
return false;
}
std::array<int32_t, BEIDOU_CNAV1_SUBFRAME1_SYMBOLS> hard_bits{};
for (int32_t i = 0; i < BEIDOU_CNAV1_SUBFRAME1_SYMBOLS; i++)
{
hard_bits[i] = (symbols[i] >= 0.0F) ? 1 : 0;
}
int32_t prn_bits[6];
int32_t soh_bits[8];
if (!decode_bch_21_6(hard_bits.data(), prn_bits))
{
return false;
}
if (!decode_bch_51_8(hard_bits.data() + 21, soh_bits))
{
return false;
}
const uint32_t prn = bits_to_unsigned(prn_bits, 6);
if (prn < 1U || prn > static_cast<uint32_t>(BEIDOU_B1C_NUMBER_OF_PRNS))
{
return false;
}
return expected_prn <= 0 || static_cast<uint32_t>(expected_prn) == prn;
}
bool Beidou_Cnav1_Navigation_Message::have_new_ephemeris() const
{
return flag_new_ephemeris_;
}
bool Beidou_Cnav1_Navigation_Message::have_new_iono() const
{
return flag_new_iono_;
}
bool Beidou_Cnav1_Navigation_Message::have_new_utc_model() const
{
return flag_new_utc_;
}
bool Beidou_Cnav1_Navigation_Message::have_new_page_data() const
{
return flag_new_page_data_;
}
void Beidou_Cnav1_Navigation_Message::clear_flags()
{
flag_new_ephemeris_ = false;
flag_new_iono_ = false;
flag_new_utc_ = false;
flag_new_page_data_ = false;
last_nav_bits_.clear();
}
const Beidou_Cnav1_Ephemeris& Beidou_Cnav1_Navigation_Message::get_ephemeris() const
{
return ephemeris_;
}
const Beidou_Cnav1_Iono& Beidou_Cnav1_Navigation_Message::get_iono() const
{
return iono_;
}
const Beidou_Cnav1_Utc_Model& Beidou_Cnav1_Navigation_Message::get_utc_model() const
{
return utc_model_;
}
const Bds3_B1c_PageData& Beidou_Cnav1_Navigation_Message::get_page_data() const
{
return page_data_;
}
const std::string& Beidou_Cnav1_Navigation_Message::get_last_nav_bits() const
{
return last_nav_bits_;
}
double Beidou_Cnav1_Navigation_Message::get_tow_s() const
{
return tow_s_;
}
@@ -0,0 +1,136 @@
/*!
* \file beidou_cnav1_navigation_message.h
* \brief B-CNAV1 navigation message parser (§6.2)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_NAVIGATION_MESSAGE_H
#define GNSS_SDR_BEIDOU_CNAV1_NAVIGATION_MESSAGE_H
#include "beidou_cnav1_ephemeris.h"
#include "beidou_cnav1_iono.h"
#include "beidou_cnav1_utc_model.h"
#include <array>
#include <cstdint>
#include <string>
struct Bds3_B1c_PageCommon
{
int32_t page_id{};
int32_t sismai{};
int32_t hs{};
bool dif{};
bool sif{};
bool aif{};
};
struct Bds3_B1c_AlmanacReduced
{
int32_t prn{};
int32_t sat_type{};
double delta_a_m{};
double omega0_rad{};
double phi0_rad{};
int32_t health{};
};
struct Bds3_B1c_AlmanacMedium
{
int32_t prn{};
int32_t sat_type{};
int32_t wna{};
int32_t toa_s{};
double eccentricity{};
double delta_i_rad{};
double sqrt_a_m_sqrt{};
double omega0_rad{};
double omega_dot_rad_s{};
double omega_rad{};
double m0_rad{};
double af0_s{};
double af1_s_s{};
int32_t health{};
};
struct Bds3_B1c_Eop
{
int32_t t_eop_s{};
double pm_x_arcsec{};
double pm_x_dot_arcsec_day{};
double pm_y_arcsec{};
double pm_y_dot_arcsec_day{};
double delta_ut1_s{};
double delta_ut1_dot_s_day{};
};
struct Bds3_B1c_Bgto
{
int32_t gnss_id{};
int32_t wn0_bgto{};
int32_t t0_bgto_s{};
double a0_bgto_s{};
double a1_bgto_s_s{};
double a2_bgto_s_s2{};
};
struct Bds3_B1c_PageData
{
Bds3_B1c_PageCommon common{};
int32_t sisaioc{};
int32_t sisaioe{};
int32_t wna{};
int32_t toa_s{};
std::array<Bds3_B1c_AlmanacReduced, 4> reduced_almanac{};
Bds3_B1c_Eop eop{};
Bds3_B1c_Bgto bgto{};
Bds3_B1c_AlmanacMedium medium_almanac{};
};
struct Beidou_Cnav1_PageData_Message
{
int32_t PRN{};
Bds3_B1c_PageData page_data{};
};
class Beidou_Cnav1_Navigation_Message
{
public:
bool decode_frame(const float* symbols, int32_t num_symbols, int32_t expected_prn = 0, int32_t* fail_stage = nullptr);
bool decode_frame_symbols(const float* symbols, int32_t num_symbols, int32_t expected_prn = 0, int32_t* fail_stage = nullptr);
bool probe_subframe1_prn(const float* symbols, int32_t num_symbols, int32_t expected_prn) const;
bool have_new_ephemeris() const;
bool have_new_iono() const;
bool have_new_utc_model() const;
bool have_new_page_data() const;
void clear_flags();
const Beidou_Cnav1_Ephemeris& get_ephemeris() const;
const Beidou_Cnav1_Iono& get_iono() const;
const Beidou_Cnav1_Utc_Model& get_utc_model() const;
const Bds3_B1c_PageData& get_page_data() const;
//! Last successfully decoded frame as '0'/'1' chars for Nav_msg_from_TLM (SF1 info + SF2 [+ SF3]).
const std::string& get_last_nav_bits() const;
double get_tow_s() const;
private:
Beidou_Cnav1_Ephemeris ephemeris_{};
Beidou_Cnav1_Iono iono_{};
Beidou_Cnav1_Utc_Model utc_model_{};
bool flag_new_ephemeris_{false};
bool flag_new_iono_{false};
bool flag_new_utc_{false};
bool flag_new_page_data_{false};
double tow_s_{0.0};
Bds3_B1c_PageData page_data_{};
std::string last_nav_bits_{};
};
#endif
@@ -0,0 +1,36 @@
/*!
* \file beidou_cnav1_utc_model.h
* \brief BeiDou B-CNAV1 UTC model (BDS-SIS-ICD-B1C-1.0 §7.12)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_CNAV1_UTC_MODEL_H
#define GNSS_SDR_BEIDOU_CNAV1_UTC_MODEL_H
#include <cstdint>
class Beidou_Cnav1_Utc_Model
{
public:
double A0{};
double A1{};
double A2{};
int32_t tot{};
int32_t WN_t{};
int32_t WN_LSF{};
int32_t DN{};
int32_t delta_t_LS{}; //!< Table 7-20: leap seconds before new leap effective [s]
int32_t delta_t_LSF{}; //!< Table 7-20: leap seconds after new leap effective [s]
bool valid{};
};
#endif
+8 -7
View File
@@ -169,8 +169,8 @@ void Gnss_Ephemeris::satellitePosition(double transmitTime)
void Gnss_Ephemeris::satellitePosVelComputation(double transmitTime, std::array<double, 7>& pos_vel_dtr) const
{
// Restore semi-major axis
const double a = this->sqrtA * this->sqrtA;
// Restore semi-major axis (sqrtA for legacy NAV; A0 for B-CNAV1)
const double a = this->sqrtA * this->sqrtA + this->A0;
// Computed mean motion
double n0;
@@ -324,8 +324,9 @@ double Gnss_Ephemeris::check_t(double time) const
double Gnss_Ephemeris::sv_clock_relativistic_term(double transmitTime) const
{
// Restore semi-major axis
const double a = this->sqrtA * this->sqrtA;
// Restore semi-major axis (sqrtA for legacy NAV; A0 for B-CNAV1)
const double a = this->sqrtA * this->sqrtA + this->A0;
const double sqrtA_eff = std::sqrt(a);
// Time from ephemeris reference epoch
const double tk = check_t(transmitTime - this->toe);
@@ -372,15 +373,15 @@ double Gnss_Ephemeris::sv_clock_relativistic_term(double transmitTime) const
double dtr_;
if (this->System == 'E')
{
dtr_ = GALILEO_F * this->ecc * this->sqrtA * sek;
dtr_ = GALILEO_F * this->ecc * sqrtA_eff * sek;
}
else if (this->System == 'C')
{
dtr_ = BEIDOU_F * this->ecc * this->sqrtA * sek;
dtr_ = BEIDOU_F * this->ecc * sqrtA_eff * sek;
}
else
{
dtr_ = GPS_F * this->ecc * this->sqrtA * sek;
dtr_ = GPS_F * this->ecc * sqrtA_eff * sek;
}
return dtr_;
}
@@ -74,6 +74,7 @@ public:
double delta_n{}; //!< Mean motion difference from computed value [rad/sec]
double ecc{}; //!< Eccentricity
double sqrtA{}; //!< Square root of the semi-major axis [meters^1/2]
double A0{}; //!< Semi-major axis [meters] (B-CNAV1); unused when sqrtA is set
double OMEGA_0{}; //!< Longitude of ascending node of orbital plane at weekly epoch [rad]
double i_0{}; //!< Inclination angle at reference time [rad]
double omega{}; //!< Argument of perigee [rad]
@@ -57,6 +57,7 @@ const std::unordered_map<std::string, double> SIGNAL_FREQ_MAP = {
{"B1", FREQ1_BDS},
{"B2", FREQ2_BDS},
{"B3", FREQ3_BDS},
{"1D", FREQ1},
{"J1", FREQ1},
{"J5", FREQ5},
};
+2 -1
View File
@@ -83,7 +83,8 @@ constexpr uint32_t CODE_L9A = 52; //!< obs code: SA SPS (IRN)
constexpr uint32_t CODE_L9B = 53; //!< obs code: SB RS(D) (IRN)
constexpr uint32_t CODE_L9C = 54; //!< obs code: SC RS(P) (IRN)
constexpr uint32_t CODE_L9X = 55; //!< obs code: SB+C (IRN)
constexpr int32_t MAXCODE = 55; //!< max number of obs code
constexpr uint32_t CODE_L1D = 56; //!< obs code: B1C data (BDS)
constexpr int32_t MAXCODE = 56; //!< max number of obs code
/** \} */
+1 -1
View File
@@ -49,7 +49,7 @@ public:
* - GPS: "1C" (L1 C/A), "2S" (L2C), "L5"
* - GLONASS: "1G" (L1 C/A), "2G" (L2 C/A)
* - Galileo: "1B" (E1B), "5X" (E5a), "7X" (E5b), "6C" (E6C)
* - BeiDou: "B1" (B1I), "B3" (B3I)
* - BeiDou: "B1" (B1I), "B3" (B3I), "1D" (B1C)
*/
std::string get_signal_str() const;
+1
View File
@@ -34,6 +34,7 @@ enum signal_flag : uint32_t
GLO_2G = 0x1 << 8,
BDS_B1 = 0x1 << 9,
BDS_B3 = 0x1 << 10,
BDS_B1C = 0x1 << 13,
QZS_J1 = 0x1 << 11,
QZS_J5 = 0x1 << 12
};
+7
View File
@@ -161,6 +161,7 @@ private:
#include "unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_tong_acquisition_gsoc2013_test.cc"
#include "unit-tests/signal-processing-blocks/adapter/adapter_test.cc"
#include "unit-tests/signal-processing-blocks/adapter/pass_through_test.cc"
#include "unit-tests/signal-processing-blocks/libs/beidou_b1c_signal_replica_test.cc"
#include "unit-tests/signal-processing-blocks/libs/item_type_helpers_test.cc"
#include "unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc"
#include "unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc"
@@ -182,6 +183,10 @@ private:
#include "unit-tests/signal-processing-blocks/tracking/galileo_e5a_tracking_test.cc"
#include "unit-tests/signal-processing-blocks/tracking/galileo_e5b_dll_pll_tracking_test.cc"
#include "unit-tests/signal-processing-blocks/tracking/tracking_loop_filter_test.cc"
#include "unit-tests/system-parameters/beidou_b1c_pvt_helpers_test.cc"
#include "unit-tests/system-parameters/beidou_bdgim_test.cc"
#include "unit-tests/system-parameters/beidou_cnav1_ldpc_test.cc"
#include "unit-tests/system-parameters/beidou_cnav1_navigation_message_test.cc"
#include "unit-tests/system-parameters/beidou_dnav_navigation_message_test.cc"
#include "unit-tests/system-parameters/galileo_e1b_reed_solomon_test.cc"
#include "unit-tests/system-parameters/galileo_e6b_reed_solomon_test.cc"
@@ -235,6 +240,8 @@ private:
#if EXTRA_TESTS
#include "unit-tests/signal-processing-blocks/acquisition/acq_performance_test.cc"
// #include "unit-tests/signal-processing-blocks/acquisition/beidou_b1i_pcps_acquisition_test.cc"
#include "unit-tests/signal-processing-blocks/acquisition/beidou_b1c_pcps_acquisition_test.cc"
#include "unit-tests/signal-processing-blocks/tracking/beidou_b1c_dll_pll_veml_tracking_test.cc"
// #include "unit-tests/signal-processing-blocks/acquisition/beidou_b3i_pcps_acquisition_test.cc"
#ifndef EXCLUDE_TESTS_REQUIRING_BINARIES
#include "unit-tests/signal-processing-blocks/acquisition/glonass_l1_ca_pcps_acquisition_test.cc"
@@ -0,0 +1,31 @@
/*!
* \file beidou_b1c_pcps_acquisition_test.cc
* \brief Unit tests for BeiDou B1C PCPS acquisition via PcpsAcquisitionAdapter
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "acquisition_interface.h"
#include "gnss_block_factory.h"
#include "gnss_sdr_make_unique.h" // for std::make_unique in C++11
#include "in_memory_configuration.h"
#include <gtest/gtest.h>
#include <memory>
TEST(BeidouB1cPcpsAcquisitionTest, Instantiate)
{
auto configuration = std::make_shared<InMemoryConfiguration>();
configuration->set_property("Acquisition_1D.implementation", "BEIDOU_B1C_PCPS_Ambiguous_Acquisition");
configuration->set_property("Acquisition_1D.item_type", "gr_complex");
std::unique_ptr<AcquisitionInterface> acquisition = block_factory::GetAcqBlock(configuration.get(), "Acquisition_1D", 1, 0);
ASSERT_NE(acquisition, nullptr);
EXPECT_STREQ("BEIDOU_B1C_PCPS_Ambiguous_Acquisition", acquisition->implementation().c_str());
}
@@ -0,0 +1,106 @@
/*!
* \file beidou_b1c_signal_replica_test.cc
* \brief Unit tests for BeiDou B1C signal replica generation
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "Beidou_B1C.h"
#include "beidou_b1c_signal_replica.h"
#include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <complex>
#include <vector>
TEST(BeidouB1cSignalReplicaTest, SampledCodeLength)
{
constexpr int32_t fs = 2046000;
const auto samples_per_chip = static_cast<uint32_t>(std::lround(static_cast<double>(fs) / BEIDOU_B1C_CODE_RATE_CPS));
const auto expected_length = static_cast<size_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS * samples_per_chip);
std::vector<std::complex<float>> code(expected_length);
const std::array<char, 3> data_signal = {{'1', 'D', '\0'}};
beidou_b1c_code_gen_complex_sampled(code, data_signal, false, 1, fs, 0);
EXPECT_EQ(code.size(), expected_length);
}
TEST(BeidouB1cSignalReplicaTest, Prn1DataFirst24ChipsMatchIcd)
{
std::array<int32_t, 10230> chips{};
const std::array<char, 3> data_signal = {{'1', 'D', '\0'}};
beidou_b1c_code_gen_int(chips, data_signal, 1);
const char* expected = "101011111111011001001110";
for (int i = 0; i < 24; i++)
{
EXPECT_EQ(chips[i], expected[i] - '0') << "chip index " << i;
}
}
TEST(BeidouB1cSignalReplicaTest, SinBoc11AutocorrelationShape)
{
std::array<float, 20460> code{};
const std::array<char, 3> data_signal = {{'1', 'D', '\0'}};
beidou_b1c_code_gen_sinboc11_float(code, data_signal, 1);
double peak = 0.0;
double side = 0.0;
const int32_t samples_per_chip = 2;
const int32_t code_samples = static_cast<int32_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS) * samples_per_chip;
for (int32_t lag = -code_samples; lag <= code_samples; lag++)
{
double corr = 0.0;
for (int32_t i = 0; i < code_samples; i++)
{
const int32_t j = (i + lag + code_samples) % code_samples;
corr += static_cast<double>(code[i]) * static_cast<double>(code[j]);
}
corr /= static_cast<double>(code_samples);
if (lag == 0)
{
peak = std::abs(corr);
}
else if (std::abs(lag) == samples_per_chip)
{
side = std::max(side, std::abs(corr));
}
}
EXPECT_GT(peak, 0.0);
EXPECT_GT(side, 0.0);
EXPECT_LT(side, peak);
}
TEST(BeidouB1cSignalReplicaTest, QmbocPilotKeepsComponentsInQuadrature)
{
// ICD eqs. (4-10)/(4-11): Re=BOC(6,1), Im=BOC(1,1).
constexpr int32_t fs = 12 * 1023000;
const auto expected_length = static_cast<size_t>(BEIDOU_B1C_CODE_LENGTH_CHIPS * 12);
std::vector<std::complex<float>> code(expected_length);
const std::array<char, 3> pilot_signal = {{'1', 'P', '\0'}};
beidou_b1c_code_gen_complex_sampled(code, pilot_signal, true, 1, fs, 0);
double power_re = 0.0;
double power_im = 0.0;
double cross = 0.0;
for (const auto& s : code)
{
power_re += static_cast<double>(s.real()) * static_cast<double>(s.real());
power_im += static_cast<double>(s.imag()) * static_cast<double>(s.imag());
cross += static_cast<double>(s.real()) * static_cast<double>(s.imag());
}
ASSERT_GT(power_re, 0.0);
ASSERT_GT(power_im, 0.0);
EXPECT_NEAR(power_re / power_im, 4.0 / 29.0, 1.0e-3);
// Re and Im should be orthogonal.
EXPECT_NEAR(cross / static_cast<double>(code.size()), 0.0, 1.0e-3);
}
@@ -0,0 +1,32 @@
/*!
* \file beidou_b1c_dll_pll_veml_tracking_test.cc
* \brief Unit tests for BeiDou B1C DLL+PLL VEML tracking via DllPllTrackingAdapter
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "gnss_block_factory.h"
#include "gnss_sdr_make_unique.h" // for std::make_unique in C++11
#include "in_memory_configuration.h"
#include "tracking_interface.h"
#include <gtest/gtest.h>
#include <memory>
TEST(BeidouB1cDllPllVemlTrackingTest, Instantiate)
{
auto configuration = std::make_shared<InMemoryConfiguration>();
configuration->set_property("Tracking_1D.implementation", "BEIDOU_B1C_DLL_PLL_VEML_Tracking");
configuration->set_property("Tracking_1D.item_type", "gr_complex");
configuration->set_property("GNSS-SDR.internal_fs_sps", "2046000");
std::unique_ptr<TrackingInterface> tracking = block_factory::GetTrkBlock(configuration.get(), "Tracking_1D", 1, 1);
ASSERT_NE(tracking, nullptr);
EXPECT_STREQ("BEIDOU_B1C_DLL_PLL_VEML_Tracking", tracking->implementation().c_str());
}
@@ -0,0 +1,207 @@
/*!
* \file beidou_b1c_pvt_helpers_test.cc
* \brief Unit tests for B1C PVT helpers (TGD/ISC, seleph, band/wavelength map)
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "Beidou_CNAV1.h"
#include "MATH_CONSTANTS.h"
#include "gnss_frequencies.h"
#include "gnss_obs_codes.h"
#include "gnss_synchro.h"
#include "rtklib.h"
#include "rtklib_conversions.h"
#include "rtklib_ephemeris.h"
#include "rtklib_pntpos.h"
#include "rtklib_rtkcmn.h"
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
namespace
{
eph_t make_bds_eph(int sat, int code, double tgd0, double tgd2, gtime_t toe)
{
eph_t e{};
std::memset(&e, 0, sizeof(e));
e.sat = sat;
e.code = code;
e.A = 27906100.0;
e.e = 0.01;
e.toe = toe;
e.toc = toe;
e.toes = 0.0;
e.tgd[0] = tgd0;
e.tgd[2] = tgd2;
return e;
}
Gnss_Synchro make_bds_synchro(const char *signal, uint32_t prn, double pr_m)
{
Gnss_Synchro gs{};
gs.System = 'C';
gs.Signal[0] = signal[0];
gs.Signal[1] = signal[1];
gs.Signal[2] = '\0';
gs.PRN = prn;
gs.Pseudorange_m = pr_m;
gs.Carrier_Doppler_hz = 100.0;
gs.Carrier_phase_rads = 0.0;
gs.CN0_dB_hz = 40.0;
gs.Flag_valid_pseudorange = true;
return gs;
}
} // namespace
TEST(BeidouB1cPvtHelpersTest, GettgdAppliesIscForDataComponent)
{
const int sat = NSATGPS + NSATGLO + NSATGAL + NSATQZS + 19;
const double ep[] = {2020, 1, 1, 0, 0, 0};
const gtime_t t0 = epoch2time(ep);
std::vector<eph_t> ephs;
ephs.push_back(make_bds_eph(sat, BDS_EPH_SOURCE_CNAV1, 1.0e-9, 2.0e-9, t0));
nav_t nav;
std::memset(&nav, 0, sizeof(nav));
nav.eph = ephs.data();
nav.n = 1;
const double tgd_pilot = gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1P));
const double tgd_data = gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1D));
EXPECT_NEAR(tgd_pilot, SPEED_OF_LIGHT_M_S * 1.0e-9, 1.0e-6);
EXPECT_NEAR(tgd_data, SPEED_OF_LIGHT_M_S * 3.0e-9, 1.0e-6);
}
TEST(BeidouB1cPvtHelpersTest, SelephPrefersCnav1WhenRequested)
{
const int sat = NSATGPS + NSATGLO + NSATGAL + NSATQZS + 20;
const double ep[] = {2020, 6, 1, 12, 0, 0};
const gtime_t t0 = epoch2time(ep);
std::vector<eph_t> ephs;
ephs.push_back(make_bds_eph(sat, 1, 1.0e-9, 0.0, t0)); /* DNAV */
ephs.push_back(make_bds_eph(sat, BDS_EPH_SOURCE_CNAV1, 2.0e-9, 1.0e-9, t0)); /* CNAV1 */
nav_t nav;
std::memset(&nav, 0, sizeof(nav));
nav.eph = ephs.data();
nav.n = 2;
eph_t *e_cnav = seleph(t0, sat, -1, &nav, BDS_EPH_SOURCE_CNAV1);
eph_t *e_dnav = seleph(t0, sat, -1, &nav, 0);
ASSERT_NE(e_cnav, static_cast<eph_t *>(nullptr));
ASSERT_NE(e_dnav, static_cast<eph_t *>(nullptr));
EXPECT_EQ(e_cnav->code, BDS_EPH_SOURCE_CNAV1);
EXPECT_NE(e_dnav->code, BDS_EPH_SOURCE_CNAV1);
}
/* satwavelen BDS: frq0=B1I, frq1=B2, frq2=B3I */
TEST(BeidouB1cPvtHelpersTest, SatwavelenBdsKeepsOfficialB1B2B3Map)
{
const int sat = satno(SYS_BDS, 19);
nav_t nav{};
const double lam0 = satwavelen(sat, 0, &nav);
const double lam1 = satwavelen(sat, 1, &nav);
const double lam2 = satwavelen(sat, 2, &nav);
EXPECT_NEAR(lam0, SPEED_OF_LIGHT_M_S / FREQ1_BDS, 1.0e-9);
EXPECT_NEAR(lam1, SPEED_OF_LIGHT_M_S / FREQ2_BDS, 1.0e-9);
EXPECT_NEAR(lam2, SPEED_OF_LIGHT_M_S / FREQ3_BDS, 1.0e-9);
EXPECT_NE(lam1, SPEED_OF_LIGHT_M_S / FREQ1);
EXPECT_NE(lam0, SPEED_OF_LIGHT_M_S / FREQ1);
}
TEST(BeidouB1cPvtHelpersTest, SignalFreqMapB1cUsesFreq1)
{
ASSERT_NE(SIGNAL_FREQ_MAP.find("1D"), SIGNAL_FREQ_MAP.end());
ASSERT_NE(SIGNAL_FREQ_MAP.find("B1"), SIGNAL_FREQ_MAP.end());
EXPECT_DOUBLE_EQ(SIGNAL_FREQ_MAP.at("1D"), FREQ1);
EXPECT_DOUBLE_EQ(SIGNAL_FREQ_MAP.at("B1"), FREQ1_BDS);
EXPECT_NE(SIGNAL_FREQ_MAP.at("1D"), SIGNAL_FREQ_MAP.at("B1"));
}
/* B1I and B1C both use slot 0; distinguished by RINEX code. */
TEST(BeidouB1cPvtHelpersTest, InsertObsPlacesB1iAndB1cOnSlot0WithDistinctCodes)
{
constexpr int k_band = 0;
constexpr uint32_t prn = 19;
constexpr int week = 2100;
Gnss_Synchro b1i = make_bds_synchro("B1", prn, 2.2e7);
Gnss_Synchro b1c = make_bds_synchro("1D", prn, 2.1e7);
obsd_t obs_b1i{};
obs_b1i = insert_obs_to_rtklib(obs_b1i, b1i, week, k_band);
EXPECT_EQ(obs_b1i.code[k_band], static_cast<unsigned char>(CODE_L2I));
EXPECT_NEAR(obs_b1i.P[k_band], 2.2e7, 1.0e-3);
obsd_t obs_b1c{};
obs_b1c = insert_obs_to_rtklib(obs_b1c, b1c, week, k_band);
EXPECT_EQ(obs_b1c.code[k_band], static_cast<unsigned char>(CODE_L1P));
EXPECT_NEAR(obs_b1c.P[k_band], 2.1e7, 1.0e-3);
}
TEST(BeidouB1cPvtHelpersTest, GettgdPrefersMatchingEphTypeWhenBothPresent)
{
const int sat = NSATGPS + NSATGLO + NSATGAL + NSATQZS + 21;
const double ep[] = {2021, 3, 1, 0, 0, 0};
const gtime_t t0 = epoch2time(ep);
std::vector<eph_t> ephs;
ephs.push_back(make_bds_eph(sat, 1, 5.0e-9, 0.0, t0)); /* DNAV TGD1 */
ephs.push_back(make_bds_eph(sat, BDS_EPH_SOURCE_CNAV1, 1.0e-9, 2.0e-9, t0)); /* CNAV1 */
nav_t nav;
std::memset(&nav, 0, sizeof(nav));
nav.eph = ephs.data();
nav.n = 2;
const double tgd_b1c = gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1D));
const double tgd_b1i = gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L2I));
EXPECT_NEAR(tgd_b1c, SPEED_OF_LIGHT_M_S * 3.0e-9, 1.0e-6);
EXPECT_NEAR(tgd_b1i, SPEED_OF_LIGHT_M_S * 5.0e-9, 1.0e-6);
}
TEST(BeidouB1cPvtHelpersTest, GettgdReturnsZeroWhenOnlyWrongEphFamilyPresent)
{
const int sat = NSATGPS + NSATGLO + NSATGAL + NSATQZS + 22;
const double ep[] = {2021, 3, 1, 0, 0, 0};
const gtime_t t0 = epoch2time(ep);
std::vector<eph_t> ephs;
ephs.push_back(make_bds_eph(sat, 1, 5.0e-9, 0.0, t0)); /* DNAV only */
nav_t nav;
std::memset(&nav, 0, sizeof(nav));
nav.eph = ephs.data();
nav.n = 1;
EXPECT_DOUBLE_EQ(gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1D)), 0.0);
EXPECT_DOUBLE_EQ(gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1P)), 0.0);
}
/* CODE_L1P also means GPS L1P; CNAV1 filtering is SYS_BDS-only. */
TEST(BeidouB1cPvtHelpersTest, GettgdGpsIgnoresB1cCodeSelection)
{
const int sat = satno(SYS_GPS, 5);
const double ep[] = {2020, 1, 1, 0, 0, 0};
const gtime_t t0 = epoch2time(ep);
eph_t e{};
std::memset(&e, 0, sizeof(e));
e.sat = sat;
e.code = 0; /* LNAV, not CNAV1 */
e.tgd[0] = 4.0e-9;
e.toe = t0;
e.toc = t0;
nav_t nav;
std::memset(&nav, 0, sizeof(nav));
nav.eph = &e;
nav.n = 1;
EXPECT_NEAR(gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1C)), SPEED_OF_LIGHT_M_S * 4.0e-9, 1.0e-6);
EXPECT_NEAR(gettgd_bds_by_obs_code(sat, &nav, static_cast<unsigned char>(CODE_L1P)), SPEED_OF_LIGHT_M_S * 4.0e-9, 1.0e-6);
}
@@ -0,0 +1,69 @@
/*!
* \file beidou_bdgim_test.cc
* \brief Smoke tests for BDGIM ionospheric model
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "MATH_CONSTANTS.h"
#include "beidou_bdgim.h"
#include "gnss_frequencies.h"
#include <gtest/gtest.h>
#include <cmath>
TEST(BeidouBdgimTest, NonZeroDelayAtMidLatitude)
{
double alpha[9] = {10.0, 1.0, 0.5, -0.5, 0.2, 0.1, -0.1, 0.05, -0.05};
const double lat = 40.0 * D2R;
const double lon = 116.0 * D2R;
const double az = 45.0 * D2R;
const double el = 40.0 * D2R;
const double mjd = 58000.0;
const double delay = beidou_bdgim_delay_m(mjd, lat, lon, az, el, alpha, FREQ1);
EXPECT_TRUE(std::isfinite(delay));
EXPECT_NE(delay, 0.0);
}
TEST(BeidouBdgimTest, ScalesWithFrequencySquared)
{
double alpha[9] = {8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
const double lat = 30.0 * D2R;
const double lon = 120.0 * D2R;
const double az = 0.0;
const double el = 60.0 * D2R;
const double mjd = 59000.0;
const double d1 = beidou_bdgim_delay_m(mjd, lat, lon, az, el, alpha, FREQ1);
const double d_b1i = beidou_bdgim_delay_m(mjd, lat, lon, az, el, alpha, FREQ1_BDS);
ASSERT_NE(d1, 0.0);
const double ratio = d_b1i / d1;
const double expected = (FREQ1 * FREQ1) / (FREQ1_BDS * FREQ1_BDS);
EXPECT_NEAR(ratio, expected, 1.0e-6);
}
TEST(BeidouBdgimTest, ZeroElevationReturnsZero)
{
double alpha[9] = {5.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
const double delay = beidou_bdgim_delay_m(58000.0, 0.0, 0.0, 0.0, 0.0, alpha, FREQ1);
EXPECT_DOUBLE_EQ(delay, 0.0);
}
TEST(BeidouBdgimTest, NearestOddHourMjd)
{
constexpr double day = 58000.0;
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day + 1.0 / 24.0), day + 1.0 / 24.0);
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day + 1.4 / 24.0), day + 1.0 / 24.0);
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day + 2.6 / 24.0), day + 3.0 / 24.0);
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day + 23.0 / 24.0), day + 23.0 / 24.0);
// Equidistant midnight → 01:00 (std::round half away from zero).
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day), day + 1.0 / 24.0);
EXPECT_DOUBLE_EQ(beidou_bdgim_nearest_odd_hour_mjd(day - 0.1 / 24.0), day - 1.0 / 24.0);
}
@@ -0,0 +1,103 @@
/*!
* \file beidou_cnav1_ldpc_test.cc
* \brief Unit tests for B-CNAV1 NB-LDPC decoder
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "Beidou_CNAV1.h"
#include "beidou_cnav1_ldpc.h"
#include <gtest/gtest.h>
#include <array>
#include <cstring>
namespace
{
std::array<float, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> make_bit_llrs(const uint8_t* bits, float magnitude)
{
std::array<float, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> llr{};
for (int32_t i = 0; i < BEIDOU_CNAV1_SUBFRAME2_SYMBOLS; i++)
{
llr[static_cast<size_t>(i)] = (bits[i] != 0U) ? magnitude : -magnitude;
}
return llr;
}
std::array<float, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> make_sf3_bit_llrs(const uint8_t* bits, float magnitude)
{
std::array<float, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> llr{};
for (int32_t i = 0; i < BEIDOU_CNAV1_SUBFRAME3_SYMBOLS; i++)
{
llr[static_cast<size_t>(i)] = (bits[i] != 0U) ? magnitude : -magnitude;
}
return llr;
}
} // namespace
TEST(BeidouCnav1LdpcTest, DecodeZeroCodeword200_100)
{
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> bits{};
const auto llr = make_bit_llrs(bits.data(), 4.0F);
std::array<uint8_t, BEIDOU_CNAV1_SF2_DATA_BITS> info{};
EXPECT_TRUE(beidou_cnav1_ldpc_decode_200_100(llr.data(), BEIDOU_CNAV1_SUBFRAME2_SYMBOLS, info.data()));
for (const auto bit : info)
{
EXPECT_EQ(bit, 0U);
}
}
TEST(BeidouCnav1LdpcTest, DecodeZeroCodeword88_44)
{
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> bits{};
const auto llr = make_sf3_bit_llrs(bits.data(), 4.0F);
std::array<uint8_t, BEIDOU_CNAV1_SF3_DATA_BITS> info{};
EXPECT_TRUE(beidou_cnav1_ldpc_decode_88_44(llr.data(), BEIDOU_CNAV1_SUBFRAME3_SYMBOLS, info.data()));
for (const auto bit : info)
{
EXPECT_EQ(bit, 0U);
}
}
TEST(BeidouCnav1LdpcTest, CorrectSingleSymbolError200_100)
{
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME2_SYMBOLS> bits{};
const uint8_t wrong_symbol = 5U;
for (int32_t bit = 0; bit < 6; bit++)
{
bits[static_cast<size_t>(bit)] = static_cast<uint8_t>((wrong_symbol >> (5 - bit)) & 1U);
}
const auto llr = make_bit_llrs(bits.data(), 4.0F);
std::array<uint8_t, BEIDOU_CNAV1_SF2_DATA_BITS> info{};
EXPECT_TRUE(beidou_cnav1_ldpc_decode_200_100(llr.data(), BEIDOU_CNAV1_SUBFRAME2_SYMBOLS, info.data()));
for (const auto bit : info)
{
EXPECT_EQ(bit, 0U);
}
}
TEST(BeidouCnav1LdpcTest, CorrectSingleSymbolError88_44)
{
std::array<uint8_t, BEIDOU_CNAV1_SUBFRAME3_SYMBOLS> bits{};
const uint8_t wrong_symbol = 1U; // single GF(64) symbol error
for (int32_t bit = 0; bit < 6; bit++)
{
bits[static_cast<size_t>(bit)] = static_cast<uint8_t>((wrong_symbol >> (5 - bit)) & 1U);
}
const auto llr = make_sf3_bit_llrs(bits.data(), 4.0F);
std::array<uint8_t, BEIDOU_CNAV1_SF3_DATA_BITS> info{};
EXPECT_TRUE(beidou_cnav1_ldpc_decode_88_44(llr.data(), BEIDOU_CNAV1_SUBFRAME3_SYMBOLS, info.data()));
for (const auto bit : info)
{
EXPECT_EQ(bit, 0U);
}
}
@@ -0,0 +1,343 @@
/*!
* \file beidou_cnav1_navigation_message_test.cc
* \brief Unit tests for B-CNAV1 navigation message parser
* \author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* -----------------------------------------------------------------------------
*
* 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 "Beidou_CNAV1.h"
#include "beidou_cnav1_navigation_message.h"
#include "rtklib_conversions.h"
#include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <cstdint>
#include <string>
#include <vector>
namespace
{
// LFSR encoder matching beidou_cnav1_navigation_message.cc
std::vector<int8_t> encode_bch_lfsr_bipolar(
uint16_t msg_bits,
int32_t n,
int32_t k,
const std::vector<int32_t>& feedback_pos_1based)
{
std::vector<int8_t> register_state(static_cast<size_t>(k), 1);
std::vector<int8_t> encoded(static_cast<size_t>(n), 1);
for (int32_t i = 0; i < k; i++)
{
const auto bit = static_cast<int32_t>((msg_bits >> static_cast<uint32_t>(k - 1 - i)) & 1U);
register_state[static_cast<size_t>(k - 1 - i)] = (bit == 0) ? 1 : -1;
}
for (int32_t ind = 0; ind < n; ind++)
{
encoded[static_cast<size_t>(ind)] = register_state.back();
int8_t feedback = 1;
for (const int32_t pos : feedback_pos_1based)
{
feedback = static_cast<int8_t>(feedback * register_state[static_cast<size_t>(pos - 1)]);
}
for (int32_t j = k - 1; j > 0; j--)
{
register_state[static_cast<size_t>(j)] = register_state[static_cast<size_t>(j - 1)];
}
register_state[0] = feedback;
}
return encoded;
}
void append_bch_codeword(
std::vector<float>& frame,
uint16_t msg_bits,
int32_t n,
int32_t k,
const std::vector<int32_t>& feedback_pos_1based)
{
const auto encoded = encode_bch_lfsr_bipolar(msg_bits, n, k, feedback_pos_1based);
for (const auto cw : encoded)
{
// Map bipolar codeword to float symbols expected by the hard-decision BCH decoder.
frame.push_back(cw > 0 ? -1.0F : 1.0F);
}
}
void append_bch21_6(std::vector<float>& frame, uint32_t prn)
{
static const std::vector<int32_t> feedback_pos = {2, 4, 5, 6};
append_bch_codeword(frame, static_cast<uint16_t>(prn & 0x3FU), 21, 6, feedback_pos);
}
void append_bch51_8(std::vector<float>& frame, uint32_t soh)
{
static const std::vector<int32_t> feedback_pos = {1, 4, 5, 6, 7, 8};
append_bch_codeword(frame, static_cast<uint16_t>(soh & 0xFFU), 51, 8, feedback_pos);
}
void deinterleave_like_icd(const std::vector<float>& sf2_bits, const std::vector<float>& sf3_bits, std::vector<float>& interleaved)
{
std::array<float, BEIDOU_CNAV1_INTERLEAVE_ROWS * BEIDOU_CNAV1_INTERLEAVE_COLS> grid{};
int32_t sf2_idx = 0;
int32_t sf3_idx = 0;
int32_t row = 0;
while (row < 33)
{
for (int32_t r = 0; r < 2; r++)
{
for (int32_t col = 0; col < BEIDOU_CNAV1_INTERLEAVE_COLS; col++)
{
grid[static_cast<size_t>(row) * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = sf2_bits[static_cast<size_t>(sf2_idx++)];
}
row++;
}
for (int32_t col = 0; col < BEIDOU_CNAV1_INTERLEAVE_COLS; col++)
{
grid[static_cast<size_t>(row) * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = sf3_bits[static_cast<size_t>(sf3_idx++)];
}
row++;
}
for (int32_t r = 0; r < 3; r++)
{
for (int32_t col = 0; col < BEIDOU_CNAV1_INTERLEAVE_COLS; col++)
{
grid[static_cast<size_t>(row) * BEIDOU_CNAV1_INTERLEAVE_COLS + col] = sf2_bits[static_cast<size_t>(sf2_idx++)];
}
row++;
}
interleaved.clear();
for (int32_t col = 0; col < BEIDOU_CNAV1_INTERLEAVE_COLS; col++)
{
for (int32_t row_idx = 0; row_idx < BEIDOU_CNAV1_INTERLEAVE_ROWS; row_idx++)
{
interleaved.push_back(grid[static_cast<size_t>(row_idx) * BEIDOU_CNAV1_INTERLEAVE_COLS + col]);
}
}
}
std::vector<float> encode_bits_to_llr(const std::vector<uint8_t>& bits)
{
std::vector<float> llr;
llr.reserve(bits.size());
for (const auto bit : bits)
{
llr.push_back(bit != 0U ? 4.0F : -4.0F);
}
return llr;
}
//! Synthetic B-CNAV1 frame with all-zero SF2/SF3 payloads.
std::vector<float> build_valid_zero_payload_frame(uint32_t prn, uint32_t soh)
{
const auto sf2_llr = encode_bits_to_llr(std::vector<uint8_t>(BEIDOU_CNAV1_SUBFRAME2_SYMBOLS, 0U));
const auto sf3_llr = encode_bits_to_llr(std::vector<uint8_t>(BEIDOU_CNAV1_SUBFRAME3_SYMBOLS, 0U));
std::vector<float> interleaved;
deinterleave_like_icd(sf2_llr, sf3_llr, interleaved);
std::vector<float> frame;
append_bch21_6(frame, prn);
append_bch51_8(frame, soh);
frame.insert(frame.end(), interleaved.begin(), interleaved.end());
return frame;
}
//! Decode one frame from stream[end-(N-1) .. end] at start_offset (decoder window layout).
bool decode_from_sliding_window(
const std::vector<float>& stream,
int64_t end_index,
int32_t start_offset,
int32_t expected_prn)
{
const int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
if (end_index < n - 1)
{
return false;
}
const int64_t start = end_index - (n - 1);
std::vector<float> extracted(static_cast<size_t>(n));
const int32_t offset = ((start_offset % n) + n) % n;
for (int32_t i = 0; i < n; i++)
{
const int64_t abs_idx = start + ((offset + i) % n);
extracted[static_cast<size_t>(i)] = stream[static_cast<size_t>(abs_idx)];
}
Beidou_Cnav1_Navigation_Message nav;
return nav.decode_frame_symbols(extracted.data(), n, expected_prn);
}
} // namespace
TEST(BeidouCnav1NavigationMessageTest, DecodeSubframe1OnlyFrameFailsWithoutSf2Crc)
{
std::vector<float> frame;
append_bch21_6(frame, 19U);
append_bch51_8(frame, 1U);
// Non-constant SF2/SF3 noise (constant LLRs can decode as the all-zero codeword).
for (int32_t i = 0; i < BEIDOU_CNAV1_INTERLEAVED_SYMBOLS; i++)
{
frame.push_back(((i * 17 + 3) % 5 < 2) ? 4.0F : -4.0F);
}
Beidou_Cnav1_Navigation_Message nav;
EXPECT_FALSE(nav.decode_frame_symbols(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS));
}
TEST(BeidouCnav1NavigationMessageTest, DecodeEphemerisFromZeroSf2Payload)
{
const std::vector<float> frame = build_valid_zero_payload_frame(19U, 1U);
ASSERT_EQ(frame.size(), static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS));
Beidou_Cnav1_Navigation_Message nav;
ASSERT_TRUE(nav.decode_frame_symbols(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, 19));
EXPECT_TRUE(nav.have_new_ephemeris());
EXPECT_EQ(nav.get_ephemeris().PRN, 19);
EXPECT_DOUBLE_EQ(nav.get_tow_s(), 18.0);
// SF1 info (6+8) + SF2 data (600) + SF3 data (264) when both CRCs pass.
EXPECT_EQ(nav.get_last_nav_bits().size(), static_cast<size_t>(6 + 8 + BEIDOU_CNAV1_SF2_DATA_BITS + BEIDOU_CNAV1_SF3_DATA_BITS));
EXPECT_EQ(nav.get_last_nav_bits().substr(0, 6), std::string("010011")); // PRN 19
const auto rtklib_eph = eph_to_rtklib(nav.get_ephemeris());
EXPECT_EQ(rtklib_eph.iode, 0);
EXPECT_EQ(rtklib_eph.iodc, 0);
EXPECT_NEAR(rtklib_eph.A, BEIDOU_CNAV1_A_REF_MEO, 1.0);
EXPECT_EQ(rtklib_eph.code, BDS_EPH_SOURCE_CNAV1);
EXPECT_DOUBLE_EQ(rtklib_eph.Adot, nav.get_ephemeris().Adot);
EXPECT_DOUBLE_EQ(rtklib_eph.ndot, nav.get_ephemeris().delta_ndot);
}
TEST(BeidouCnav1NavigationMessageTest, SingleSymbolErrorIsCorrectedAnywhereInFrame)
{
// One wrong channel symbol (full-confidence sign flip) must be corrected by
// the BCH decoders (SF1) or the 64-ary LDPC decoders (SF2/SF3), and the
// decoded navigation bits must be identical to the clean-frame decode.
constexpr uint32_t prn = 19U;
const std::vector<float> clean_frame = build_valid_zero_payload_frame(prn, 1U);
ASSERT_EQ(clean_frame.size(), static_cast<size_t>(BEIDOU_CNAV1_FRAME_SYMBOLS));
Beidou_Cnav1_Navigation_Message reference_nav;
ASSERT_TRUE(reference_nav.decode_frame_symbols(clean_frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, static_cast<int32_t>(prn)));
const std::string reference_bits = reference_nav.get_last_nav_bits();
ASSERT_FALSE(reference_bits.empty());
// Positions covering the BCH(21,6) PRN block, the BCH(51,8) SOH block, and
// a stride across the interleaved LDPC region (SF2 and SF3 symbols mixed).
std::vector<int32_t> error_positions = {0, 10, 20, 21, 45, 71, 72, 1799};
for (int32_t pos = 100; pos < BEIDOU_CNAV1_FRAME_SYMBOLS; pos += 97)
{
error_positions.push_back(pos);
}
for (const int32_t error_position : error_positions)
{
std::vector<float> corrupted_frame(clean_frame);
corrupted_frame[static_cast<size_t>(error_position)] = -corrupted_frame[static_cast<size_t>(error_position)];
Beidou_Cnav1_Navigation_Message nav;
ASSERT_TRUE(nav.decode_frame_symbols(corrupted_frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, static_cast<int32_t>(prn)))
<< "decode failed with a single symbol error at position " << error_position;
EXPECT_TRUE(nav.have_new_ephemeris())
<< "no ephemeris with a single symbol error at position " << error_position;
EXPECT_EQ(nav.get_last_nav_bits(), reference_bits)
<< "decoded bits differ from clean frame with error at position " << error_position;
}
}
TEST(BeidouCnav1NavigationMessageTest, MultiFrameSteadyStateKeepsConstantSohOffset)
{
// Nonzero start_offset must stay constant across frame boundaries.
constexpr uint32_t prn = 19U;
constexpr int32_t matched_offset = 37;
constexpr int32_t num_frames = 12; // > CRC_ERROR_LIMIT (8)
constexpr int32_t n = BEIDOU_CNAV1_FRAME_SYMBOLS;
const std::vector<float> frame = build_valid_zero_payload_frame(prn, 1U);
ASSERT_EQ(frame.size(), static_cast<size_t>(n));
ASSERT_TRUE(Beidou_Cnav1_Navigation_Message{}.decode_frame_symbols(
frame.data(), n, static_cast<int32_t>(prn)));
// stream[t] = frame[(t - matched_offset) mod N]
const int64_t total_symbols = static_cast<int64_t>(num_frames) * n;
std::vector<float> stream(static_cast<size_t>(total_symbols));
for (int64_t t = 0; t < total_symbols; t++)
{
const int32_t idx = static_cast<int32_t>(
((t - matched_offset) % n + n) % n);
stream[static_cast<size_t>(t)] = frame[static_cast<size_t>(idx)];
}
for (int32_t f = 1; f <= num_frames; f++)
{
const int64_t end_index = static_cast<int64_t>(f) * n - 1;
EXPECT_TRUE(decode_from_sliding_window(stream, end_index, matched_offset, static_cast<int32_t>(prn)))
<< "constant-offset decode failed at frame " << f;
}
}
TEST(BeidouCnav1NavigationMessageTest, RejectsEphemerisWhenIodeIodcMismatch)
{
// SF2 with IODC/IODE mismatch (ICD §7.4.3).
std::vector<uint8_t> sf2_bits(static_cast<size_t>(BEIDOU_CNAV1_SUBFRAME2_SYMBOLS), 0U);
const int32_t iodc_offset = 13 + 8;
const uint32_t iodc = 0x101U; // low 8 = 0x01
for (int32_t b = 0; b < 10; b++)
{
sf2_bits[static_cast<size_t>(iodc_offset + b)] = static_cast<uint8_t>((iodc >> static_cast<uint32_t>(9 - b)) & 1U);
}
const int32_t iode_offset = iodc_offset + 10;
const uint32_t iode = 0x02U;
for (int32_t b = 0; b < 8; b++)
{
sf2_bits[static_cast<size_t>(iode_offset + b)] = static_cast<uint8_t>((iode >> static_cast<uint32_t>(7 - b)) & 1U);
}
const int32_t sat_type_offset = iode_offset + 8 + 11; // MEO
sf2_bits[static_cast<size_t>(sat_type_offset)] = 1U;
sf2_bits[static_cast<size_t>(sat_type_offset + 1)] = 1U;
auto crc24q_bits = [](const uint8_t* bits, int32_t num_bits) -> uint32_t {
uint32_t crc = 0;
const uint32_t POLY = 0x864CFBU;
for (int32_t i = 0; i < num_bits; i++)
{
uint32_t msb = (crc >> 23) & 1U;
uint32_t bit = (bits[i] != 0U) ? 1U : 0U;
crc = (crc << 1) & 0xFFFFFFU;
if (msb ^ bit)
{
crc ^= POLY;
}
}
return crc;
};
const uint32_t crc = crc24q_bits(sf2_bits.data(), BEIDOU_CNAV1_SF2_DATA_BITS - BEIDOU_CNAV1_CRC_BITS);
for (int32_t b = 0; b < BEIDOU_CNAV1_CRC_BITS; b++)
{
sf2_bits[static_cast<size_t>(BEIDOU_CNAV1_SF2_DATA_BITS - BEIDOU_CNAV1_CRC_BITS + b)] =
static_cast<uint8_t>((crc >> static_cast<uint32_t>(23 - b)) & 1U);
}
const auto sf2_llr = encode_bits_to_llr(sf2_bits);
const auto sf3_llr = encode_bits_to_llr(std::vector<uint8_t>(BEIDOU_CNAV1_SUBFRAME3_SYMBOLS, 0U));
std::vector<float> interleaved;
deinterleave_like_icd(sf2_llr, sf3_llr, interleaved);
std::vector<float> frame;
append_bch21_6(frame, 19U);
append_bch51_8(frame, 1U);
frame.insert(frame.end(), interleaved.begin(), interleaved.end());
Beidou_Cnav1_Navigation_Message nav;
ASSERT_TRUE(nav.decode_frame_symbols(frame.data(), BEIDOU_CNAV1_FRAME_SYMBOLS, 19));
EXPECT_FALSE(nav.have_new_ephemeris());
}
+30
View File
@@ -0,0 +1,30 @@
<!-- prettier-ignore-start -->
[comment]: # (
SPDX-License-Identifier: GPL-3.0-or-later
)
[comment]: # (
SPDX-FileCopyrightText: 2026 Carles Fernandez-Prades <carles.fernandez@cttc.es>
)
<!-- prettier-ignore-end -->
# Code Generation Utilities
The scripts in this folder regenerate source files that are checked into the
repository. They are not needed to build or run GNSS-SDR; they only need to be
re-run when their inputs change, and the regenerated files must be committed.
## generate_beidou_b1c_codes.py
Generates `src/core/system_parameters/Beidou_B1C_codes.h` and
`Beidou_B1C_codes.cc` (the precomputed BeiDou B1C primary and secondary
spreading codes) from the ICD Weil-code parameters declared in
`src/core/system_parameters/Beidou_B1C_prn.h`. Run it from any directory:
```
python3 utils/code_generation/generate_beidou_b1c_codes.py
```
The script overwrites both output files in place and prints the paths it wrote.
The generated files carry an "Auto-generated. Do not edit manually" notice; edit
`Beidou_B1C_prn.h` (or this script) instead and regenerate.
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""
Generate Beidou_B1C_codes.h/.cc from ICD Weil parameters in Beidou_B1C_prn.h.
-----------------------------------------------------------------------------
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
Author: Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
-----------------------------------------------------------------------------
"""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
PRN_HEADER = ROOT / "src/core/system_parameters/Beidou_B1C_prn.h"
OUT_HEADER = ROOT / "src/core/system_parameters/Beidou_B1C_codes.h"
OUT_SOURCE = ROOT / "src/core/system_parameters/Beidou_B1C_codes.cc"
def extract(name: str, text: str) -> list[int]:
match = re.search(rf"{name}\[63\]\s*=\s*\{{([^}}]+)\}}", text, re.S)
if not match:
raise RuntimeError(f"Could not parse {name}")
return [int(x.strip()) for x in match.group(1).split(",") if x.strip()]
def legendre(n: int) -> list[int]:
seq = [0] * n
for x in range(1, n):
seq[(x * x) % n] = 1
return seq
def weil_code(L: list[int], weil_len: int, w: int, p: int, out_len: int) -> list[int]:
W = [L[k] ^ L[(k + w) % weil_len] for k in range(weil_len)]
return [W[(n + p - 1) % weil_len] for n in range(out_len)]
def chips_to_hex(chips: list[int]) -> str:
out = ""
for i in range(0, len(chips), 4):
chunk = chips[i : i + 4]
value = 0
for bit in chunk:
value = (value << 1) | bit
while len(chunk) < 4:
value <<= 1
chunk.append(0)
out += format(value, "X")
return out
def chips_to_binstr(chips: list[int]) -> str:
return "".join(str(c) for c in chips)
# Keep generated source under the CI long-line limit (512 chars).
# Match the historical 76-char fragment width used in Beidou_B1C_codes.h.
_CODE_LINE_WIDTH = 76
def emit_array_decl(name: str, rows: list[str]) -> str:
return f"extern const char {name}[63][{len(rows[0]) + 1}];"
def emit_wrapped_string(row: str) -> list[str]:
"""Emit one array element as adjacent string literals, one chunk per line."""
chunks = [row[i : i + _CODE_LINE_WIDTH] for i in range(0, len(row), _CODE_LINE_WIDTH)]
if not chunks:
chunks = [""]
out: list[str] = []
for idx, chunk in enumerate(chunks):
suffix = "," if idx == len(chunks) - 1 else ""
out.append(f' "{chunk}"{suffix}')
return out
def emit_array_def(name: str, rows: list[str]) -> str:
lines = [f"const char {name}[63][{len(rows[0]) + 1}] = {{"]
for row in rows:
lines.extend(emit_wrapped_string(row))
lines.append("};")
return "\n".join(lines)
def main() -> None:
text = PRN_HEADER.read_text(encoding="utf-8")
data_w = extract("BEIDOU_B1C_DATA_PRN_W", text)
data_p = extract("BEIDOU_B1C_DATA_PRN_P", text)
pilot_w = extract("BEIDOU_B1C_PILOT_PRN_W", text)
pilot_p = extract("BEIDOU_B1C_PILOT_PRN_P", text)
sec_w = extract("BEIDOU_B1C_PILOT_SECONDARY_W", text)
sec_p = extract("BEIDOU_B1C_PILOT_SECONDARY_P", text)
L10243 = legendre(10243)
L3607 = legendre(3607)
data_hex = [
chips_to_hex(weil_code(L10243, 10243, data_w[i], data_p[i], 10230)) for i in range(63)
]
pilot_hex = [
chips_to_hex(weil_code(L10243, 10243, pilot_w[i], pilot_p[i], 10230)) for i in range(63)
]
sec_bin = [chips_to_binstr(weil_code(L3607, 3607, sec_w[i], sec_p[i], 1800)) for i in range(63)]
expected = "101011111111011001001110"
assert "".join(str(b) for b in weil_code(L10243, 10243, data_w[0], data_p[0], 10230)[:24]) == expected
script_name = Path(__file__).name
header = f"""/*!
* \\file Beidou_B1C_codes.h
* \\brief Precomputed BeiDou B1C primary and secondary codes (declarations)
* \\author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* Auto-generated by {script_name}. Do not edit manually.
* Definitions live in Beidou_B1C_codes.cc.
*
* -----------------------------------------------------------------------------
*
* 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_BEIDOU_B1C_CODES_H
#define GNSS_SDR_BEIDOU_B1C_CODES_H
#include <cstddef>
constexpr size_t BEIDOU_B1C_PRIMARY_CODE_STR_LENGTH = 2558;
constexpr size_t BEIDOU_B1C_PILOT_SECONDARY_CODE_STR_LENGTH = 1800;
{emit_array_decl("BEIDOU_B1C_DATA_PRIMARY_CODE", data_hex)}
{emit_array_decl("BEIDOU_B1C_PILOT_PRIMARY_CODE", pilot_hex)}
{emit_array_decl("BEIDOU_B1C_PILOT_SECONDARY_CODE", sec_bin)}
#endif
"""
source = f"""/*!
* \\file Beidou_B1C_codes.cc
* \\brief Precomputed BeiDou B1C primary and secondary codes
* \\author Wenhao Ou, 2026. ouwh(at)mail2.sysu.edu.cn
*
* Auto-generated by {script_name}. Do not edit manually.
*
* -----------------------------------------------------------------------------
*
* 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 "Beidou_B1C_codes.h"
{emit_array_def("BEIDOU_B1C_DATA_PRIMARY_CODE", data_hex)}
{emit_array_def("BEIDOU_B1C_PILOT_PRIMARY_CODE", pilot_hex)}
{emit_array_def("BEIDOU_B1C_PILOT_SECONDARY_CODE", sec_bin)}
"""
OUT_HEADER.write_text(header, encoding="utf-8")
OUT_SOURCE.write_text(source, encoding="utf-8")
print(f"Wrote {OUT_HEADER} ({len(header)} bytes)")
print(f"Wrote {OUT_SOURCE} ({len(source)} bytes)")
if __name__ == "__main__":
main()