From 07fb80e13240f5fa9a3ecbac2748c706c0aa20c7 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Fri, 17 Jul 2026 21:29:38 +0200 Subject: [PATCH] Fix GGTO handling (see ICD 5.1.8) --- src/algorithms/PVT/libs/rinex_printer.cc | 10 ++- .../galileo_telemetry_decoder_gs.cc | 7 +- .../system_parameters/galileo_inav_message.cc | 7 ++ .../system_parameters/galileo_inav_message.h | 3 +- .../system_parameters/galileo_utc_model.h | 24 +++++- .../galileo_fnav_inav_decoder_test.cc | 78 +++++++++++++++++-- 6 files changed, 115 insertions(+), 14 deletions(-) diff --git a/src/algorithms/PVT/libs/rinex_printer.cc b/src/algorithms/PVT/libs/rinex_printer.cc index 92fc7f0e3..c068e9947 100644 --- a/src/algorithms/PVT/libs/rinex_printer.cc +++ b/src/algorithms/PVT/libs/rinex_printer.cc @@ -2393,7 +2393,10 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver, { iono_lines.emplace_back(get_galileo_iono_alpha_line(pvt_solver->galileo_iono)); time_corr_lines.emplace_back(get_galileo_time_corr_line(pvt_solver->galileo_utc_model)); - time_corr_lines.emplace_back(get_gps_to_galileo_time_corr_line(pvt_solver->galileo_utc_model)); + if (pvt_solver->galileo_utc_model.flag_GGTO) + { + time_corr_lines.emplace_back(get_gps_to_galileo_time_corr_line(pvt_solver->galileo_utc_model)); + } if (system_time_str == "GAL") { @@ -2507,7 +2510,10 @@ void Rinex_Printer::print_rinex_annotation(const Rtklib_Solver* pvt_solver, { nav_header_info.emplace_back("GAL", "IONOSPHERIC CORR", get_galileo_iono_alpha_line(pvt_solver->galileo_iono)); nav_header_info.emplace_back("GAUT", "TIME SYSTEM CORR", get_galileo_time_corr_line(pvt_solver->galileo_utc_model)); - nav_header_info.emplace_back("GPGA", "TIME SYSTEM CORR", get_gps_to_galileo_time_corr_line(pvt_solver->galileo_utc_model)); + if (pvt_solver->galileo_utc_model.flag_GGTO) + { + nav_header_info.emplace_back("GPGA", "TIME SYSTEM CORR", get_gps_to_galileo_time_corr_line(pvt_solver->galileo_utc_model)); + } d_rinex_header_galileo_updated = true; if (system_time_str == "GAL") diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc index 49de9bf0d..0feacc0a7 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc @@ -649,8 +649,11 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in << " dB-Hz" << TEXT_RESET << std::endl; } - d_delta_t = tmp_obj->A_0G + tmp_obj->A_1G * (static_cast(d_TOW_at_current_symbol_ms) / 1000.0 - tmp_obj->t_0G + 604800 * (std::fmod(static_cast(d_inav_nav.get_Galileo_week() - tmp_obj->WN_0G), 64.0))); - DLOG(INFO) << "delta_t=" << d_delta_t << "[s]"; + if (tmp_obj->flag_GGTO) + { + d_delta_t = tmp_obj->A_0G + tmp_obj->A_1G * (static_cast(d_TOW_at_current_symbol_ms) / 1000.0 - tmp_obj->t_0G + 604800 * (std::fmod(static_cast(d_inav_nav.get_Galileo_week() - tmp_obj->WN_0G), 64.0))); + DLOG(INFO) << "delta_t=" << d_delta_t << "[s]"; + } } if (d_inav_nav.have_new_almanac() == true) // flag_almanac_4 tells if W10 available. diff --git a/src/core/system_parameters/galileo_inav_message.cc b/src/core/system_parameters/galileo_inav_message.cc index 75829dd45..2ca50c6c7 100644 --- a/src/core/system_parameters/galileo_inav_message.cc +++ b/src/core/system_parameters/galileo_inav_message.cc @@ -524,6 +524,7 @@ Galileo_Utc_Model Galileo_Inav_Message::get_utc_model() const utc_model.A_1G = A_1G_10; utc_model.t_0G = t_0G_10; utc_model.WN_0G = WN_0G_10; + utc_model.flag_GGTO = get_flag_GGTO(); return utc_model; } @@ -1243,6 +1244,12 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) WN_0G_10 = static_cast(read_navigation_unsigned(data_jk_bits, WN_0_G_10_BIT)); flag_GGTO_4 = true; DLOG(INFO) << "WN_0G_10= " << WN_0G_10; + // OS SIS ICD: GGTO is unavailable only when all four raw fields + // are simultaneously encoded as all ones. + flag_GGTO_valid = !(read_navigation_unsigned(data_jk_bits, A_0_G_10_BIT) == 0xFFFFU && + read_navigation_unsigned(data_jk_bits, A_1_G_10_BIT) == 0x0FFFU && + read_navigation_unsigned(data_jk_bits, T_0_G_10_BIT) == 0x00FFU && + read_navigation_unsigned(data_jk_bits, WN_0_G_10_BIT) == 0x003FU); flag_almanac_4 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; nav_bits_word_10 = data_jk_bits.to_string().substr(86, 42); diff --git a/src/core/system_parameters/galileo_inav_message.h b/src/core/system_parameters/galileo_inav_message.h index 10dc9e871..71d234ce2 100644 --- a/src/core/system_parameters/galileo_inav_message.h +++ b/src/core/system_parameters/galileo_inav_message.h @@ -230,7 +230,7 @@ public: inline bool get_flag_GGTO() const { - return (flag_GGTO_1 == true && flag_GGTO_2 == true && flag_GGTO_3 == true && flag_GGTO_4 == true); + return (flag_GGTO_1 == true && flag_GGTO_2 == true && flag_GGTO_3 == true && flag_GGTO_4 == true && flag_GGTO_valid == true); } inline double get_A0G() const @@ -499,6 +499,7 @@ private: bool flag_GGTO_2{}; bool flag_GGTO_3{}; bool flag_GGTO_4{}; + bool flag_GGTO_valid{}; bool flag_CED{}; bool enable_rs{}; diff --git a/src/core/system_parameters/galileo_utc_model.h b/src/core/system_parameters/galileo_utc_model.h index adb941daa..0c3dae3c2 100644 --- a/src/core/system_parameters/galileo_utc_model.h +++ b/src/core/system_parameters/galileo_utc_model.h @@ -20,6 +20,7 @@ #define GNSS_SDR_GALILEO_UTC_MODEL_H #include +#include #include /** \addtogroup Core @@ -61,6 +62,7 @@ public: int32_t WN_0G{}; bool flag_utc_model{}; + bool flag_GGTO{}; template @@ -70,9 +72,6 @@ public: */ inline void serialize(Archive& archive, const unsigned int version) { - if (version) - { - }; archive& BOOST_SERIALIZATION_NVP(A0); archive& BOOST_SERIALIZATION_NVP(A1); archive& BOOST_SERIALIZATION_NVP(Delta_tLS); @@ -82,9 +81,28 @@ public: archive& BOOST_SERIALIZATION_NVP(DN); archive& BOOST_SERIALIZATION_NVP(Delta_tLSF); archive& BOOST_SERIALIZATION_NVP(flag_utc_model); + if (version > 0) + { + archive& BOOST_SERIALIZATION_NVP(A_0G); + archive& BOOST_SERIALIZATION_NVP(A_1G); + archive& BOOST_SERIALIZATION_NVP(t_0G); + archive& BOOST_SERIALIZATION_NVP(WN_0G); + archive& BOOST_SERIALIZATION_NVP(flag_GGTO); + } + else + { + // Version 0 archives predate GGTO persistence. + A_0G = 0.0; + A_1G = 0.0; + t_0G = 0; + WN_0G = 0; + flag_GGTO = false; + } } }; +BOOST_CLASS_VERSION(Galileo_Utc_Model, 1) + /** \} */ /** \} */ diff --git a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/galileo_fnav_inav_decoder_test.cc b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/galileo_fnav_inav_decoder_test.cc index fabcd8053..fcb637908 100644 --- a/tests/unit-tests/signal-processing-blocks/telemetry_decoder/galileo_fnav_inav_decoder_test.cc +++ b/tests/unit-tests/signal-processing-blocks/telemetry_decoder/galileo_fnav_inav_decoder_test.cc @@ -70,22 +70,19 @@ uint32_t compute_galileo_inav_crc(const std::string& crc_data) } -std::pair build_inav_page(uint8_t word_type, char page_type, const std::string& osnma_sis) +std::pair build_inav_page_from_data(const std::string& data_jk, char page_type, const std::string& osnma_sis) { - std::string data_k = to_bit_string(word_type, 6); - data_k.append(106, '0'); - std::string even_page; even_page.reserve(114); even_page.push_back('0'); even_page.push_back(page_type); - even_page += data_k; + even_page += data_jk.substr(0, 112); std::string odd_page_without_crc; odd_page_without_crc.reserve(82); odd_page_without_crc.push_back('1'); odd_page_without_crc.push_back(page_type); - odd_page_without_crc.append(16, '0'); + odd_page_without_crc += data_jk.substr(112, 16); odd_page_without_crc += osnma_sis; odd_page_without_crc.append(22, '0'); // SAR odd_page_without_crc.append(2, '0'); // Spare @@ -101,6 +98,33 @@ std::pair build_inav_page(uint8_t word_type, char page } +std::pair build_inav_page(uint8_t word_type, char page_type, const std::string& osnma_sis) +{ + std::string data_jk = to_bit_string(word_type, 6); + data_jk.append(122, '0'); + return build_inav_page_from_data(data_jk, page_type, osnma_sis); +} + + +void set_inav_field(std::string& data_jk, const std::vector>& field, uint32_t value) +{ + const auto& range = field.front(); + data_jk.replace(static_cast(range.first - 1), static_cast(range.second), to_bit_string(value, static_cast(range.second))); +} + + +std::string build_ggto_word(uint32_t a0g, uint32_t a1g, uint32_t t0g, uint32_t wn0g) +{ + std::string data_jk(128, '0'); + set_inav_field(data_jk, PAGE_TYPE_BIT, 10U); + set_inav_field(data_jk, A_0_G_10_BIT, a0g); + set_inav_field(data_jk, A_1_G_10_BIT, a1g); + set_inav_field(data_jk, T_0_G_10_BIT, t0g); + set_inav_field(data_jk, WN_0_G_10_BIT, wn0g); + return data_jk; +} + + std::string build_osnma_sis(uint8_t hkroot, uint32_t mack) { return to_bit_string(hkroot, 8) + to_bit_string(mack, 32); @@ -115,6 +139,14 @@ void feed_inav_page(Galileo_Inav_Message& decoder, uint8_t word_type, char page_ } +void feed_inav_data_page(Galileo_Inav_Message& decoder, const std::string& data_jk) +{ + const auto page = build_inav_page_from_data(data_jk, '0', std::string(40, '0')); + decoder.split_page(page.first, 0); + decoder.split_page(page.second, 1); +} + + OSNMA_msg decode_osnma_page(uint8_t word_type, char page_type, const std::string& osnma_sis) { Galileo_Inav_Message decoder; @@ -124,6 +156,40 @@ OSNMA_msg decode_osnma_page(uint8_t word_type, char page_type, const std::string } // namespace +TEST(Galileo_INAV_Message_Test, GgtoAllOnesEncodingIsUnavailable) +{ + Galileo_Inav_Message decoder; + feed_inav_data_page(decoder, build_ggto_word(0xFFFFU, 0x0FFFU, 0x00FFU, 0x003FU)); + + ASSERT_TRUE(decoder.get_flag_CRC_test()); + EXPECT_FALSE(decoder.get_flag_GGTO()); + EXPECT_FALSE(decoder.get_utc_model().flag_GGTO); +} + + +TEST(Galileo_INAV_Message_Test, GgtoUnavailableRequiresAllFourAllOnesFields) +{ + const std::array, 4> available_encodings{{ + {{0xFFFEU, 0x0FFFU, 0x00FFU, 0x003FU}}, + {{0xFFFFU, 0x0FFEU, 0x00FFU, 0x003FU}}, + {{0xFFFFU, 0x0FFFU, 0x00FEU, 0x003FU}}, + {{0xFFFFU, 0x0FFFU, 0x00FFU, 0x003EU}}, + }}; + + for (size_t i = 0; i < available_encodings.size(); ++i) + { + SCOPED_TRACE(i); + Galileo_Inav_Message decoder; + const auto& fields = available_encodings[i]; + feed_inav_data_page(decoder, build_ggto_word(fields[0], fields[1], fields[2], fields[3])); + + ASSERT_TRUE(decoder.get_flag_CRC_test()); + EXPECT_TRUE(decoder.get_flag_GGTO()); + EXPECT_TRUE(decoder.get_utc_model().flag_GGTO); + } +} + + class Galileo_FNAV_INAV_test : public ::testing::Test { public: