fix(panadapter): prime spectrum widget dBm range on wire to prevent flat secondary pans on reconnect#3034
Conversation
…lat secondary pans on reconnect (aethersdr#3029) Root cause (two compounding issues): 1. SpectrumWidget default / radio model mismatch at wire time SpectrumWidget initialises with refLevel = -50 dBm, dynamicRange = 100 dB (i.e. display window −150 → −50 dBm). PanadapterModel already carries the correct saved range (default −130 → −40 dBm, or whatever the radio last reported). wirePanadapter() and the panadapterAdded reconnect path both skipped priming the widget from the model, so every non-primary pan (Slice B through H — up to 8 slices total) started in the wrong state. 2. Noise-floor auto-adjust racing the first levelChanged echo If DisplayNoiseFloorEnable is on for any non-primary pan, the auto-adjust begins animating refLevel toward its target as soon as the first FFT frames arrive — before the radio has had time to echo back its saved min_dbm / max_dbm. Because it starts from −50 dBm instead of the saved top-of-range (e.g. −40 dBm), the computed target is wildly wrong (e.g. +5.41 dBm with noise floor at −80 dBm and position 95%). sendNoiseFloorRangeCommand() emits dbmRangeChangeRequested with those bogus values. wirePanadapter()'s dbmRangeChangeRequested handler sets pendingDbm->active = true, calls setStreamDbmRange(bogus, waitForEcho=true) — poisoning PanadapterStream::m_dbmRanges so the VITA-49 decoder normalises incoming FFT bins to the wrong window — and fires "display pan set 0xXXXXXXXX min_dbm=<bogus> max_dbm=<bogus>" to the radio. Once the radio starts encoding FFT data for that window, all real HF signals fall far below the display floor, producing the flat/dead panadapter. The pendingDbm guard then re-asserts the bogus range whenever the radio tries to echo the correct values, creating a self-reinforcing lock-out. The primary pan (Slice A) was not affected because it is wired first and its dBm command arrives from a different code path before the auto-adjust fires. Every subsequent pan — Slices B through H — had no such early command and fell into the race whenever noise-floor tracking was enabled. The symptom was intermittent ("hard to reproduce consistently") because the race window is narrow: it only manifests if the noise-floor auto-adjust fires before the radio's pan-status echo populates levelChanged. Switching bands fixed it by sending a fresh display pan set min_dbm/max_dbm command, breaking the stale pendingDbm lock. Fix: call sw->setDbmRange(pan->minDbm(), pan->maxDbm()) immediately after wiring the levelChanged signal in both code paths: • wirePanadapter() — covers every new applet at initial connection • panadapterAdded() "existing applet" branch — covers every pan on reconnect Both paths apply universally to all panadapters regardless of count (1–8). The prime aligns the widget's refLevel/dynamicRange with the radio model's values before any spectrum data arrives, so the noise-floor auto-adjust starts from the correct baseline and produces sensible targets rather than values derived from the stale constructor defaults. If a stale m_pendingDbmRangeEcho survives from a dropped session (edge case: connection lost mid-command), SpectrumWidget::setDbmRange's 2-second timeout check at line 2149 clears it and applies the prime on the first call, so no additional reset is required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@AetherClaude your thoughts on this? |
|
The diagnosis looks correct after walking the code. Confirmed:
The Proposed fix — add one line in each of the two sites, right after the // src/gui/MainWindow.cpp — wirePanadapter(), after the connect at ~11337-11359
connect(pan, &PanadapterModel::levelChanged,
sw, [sw, pendingDbm, dbmMatches, setStreamDbmRange](float minDbm, float maxDbm) {
// ...existing guard body...
sw->setDbmRange(minDbm, maxDbm);
});
// Prime widget from the model so noise-floor auto-adjust starts from the
// correct refLevel/dynamicRange instead of SpectrumWidget's -50/100 defaults
// (#3034). Without this, secondary pans can lock into a bogus dBm range
// via the pendingDbm guard on reconnect.
sw->setDbmRange(pan->minDbm(), pan->maxDbm());// src/gui/MainWindow.cpp — panadapterAdded existing-applet branch, after ~2816-2822
connect(pan, &PanadapterModel::levelChanged,
sw, [sw](float minDbm, float maxDbm) {
if (sw->isDraggingDbmScale()) {
return;
}
sw->setDbmRange(minDbm, maxDbm);
});
// Prime widget from the model on reconnect — see #3034.
sw->setDbmRange(pan->minDbm(), pan->maxDbm());Two small notes worth checking when implementing:
A maintainer can authorize the orchestrator to land this fix by adding the 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 aethersdr-agent · cost: $1.6706 · model: claude-opus-4-7 |
|
@AetherClaude Both notes verified against the implementation: Note 1 — ordering: The prime at line 11372 lands 121 lines before the Note 2 — no surprising side-effects: |
|
Merged at 06:22 UTC — thanks @chibondking, great diagnosis and fix. Filed #3047 as a small follow-up for the |
… comments (#3047). Principle XI. PR #3034 introduced two comment blocks in src/gui/MainWindow.cpp tagged (#3029). That number refers to an unrelated FlexControl wheel-tuning PR that merged the same night; the meaningful reference for the panadapter dBm-range prime fix is #3034 itself. Correct both citations so code archaeology lands on the actual fix PR. Comment-only change; no behavior, no symbols touched.
… comments (#3047). Principle XI. (#3048) ## Summary Fixes #3047 ### What was changed cleanup(panadapter): fix stale #3029 references to #3034 in dBm-prime comments (#3047). Principle XI. ### Files modified - `src/gui/MainWindow.cpp` ``` src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
…lat secondary pans on reconnect (aethersdr#3034) ## Summary - **Bug**: Secondary panadapters (Slices B–H, up to 8 total) occasionally go completely flat after reconnecting to the radio — showing no signal while Slice A works normally. Switching bands restores them, and the issue is intermittent. - **Root cause**: Two compounding issues: 1. `SpectrumWidget` initialises with `refLevel = -50 dBm, dynamicRange = 100 dB` but `PanadapterModel` already carries the correct saved range (default −130 → −40 dBm). Neither `wirePanadapter()` nor the `panadapterAdded` reconnect path primed the widget from the model, so every non-primary pan started with a stale widget state. 2. If `DisplayNoiseFloorEnable` is on for any secondary pan, the noise-floor auto-adjust begins animating `refLevel` toward its target from the wrong starting point (−50 instead of −40 dBm) before the radio echoes back its saved `min_dbm`/`max_dbm`. This produces bogus `dbmRangeChangeRequested` values (e.g. −84.59 → +5.41 dBm). The `pendingDbm` guard in `wirePanadapter` then poisons `PanadapterStream::m_dbmRanges` with those values and sends them to the radio, causing the VITA-49 decoder to normalise FFT bins to a window far above the actual HF noise floor. All real signals fall off the bottom of the display. The guard re-asserts the bogus range on every subsequent `levelChanged` echo, creating a self-reinforcing lock-out. - **Slice A immunity**: Slice A is wired first; by the time its noise-floor auto-adjust fires, a dBm command from a separate code path has already arrived and set the correct range. ## Fix Prime `sw->setDbmRange(pan->minDbm(), pan->maxDbm())` immediately after wiring `levelChanged` in both affected code paths (`MainWindow.cpp`): - `wirePanadapter()` — every new applet at initial connection - `panadapterAdded()` "existing applet" branch — every pan on reconnect Both sites apply to all panadapters (1–8) unconditionally. ## Test plan - [ ] Connect to radio with two or more panadapters and noise-floor auto-adjust enabled on all pans — confirm all pans display spectrum data after connect - [ ] Disconnect and reconnect 5–10 times — confirm no secondary pan goes flat - [ ] Switch bands while connected — confirm dBm range adjusts correctly on all pans - [ ] Connect with a single panadapter — confirm no regression - [ ] Verify noise-floor auto-adjust still tracks and adjusts the display correctly on all pans after connect 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#3034 in dBm-prime comments (aethersdr#3047). Principle XI. (aethersdr#3048) ## Summary Fixes aethersdr#3047 ### What was changed cleanup(panadapter): fix stale aethersdr#3029 references to aethersdr#3034 in dBm-prime comments (aethersdr#3047). Principle XI. ### Files modified - `src/gui/MainWindow.cpp` ``` src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
Summary
SpectrumWidgetinitialises withrefLevel = -50 dBm, dynamicRange = 100 dBbutPanadapterModelalready carries the correct saved range (default −130 → −40 dBm). NeitherwirePanadapter()nor thepanadapterAddedreconnect path primed the widget from the model, so every non-primary pan started with a stale widget state.DisplayNoiseFloorEnableis on for any secondary pan, the noise-floor auto-adjust begins animatingrefLeveltoward its target from the wrong starting point (−50 instead of −40 dBm) before the radio echoes back its savedmin_dbm/max_dbm. This produces bogusdbmRangeChangeRequestedvalues (e.g. −84.59 → +5.41 dBm). ThependingDbmguard inwirePanadapterthen poisonsPanadapterStream::m_dbmRangeswith those values and sends them to the radio, causing the VITA-49 decoder to normalise FFT bins to a window far above the actual HF noise floor. All real signals fall off the bottom of the display. The guard re-asserts the bogus range on every subsequentlevelChangedecho, creating a self-reinforcing lock-out.Fix
Prime
sw->setDbmRange(pan->minDbm(), pan->maxDbm())immediately after wiringlevelChangedin both affected code paths (MainWindow.cpp):wirePanadapter()— every new applet at initial connectionpanadapterAdded()"existing applet" branch — every pan on reconnectBoth sites apply to all panadapters (1–8) unconditionally.
Test plan
🤖 Generated with Claude Code