Make the Monitor report channels without a valid time reference

The Monitor block is fed from the Observables output, which only
produced data for channels with a decoded time of week. Galileo E6
channels cannot obtain the TOW from HAS pages alone, so E6-only
configurations never produced any Monitor output. When
Monitor.enable_monitor=true, the Observables block now emits every
epoch, filling channels without valid observables with their latest
tracking data (C/N0, Doppler, carrier phase), flagged as invalid so
that the PVT engine and other consumers keep ignoring them.

Fixes #1018
This commit is contained in:
Carles Fernandez
2026-07-03 21:24:17 +02:00
parent b898bcad28
commit ed5a75e31e
5 changed files with 41 additions and 1 deletions
+7
View File
@@ -109,6 +109,13 @@ All notable changes to GNSS-SDR will be documented in this file.
Their generation can be disabled by setting `Observables.enable_E6=false`.
This setting is now independent of `PVT.use_e6_for_pvt`, which keeps
controlling whether E6 observables are used in the PVT solution.
- The Monitor (`Monitor.enable_monitor=true`) now also reports channels that are
tracking a signal but do not have a valid time reference yet, filling their
entries with the latest raw tracking data (C/N0, Doppler, carrier phase) while
keeping their observable validity flags unset. This makes the Monitor usable
in Galileo E6-only configurations, where the time of week cannot be obtained
from HAS pages, as well as during the initial seconds of operation, before the
telemetry decoders attain synchronization.
- Reworked the Python plotting utilities under `utils/python` (acquisition,
tracking, telemetry, observables, and PVT diagnostics). Each script now
exposes a command-line interface (run with `--help`) and can be executed from
@@ -50,6 +50,7 @@ HybridObservables::HybridObservables(const ConfigurationInterface* configuration
conf.enable_carrier_smoothing = configuration->property(role + ".enable_carrier_smoothing", conf.enable_carrier_smoothing);
conf.always_output_gs = configuration->property("PVT.an_output_enabled", conf.always_output_gs) || configuration->property(role + ".always_output_gs", conf.always_output_gs);
conf.enable_E6 = configuration->property(role + ".enable_E6", conf.enable_E6);
conf.enable_monitor = configuration->property("Monitor.enable_monitor", conf.enable_monitor);
#if USE_GLOG_AND_GFLAGS
if (FLAGS_carrier_smoothing_factor == DEFAULT_CARRIER_SMOOTHING_FACTOR)
@@ -106,6 +106,7 @@ hybrid_observables_gs::hybrid_observables_gs(const Obs_Conf &conf_)
d_channel_last_pseudorange_smooth = std::vector<double>(d_nchannels_out, 0.0);
d_channel_last_carrier_phase_rads = std::vector<double>(d_nchannels_out, 0.0);
d_channel_last_rx_time_valid = std::vector<bool>(d_nchannels_out, false);
d_last_trk_data = std::vector<Gnss_Synchro>(d_nchannels_out);
d_SourceTagTimestamps = std::vector<std::queue<GnssTime>>(d_nchannels_out);
@@ -512,6 +513,19 @@ void hybrid_observables_gs::forecast(int noutput_items __attribute__((unused)),
}
bool hybrid_observables_gs::has_fresh_trk_data(uint32_t ch, uint64_t rx_clock) const
{
const Gnss_Synchro &trk = d_last_trk_data[ch];
if (trk.fs <= 0)
{
return false;
}
// the interpolation epoch lags the latest tracking data, so a negative age is expected
const auto age_samples = static_cast<int64_t>(rx_clock) - static_cast<int64_t>(trk.Tracking_sample_counter);
return age_samples < trk.fs; // tracking data not older than one second
}
void hybrid_observables_gs::update_TOW(const std::vector<Gnss_Synchro> &data)
{
// 1. Set the TOW using the latest TOW in the observables.
@@ -888,6 +902,12 @@ int hybrid_observables_gs::general_work(int noutput_items __attribute__((unused)
// ************ end time tags **************
for (int32_t m = 0; m < ninput_items[n]; m++)
{
if (d_conf.enable_monitor)
{
// Keep the latest tracking data so that channels without a
// valid time reference can be reported through the Monitor
d_last_trk_data[n] = in[n][m];
}
// Push the valid tracking Gnss_Synchros to their corresponding deque
if (in[n][m].Flag_valid_word)
{
@@ -919,6 +939,7 @@ int hybrid_observables_gs::general_work(int noutput_items __attribute__((unused)
{
std::vector<Gnss_Synchro> epoch_data(d_nchannels_out);
int32_t n_valid = 0;
int32_t n_trk_only = 0;
for (uint32_t n = 0; n < d_nchannels_out; n++)
{
Gnss_Synchro interpolated_gnss_synchro{};
@@ -926,6 +947,14 @@ int hybrid_observables_gs::general_work(int noutput_items __attribute__((unused)
{
// Produce an empty observation
interpolated_gnss_synchro = Gnss_Synchro();
if (d_conf.enable_monitor && has_fresh_trk_data(n, d_Rx_clock_buffer.front()))
{
// Report the latest tracking data (e.g., C/N0, Doppler) for
// channels lacking a valid time reference, such as Galileo E6
// when no I/NAV or F/NAV channel provides the time of week
interpolated_gnss_synchro = d_last_trk_data[n];
n_trk_only++;
}
interpolated_gnss_synchro.Flag_valid_pseudorange = false;
interpolated_gnss_synchro.Flag_valid_word = false;
interpolated_gnss_synchro.Flag_valid_acquisition = false;
@@ -1017,7 +1046,7 @@ int hybrid_observables_gs::general_work(int noutput_items __attribute__((unused)
}
}
if (n_valid > 0)
if (n_valid > 0 || n_trk_only > 0)
{
// LOG(INFO) << "OBS: diff time: " << out[0][0].RX_time * 1000.0 - old_time_debug;
// old_time_debug = out[0][0].RX_time * 1000.0;
@@ -74,6 +74,7 @@ private:
void msg_handler_pvt_to_observables(const pmt::pmt_t& msg);
double compute_T_rx_s(const Gnss_Synchro& a) const;
bool interp_trk_obs(Gnss_Synchro& interpolated_obs, uint32_t ch, uint64_t rx_clock) const;
bool has_fresh_trk_data(uint32_t ch, uint64_t rx_clock) const;
void update_TOW(const std::vector<Gnss_Synchro>& data);
void compute_pranges(std::vector<Gnss_Synchro>& data) const;
void smooth_pseudoranges(std::vector<Gnss_Synchro>& data);
@@ -101,6 +102,7 @@ private:
std::vector<double> d_channel_last_pseudorange_smooth;
std::vector<double> d_channel_last_carrier_phase_rads;
std::vector<bool> d_channel_last_rx_time_valid;
std::vector<Gnss_Synchro> d_last_trk_data; // latest tracking data per channel, for the Monitor
std::string d_dump_filename;
@@ -42,6 +42,7 @@ public:
bool dump{false};
bool dump_mat{false};
bool enable_E6{true};
bool enable_monitor{false};
};
/** \} */