From bf8df18ba59bfcb1437f4a0f53dbe6b587a05efe Mon Sep 17 00:00:00 2001 From: joebre Date: Mon, 28 Sep 2026 09:11:02 +0200 Subject: [PATCH 1/2] Load GPS assistance XML for L5/L2C-only configurations too GPS/QZSS UTC model, ionosphere and almanac data are broadcast system-wide parameters, not specific to the L1 C/A signal -- L5 and L2C channels rely on the exact same almanac (via the PRN-keyed map PVT and the visibility/ Doppler-assist logic read from) for visibility classification and Doppler prediction just as much as L1 C/A does. read_assistance_from_XML() gated the whole GPS ephemeris/UTC/iono/almanac block on Channels_1C.count/Channels_J1.count alone. A configuration running only L5 and/or L2C channels (no L1 C/A) never even attempted to load its almanac, UTC or iono XML files -- not because those files were missing or invalid, but because the code never asked for them, unconditionally reporting "Error reading XML files" / "Disabling GNSS assistance" even with perfectly valid assistance data on disk. Broaden the gate to also fire when Channels_2S.count (L2C) or Channels_L5.count is > 0, matching the same OR-together-all-constellation- signals pattern already used for the Galileo block just below it. The L1 C/A LNAV ephemeris load stays in this same block; L5/L2C use their own separately-gated CNAV ephemeris further down, and requesting the LNAV ephemeris is harmless when no L1 C/A channel exists to use it. --- src/core/receiver/control_thread.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/receiver/control_thread.cc b/src/core/receiver/control_thread.cc index 1388a79e3..f036d2bb3 100644 --- a/src/core/receiver/control_thread.cc +++ b/src/core/receiver/control_thread.cc @@ -534,7 +534,18 @@ bool ControlThread::read_assistance_from_XML() std::cout << "Trying to read GNSS ephemeris from XML file(s)...\n"; - if ((configuration_->property("Channels_1C.count", 0) > 0) || (configuration_->property("Channels_J1.count", 0) > 0)) + // GPS/QZSS UTC, iono and almanac data are broadcast system-wide parameters, not + // specific to the L1 C/A signal -- L5/L2C channels rely on the exact same almanac + // (via the PRN-keyed map PVT and the visibility/Doppler-assist logic read from) for + // visibility classification and Doppler prediction just as much as L1 C/A does. Gating + // this whole block on Channels_1C/Channels_J1 alone meant an L5-or-L2C-only + // configuration never even attempted to load its almanac, UTC or iono files -- not + // because those files were missing or invalid, but because the code never asked for + // them. The L1 C/A LNAV ephemeris load stays inside this same block (L5/L2C use their + // own separately-gated CNAV ephemeris below); requesting it is harmless when no L1C + // channel exists to use it. + if ((configuration_->property("Channels_1C.count", 0) > 0) || (configuration_->property("Channels_J1.count", 0) > 0) || + (configuration_->property("Channels_2S.count", 0) > 0) || (configuration_->property("Channels_L5.count", 0) > 0)) { if (supl_client_ephemeris_.load_ephemeris_xml(eph_xml_filename) == true) { From 7050b485641e5045c2762211f13edbd225bb6908 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Mon, 28 Sep 2026 23:44:52 +0200 Subject: [PATCH 2/2] Add loading of QZSS L5 assistance --- src/core/receiver/control_thread.cc | 31 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/core/receiver/control_thread.cc b/src/core/receiver/control_thread.cc index f036d2bb3..230059459 100644 --- a/src/core/receiver/control_thread.cc +++ b/src/core/receiver/control_thread.cc @@ -535,17 +535,21 @@ bool ControlThread::read_assistance_from_XML() std::cout << "Trying to read GNSS ephemeris from XML file(s)...\n"; // GPS/QZSS UTC, iono and almanac data are broadcast system-wide parameters, not - // specific to the L1 C/A signal -- L5/L2C channels rely on the exact same almanac + // specific to the L1 C/A signal -- L5/L2C/J5 channels rely on the exact same almanac // (via the PRN-keyed map PVT and the visibility/Doppler-assist logic read from) for - // visibility classification and Doppler prediction just as much as L1 C/A does. Gating - // this whole block on Channels_1C/Channels_J1 alone meant an L5-or-L2C-only - // configuration never even attempted to load its almanac, UTC or iono files -- not - // because those files were missing or invalid, but because the code never asked for - // them. The L1 C/A LNAV ephemeris load stays inside this same block (L5/L2C use their - // own separately-gated CNAV ephemeris below); requesting it is harmless when no L1C + // visibility classification and Doppler prediction just as much as L1 C/A does. QZSS + // LNAV ephemerides and almanacs are stored in these same maps under their QZSS PRNs, + // and the L5 CNAV decoder does not provide almanacs, so for a J5-only configuration + // these XML files are the only almanac source. Gating this whole block on + // Channels_1C/Channels_J1 alone meant an L5, L2C or J5-only configuration never even + // attempted to load its almanac, UTC or iono files -- not because those files were + // missing or invalid, but because the code never asked for them. The L1 C/A LNAV + // ephemeris load stays inside this same block (L5/L2C/J5 use their own + // separately-gated CNAV ephemeris below); requesting it is harmless when no L1 C/A // channel exists to use it. if ((configuration_->property("Channels_1C.count", 0) > 0) || (configuration_->property("Channels_J1.count", 0) > 0) || - (configuration_->property("Channels_2S.count", 0) > 0) || (configuration_->property("Channels_L5.count", 0) > 0)) + (configuration_->property("Channels_2S.count", 0) > 0) || (configuration_->property("Channels_L5.count", 0) > 0) || + (configuration_->property("Channels_J5.count", 0) > 0)) { if (supl_client_ephemeris_.load_ephemeris_xml(eph_xml_filename) == true) { @@ -585,7 +589,8 @@ bool ControlThread::read_assistance_from_XML() gps_alm_iter != supl_client_ephemeris_.gps_almanac_map.cend(); gps_alm_iter++) { - std::cout << "From XML file: Read GPS almanac for satellite " << Gnss_Satellite("GPS", gps_alm_iter->second.PRN) << '\n'; + const std::string system = (gps_alm_iter->second.PRN >= MINPRNQZS && gps_alm_iter->second.PRN <= MAXPRNQZS) ? "QZSS" : "GPS"; + std::cout << "From XML file: Read " << system << " almanac for satellite " << Gnss_Satellite(system, gps_alm_iter->second.PRN) << '\n'; const std::shared_ptr tmp_obj = std::make_shared(gps_alm_iter->second); tmp_obj->from_startup_load = true; flowgraph_->send_telemetry_msg(pmt::make_any(tmp_obj)); @@ -659,7 +664,10 @@ bool ControlThread::read_assistance_from_XML() } } - if ((configuration_->property("Channels_2S.count", 0) > 0) || (configuration_->property("Channels_L5.count", 0) > 0)) + // QZSS L5 CNAV ephemerides are stored in the same PRN-keyed CNAV map (and saved to + // the same XML file) as the GPS ones, so J5 channels need this block too. + if ((configuration_->property("Channels_2S.count", 0) > 0) || (configuration_->property("Channels_L5.count", 0) > 0) || + (configuration_->property("Channels_J5.count", 0) > 0)) { if (supl_client_ephemeris_.load_cnav_ephemeris_xml(eph_cnav_xml_filename) == true) { @@ -668,7 +676,8 @@ bool ControlThread::read_assistance_from_XML() gps_cnav_eph_iter != supl_client_ephemeris_.gps_cnav_ephemeris_map.cend(); gps_cnav_eph_iter++) { - std::cout << "From XML file: Read CNAV ephemeris for satellite " << Gnss_Satellite("GPS", gps_cnav_eph_iter->second.PRN) << '\n'; + const std::string system = (gps_cnav_eph_iter->second.PRN >= MINPRNQZS && gps_cnav_eph_iter->second.PRN <= MAXPRNQZS) ? "QZSS" : "GPS"; + std::cout << "From XML file: Read CNAV ephemeris for satellite " << Gnss_Satellite(system, gps_cnav_eph_iter->second.PRN) << '\n'; const std::shared_ptr tmp_obj = std::make_shared(gps_cnav_eph_iter->second); flowgraph_->send_telemetry_msg(pmt::make_any(tmp_obj)); }