A deadlock may occur when ChannelFsm receives Event_stop_channel when
the non-blocking acquisition is in progress.
The control flow will be the following:
ChannelFsm::Event_stop_channel locks the mutex mx_
ChannelFsm::Event_stop_channel calls stop_acquisition
ChannelFsm::stop_acquisition calls PcpsAcquisitionAdapter::stop_acquisition
PcpsAcquisitionAdapter::stop_acquisition calls pcps_acquisition::set_active
pcps_acquisition::set_active calls pcps_acquisition::wait_if_active
pcps_acquisition::wait_if_active blocks waiting for the background
acquisition thread to finish processing
pcps_acquisition::acquisition_core calls pcps_acquisition::handle_threshold_reached
pcps_acquisition::handle_threshold_reached calls pcps_acquisition::send_positive_acquisition
pcps_acquisition::send_positive_acquisition calls ChannelFsm::Event_valid_acquisition
ChannelFsm::Event_valid_acquisition tries to lock the mutex mx_ for one
more time
Deadlock.
This commit does three things:
- removes "magic numbers" used to mark different channel states as "magic numbers"
made the first attempt to fix the deadlock fail in a really strange way.
- Implements a new state FSM_STATE_TERMINATING, used to prevent
unintended effects during FSM transition from tracking/acquisition to
FSM_STATE_IDLE.
- Unlocks the mutex mx_ during actual attempt to stop
tracking/acquisition.
Signed-off-by: Vladislav P <vladisslav2011@gmail.com>
Set result.positive_acq to true before sending positive acquisition.
This makes positive acquisitions being logged as positive instead of
negative.
Signed-off-by: Vladislav P <vladisslav2011@gmail.com>
TrackedSatelliteInfo.used only says whether a tracked satellite/signal
contributed to the fix, not why it didn't -- below PVT.elevation_mask,
RAIM FDE, and broadcast-unhealthy are all indistinguishable to a monitor
client. Add a `healthy` field (proto field 8, default true) alongside it.
GPS health comes from the decoded ephemeris's SV_health, falling back to
the almanac (broadcast by every satellite) when no ephemeris has been
decoded yet. Galileo health is resolved per-signal via the existing
get_galileo_signal_health(), with the same almanac fallback when no
ephemeris is available for that signal's service. Other systems default
to healthy=true.
Galileo_Ephemeris_Store::combined_view() overwrote a preferred source's
merged entry with the raw ephemeris from that source, discarding the
health/DVS fields the preferred source's message type structurally never
carries (F/NAV never decodes E1B_HS/E1B_DVS/E5b_HS/E5b_DVS, I/NAV never
decodes E5a_HS/E5a_DVS). Those fields default to 0 ("OK"), so an E1+E5a
configuration preferring F/NAV would silently present an I/NAV-flagged
unhealthy satellite as healthy. Now the merge fills in only the fields the
preferred source can't carry, from the secondary source's entry.
select_galileo_ephemeris()'s Reduced CED bootstrap fallback had no health
check at all: Galileo_Reduced_CED carries no health/DVS bits of its own, so
an unhealthy satellite with no full ephemeris decoded yet would still be
accepted via Reduced CED. Cross-check the almanac's E1B_HS instead, which
every other satellite broadcasts as part of the normal almanac rotation.
log_time_to_fix_breakdown() and its six supporting state variables
were left-over instrumentation from developing the frequency-error-
reduction feature, not part of its documented behavior -- strip
before this is mergeable.
and reloading it from the XML assistance files, which could replace a
fresher, live-decoded almanac with a stale snapshot from disk, and the
handler now uses the visible-satellite list it computed instead of
prioritizing an empty one.
The Doppler wipeoff replicas were generated with
volk_gnsssdr_s32f_sincos_32fc, which accumulates the phase in single
precision over the whole vector without wrapping it. With a large
frequency offset, such as the GLONASS FDMA channel bias (several rad per
sample), the accumulated phase reaches 1e5 rad over a coherent block and
its float resolution drops to 0.01-0.06 rad, so the effective frequency
of the local carrier was off by hundreds of Hz to kHz, changing at every
power-of-two boundary of the accumulator.
In GLONASS L1/L2 C/A receivers this corrupted the Doppler grid: the
noise estimate taken from the opposite Doppler row no longer represented
the grid, satellites absent from the signal passed the pfa threshold on
every dwell (tracked for ~11 s at ~30 dB-Hz with no carrier lock, then
dropped and re-acquired, periodically), and the acquisition Doppler of
the satellites actually present was biased by up to one bin, which could
push the tracking loop into a false lock.
Generate the carrier with a complex rotator instead, renormalized every
512 samples. The residual frequency error is below 0.1 Hz.
Validate i_alm_satellite_slot_number before trying to access gnav_almanac
elements by index in Glonass_Gnav_Navigation_Message::string_decoder.
Signed-off-by: Vladislav P <vladisslav2011@gmail.com>
Visibility-aware acquisition search (GNSS-SDR.enable_visibility_aware_search):
- Classify GLONASS and QZSS satellites too. GLONASS positions come from the
GNAV almanac (new Glonass_Gnav_Almanac::satellite_position with the
standard perturbation model), so the search order benefits before any
GLONASS ephemeris is decoded.
- PVT publishes a lock-free navigation snapshot (ephemeris, almanac, UTC
models per system) that the classifier reads instead of locking the PVT
maps on every recompute.
- Recompute on new navigation data, on position changes larger than
visibility_recompute_position_threshold_m, every
visibility_recompute_interval_s, and when the almanac ages out; fix the
periodic and position-triggered recomputes that never fired, and the
off-by-one that excluded satellites in [mask, mask+1) degrees.
- Enter the start-up standby only when the feature is enabled, so the
default search order is untouched.
Secondary-frequency acquisition assistance:
- Do not start an acquisition on the PRN 0 placeholder left on assisted
channels at connect time; it threw inside the control thread and left the
flowgraph running headless with the secondary band never acquired.
- Match GLONASS assistance by FDMA frequency channel: the search pool keeps
one slot per channel while tracked L1 channels report the decoded slot, so
L2 was never picked.
- Assist from locked channels before their telemetry is decoded: the
observables status now reports tracking-only channels and the flowgraph
keeps a separate tracking map for the assisted search.
Follow-ups to the frequency-error-reduction merge (tracking state 5):
- Anchor Flag_PLL_180_deg_phase_locked to the sign of the last Prompt
symbol. The differential bit/secondary-code detector reconstructs bits
relative to an arbitrary first bit, so the previous corr_value sign rule
was independent of the actual carrier polarity.
- Do not feed the deliberate Doppler bin hops of the scan into the
high_dyn carrier/code phase-rate smoothers: clear the histories and
zero the rate steps while scanning.
- Start the pull_in_time_s and bit_synchronization_time_limit_s budgets
at the first sample after the scan instead of at acquisition, so long
scans no longer consume the FLL pull-in or the sync time limit. Scan
completion re-arms pull-in and resets the lock-fail counters. The
non-scan path keeps the acquisition time origin.
- Clamp f_error_accumulation to at least 1, both when parsed from the
configuration and in the block constructor, to avoid an out-of-bounds
write and a scan that never advances.
- Add unit tests for polarity, rate suppression, timers, and a full L2C
scan, and document the new options in the CHANGELOG.
compute_visible_satellites() correctly decides visible-vs-excluded
using the raw double elevation (`El > elevation_mask_deg`), but then
truncates the returned elevation to floor(El) for the diagnostic dump.
Tick() later re-derives the same visible/excluded decision from that
already-floored value (twice: once to separate a health-only exclusion
from an elevation one, once for the final visible_/excluded_ split).
For a satellite whose true elevation lands in [mask, mask+1) --
e.g. El=15.63 against a 15 deg mask -- floor(El) collapses to exactly
`mask`, and the strict `>` re-check then wrongly excludes a satellite
that was already correctly accepted by the untruncated comparison.
Verified live: G11 at El=15.63 (15 deg mask) stayed stuck in EXCLUDED
across a full recompute instead of promoting to VISIBLE.
Both re-derivations need `>=` instead of `>` to correctly compensate
for the floor() truncation bias (proof in the added comments); this is
exact for every fractional elevation except one landing exactly on the
integer mask value, a measure-zero case for a continuous real angle.
last_recompute_rx_time_s_ and last_recompute_r_eb_e_ were being reset
on every Tick() that passed the early-return gate, including a
data_changed-only (targeted) recompute -- not just on an actual full
sweep, despite both being documented as tracking the last *full*
recompute. With a dozen+ satellites actively updating their ephemeris/
almanac, data_changed fires often enough to keep resetting both clocks
before visibility_recompute_interval_s (or the position-displacement
threshold) is ever reached, silently starving the periodic full
recompute -- the one mechanism that can catch a satellite rising above
the horizon with no ephemeris/almanac change of its own to trigger a
targeted recompute.
Move both assignments inside the needs_full_recompute block so they
only advance when a full sweep actually just ran.