fix: RADE deactivated by mode change on unrelated slice in multi-pan setup#2026
Conversation
There was a problem hiding this comment.
Nice work tracking down all three bugs, @NF0T — the root cause analysis in the PR description is excellent, and the fixes are well-scoped.
Review
All three fixes look correct:
-
VfoWidget guard (
if (m_radeActive)) — matches the existing pattern on the quick-mode buttons. Clean fix. -
MainWindow sliceId guard (
sliceId == m_radeSliceId) — correctly applied to both handler lambdas (RxApplet at ~L2270, VfoWidget at ~L9049). This is the key defense against cross-slice teardown. -
spectrum()→spectrumForSlice()— correct in bothactivateRADEanddeactivateRADE. The null-check on the return value is already in place viaif (auto* sw = ...), so this is safe even if the slice's pan is somehow missing.
No issues found with: conventions (AppSettings used correctly), null safety, resource management, or scope creep — changes are strictly within the stated scope.
One observation (non-blocking)
RxApplet::radeActivated at RxApplet.cpp:396 has the same unconditional emit radeActivated(false, ...) pattern that VfoWidget had before this PR:
// RxApplet.cpp ~L396
emit radeActivated(false, m_slice ? m_slice->sliceId() : -1);This is now harmless because the MainWindow handler (Bug 2 fix) will reject it when sliceId != m_radeSliceId. But for consistency with the VfoWidget fix, you may want to add the same if (m_radeActive) guard in RxApplet's combo handler too. Not a blocker — the MainWindow guard is sufficient protection — but it would eliminate the spurious signal at the source.
Overall this is a clean, minimal fix. Thanks for catching this and for the thorough multi-pan testing on real hardware.
…etup Two bugs found via user testing (FLEX-8400 v4.1.5, two-pan RADE+SSB layout): Bug 1: Switching Slice B (Pan B) to DIGU silently deactivated RADE on Slice A (Pan A) and unmuted its audio. Root cause: VfoWidget's mode combo handler unconditionally emitted radeActivated(false, sliceId) for any non-RADE mode selection — even on a slice that never had RADE active. Every VfoWidget is wired to the same MainWindow deactivateRADE() handler, so Slice B's combo change fired deactivateRADE(), which restored m_radePrevMute (false) on Slice A and tore down the RADE engine. The quick-mode button handler already had the correct guard: if (m_radeActive) emit radeActivated(false, ...) The combo handler was simply missing it. Fix applied to VfoWidget (source guard using m_radeActive). The RxApplet combo has the same missing guard but has no m_radeActive member, so both MainWindow radeActivated handlers now also check sliceId == m_radeSliceId before calling deactivateRADE() — belt-and-suspenders that covers both widgets correctly. Bug 2: After the spurious deactivation, re-selecting RADE on Slice A re-muted the slice but the RADE status label / sync indicator / SNR meter did not appear. Root cause: activateRADE() and deactivateRADE() used spectrum() (returns the *active* pan's SpectrumWidget) to find the RADE slice's VfoWidget. In multi-pan, if Pan B had focus, spectrum() returned Pan B's widget and vfoWidget(sliceA_id) returned null — setRadeActive(true) was never called, leaving the VfoWidget in m_radeActive=false with no status label shown. Additionally the syncChanged/snrChanged/freqOffsetChanged connections to the VfoWidget were silently skipped, so those indicators never updated. Fix: use spectrumForSlice(m_radioModel.slice(sliceId)) which resolves the correct pan by panId regardless of which pan is currently focused. spectrumForSlice() falls back to spectrum() when the slice is null, so behaviour is identical for the single-pan case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3bc68a6 to
200e841
Compare
…t pattern PR aethersdr#2026 added an `if (m_radeActive)` guard to VfoWidget's unconditional `emit radeActivated(false, ...)` to prevent spurious deactivation of RADE on a different slice when any slice changes mode. RxApplet had the identical pattern but was not updated in the same PR. Add `m_radeActive{false}` and `setRadeActive(bool)` to RxApplet (mirroring VfoWidget and PhoneCwApplet), guard the false emit, and wire the setter in MainWindow::activateRADE / deactivateRADE so the state tracks correctly. The MainWindow `sliceId == m_radeSliceId` handler guard remains load-bearing and prevents any behavioral regression; this is a defense-in-depth cleanup for code clarity and future-proofing. Closes aethersdr#2062 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2376) * fix(rx-applet): add matching radeActivated guard to mirror VfoWidget Closes #2062 PR #2026 patched VfoWidget so its mode-switch lambda only emits `radeActivated(false, ...)` when RADE was actually active on that widget. The MainWindow handler `(else if (sliceId == m_radeSliceId) deactivateRADE())` makes the matching unguarded emit at `RxApplet.cpp:396` harmless today, but the asymmetry is exactly the shape of the multi-pan regression #2026 fixed: it would re-tear-down RADE on a different slice if the MainWindow guard ever softened. This applies the same defense-in-depth guard: - Add `bool m_radeActive{false};` to `RxApplet.h` (private), mirroring `VfoWidget`. - In the mode-combo lambda, set `m_radeActive = true` after emitting the activate signal. - Wrap the deactivate emit in `if (m_radeActive) { ... m_radeActive = false; }` so a USB->LSB swap on a non-RADE slice no longer fires a spurious `radeActivated(false)`. Comment cites #2026 inline so future readers see the parallel. No behavior change in the steady state: MainWindow still owns the authoritative `m_radeSliceId` check; this just stops shouting `false` from a slice that never said `true`. * fix(rx-applet): use slice mode pre-change instead of local m_radeActive flag The local-flag pattern from VfoWidget doesn't transfer cleanly to RxApplet because the lifecycle is different: - VfoWidget is one instance per slice; m_radeActive is set externally by MainWindow::setRadeActive() so it tracks the authoritative state. - RxApplet is a single instance rebound across slices via setSlice(); the local flag isn't synced from MainWindow and isn't reset on setSlice(), so it gets out of sync in two real scenarios: 1. Externally activated RADE (VfoWidget combo, profile load on startup, MainWindow::activateRADE direct path) leaves m_radeActive=false in RxApplet; subsequent mode change in RxApplet would suppress the deactivate emit and orphan the RADE engine. 2. Slice 0 in RADE → slice switch to slice 1 (USB) doesn't reset the flag → user picks LSB on slice 1 → flag clears (because the lambda runs even when MainWindow's authoritative guard silently filters the spurious emit) → user switches back to slice 0 (still RADE) → guard now incorrectly suppresses. Both reachable in normal multi-pan workflows, same class of bug as docs/multi-pan-pitfalls.md catalogues. The simpler fix uses what's already authoritative: the slice's pre-change mode. m_slice->setMode(mode) runs at the END of the lambda, so m_slice->mode() inside the lambda still holds the prior mode. Read it directly; emit deactivate only when the slice was actually in RADE. No new state, no sync hazard, correct across: - single-instance setSlice() rebind - externally activated RADE - the multi-pan case the original PR was targeting Co-Authored-By: Matt Van Horn <mvanhorn@nibbcorp.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Jeremy Fielder <kk7gwy@aethersdr.com> Co-authored-by: Matt Van Horn <mvanhorn@nibbcorp.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #2734 ## Why `RxApplet.cpp` was patched in #2376 so its mode-combo lambda only emits `radeActivated(false, ...)` when `m_slice->mode() == "RADE"`. That guard is never true in steady state. "RADE" is a client-side-only concept. When the user selects RADE, `setMode("RADE")` sends `slice set N mode=RADE` to the radio, which ignores the unknown mode string and echoes back the real mode (`DIGL` for 40m, `DIGU` for 60m/WSPR). `SliceModel::handleSliceStatus` immediately overwrites `m_mode` from that echo. By the time the user switches away from RADE, `m_slice->mode()` is already `"DIGL"` — the guard always evaluates false, `radeActivated(false)` is never emitted, `deactivateRADE()` never runs, and `audio_mute=1` is stranded on the slice. The radio's per-band stack saves that muted state. Returning to the band (via the band-stack, on reconnect, or after a restart) reloads `audio_mute=1`, making RX audio silently dead on that band until manually toggled. All other bands are unaffected because they were saved before RADE muted the slice. ## What changes **`src/gui/RxApplet.cpp`** — remove the `mode() == "RADE"` guard; emit `radeActivated(false, ...)` unconditionally, matching pre-#2376 behavior: ```cpp // "RADE" is client-side only — the radio echoes back the real mode // (DIGL/DIGU) immediately, so mode() == "RADE" is never true in // steady state. Emit unconditionally; MainWindow's // sliceId == m_radeSliceId check is the authoritative filter that // prevents spurious deactivations from non-RADE slices (#2026). emit radeActivated(false, m_slice ? m_slice->sliceId() : -1); ``` Net diff: +6 / −14 in one file. ## Why the unconditional emit is safe `MainWindow`'s `radeActivated` handler (wired in `MainWindow.cpp`) already guards: ```cpp else if (sliceId == m_radeSliceId) deactivateRADE(); ``` A spurious `radeActivated(false, sliceId)` from a non-RADE slice is silently dropped there. That check is the safety #2026 actually relied on for the multi-pan case — not the mode string. `RxApplet` is also architecturally different from `VfoWidget`: a single `RxApplet` instance cycles through whichever slice the user selects via the tab row (N:1), whereas each `VfoWidget` is permanently bound to one slice (1:1). A mode-string or boolean flag check in `RxApplet` is inherently unreliable across slice rebinds; `MainWindow::m_radeSliceId` is the right authority. ## Behavior - **Steady state / multi-pan**: identical to today. A USB→LSB swap on a non-RADE slice fires `radeActivated(false, sliceB)`; MainWindow drops it because `sliceB != m_radeSliceId`. RADE on slice A is unaffected. - **The fixed case**: user enables RADE on 40m, then switches to USB via the RxApplet combo. `radeActivated(false, 0)` now fires; MainWindow calls `deactivateRADE()`; `audio_mute` is restored; band-stack saves the clean state. ## Verification Tested on **FLEX-8400, firmware v4.1.5.39794**: - Enable RADE on 40m via RxApplet combo → switch to USB → slice unmutes, RADE engine stops ✓ - Enable RADE on 40m via VfoWidget combo → switch to USB via RxApplet combo → same result ✓ - Multi-pan: RADE on slice 0 (Pan A), swap slice 1 (Pan B) USB→LSB via RxApplet → RADE on Pan A remains active ✓ - Close + reopen AetherSDR, tune to 40m → audio present ✓ ## Follow-on A separate PR will add a `modeChanged` listener in `MainWindow::activateRADE` to auto-deactivate RADE when the slice mode changes via external paths (TCI client, profile load, remote SmartSDR client). That closes the same class of bug regardless of which code path drives the mode change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Background
This fix was identified during post-merge testing of PR #1953 (dual-buffer RADE
RX architecture). Multi-pan verification of the new RADE audio path uncovered
two independent bugs that together caused RADE to be torn down whenever the mode
was changed on a completely unrelated slice.
Problem
In a two-panadapter setup, switching Slice B to DIGU (or any non-RADE mode via
the mode combo box) killed RADE on Slice A:
Pan A's VFO
Switching Slice B mode again did not restore RADE — a full manual re-activation
was required.
Root Cause (three independent bugs)
Bug 1 — Unconditional
radeActivated(false)emit in VfoWidget:The mode combo-box
currentTextChangedhandler emittedradeActivated(false, sliceId)for every non-RADE mode selection, regardless of whether RADE wasactive on that widget. The quick-mode buttons directly below in the same file
already had the correct
if (m_radeActive)guard; the combo handler was missingit.
Bug 2 — Missing
sliceIdguard in MainWindow handlers:Both
radeActivatedsignal handlers in MainWindow (one wired to RxApplet, oneto VfoWidget) called
deactivateRADE()unconditionally onon=false. Aspurious
radeActivated(false, sliceB)from Bug 1 was enough to tear down RADEeven when
m_radeSliceIdpointed to Slice A.Bug 3 — Wrong spectrum lookup in
activateRADE/deactivateRADE:Both methods called
spectrum()(returns the currently focused pan'sSpectrumWidget) instead of
spectrumForSlice()(looks up by pan ID). RADEstatus indicators would appear/disappear on the wrong pan if a different pan
was focused at the time of activation.
Fix
if (m_radeActive)guard beforeemit radeActivated(false, ...)in the combo handler, matching the existingguard on the quick-mode buttons
else deactivateRADE()→else if (sliceId == m_radeSliceId) deactivateRADE()so only the owningslice can trigger deactivation
activateRADE/deactivateRADE): replacespectrum()with
spectrumForSlice(m_radioModel.slice(...))for correct indicatorplacement in any pan layout
Testing
Tested on FLEX-8400, firmware v4.1.5.39794, two-panadapter layout:
🤖 Generated with Claude Code