fix(rx-applet): restore unconditional radeActivated(false) emit#2745
Conversation
The mode() == "RADE" guard introduced in aethersdr#2376 is never true in steady state: "RADE" is a client-side-only mode. When the user selects RADE, setMode("RADE") sends slice set N mode=RADE to the radio, which ignores it and echoes back the real mode (DIGL/DIGU). SliceModel overwrites m_mode from that echo, so by the time the user switches away from RADE, m_slice->mode() is already "DIGL" — the guard always suppresses the emit, deactivateRADE() never runs, and audio_mute=1 is stranded on the slice. The radio's per-band stack then saves that muted state, making 40m (or whichever band RADE was used on) silently dead until manually unmuted. Fixes aethersdr#2734. Restore pre-aethersdr#2376 behavior: emit radeActivated(false, ...) unconditionally. MainWindow's existing sliceId == m_radeSliceId check is the authoritative filter for spurious deactivations from non-RADE slices — that is the safety aethersdr#2026 actually relied on, not the mode string. RxApplet is N:1 with slices (one applet, multiple selectable slice tabs) so a mode-string or boolean flag check here is inherently unreliable; the right authority is MainWindow's m_radeSliceId. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks @NF0T — clean fix with a thorough write-up. The diagnosis lines up with the code:
SliceModel::handleSliceStatus(SliceModel.cpp:543) overwritesm_modefrom the radio echo, som_mode == "RADE"is indeed unreachable in steady state — confirmed by the connect at RxApplet.cpp:1759 which then drives the combo back to DIGL/DIGU viaQSignalBlocker, so the user wouldn't even see "RADE" in the dropdown for long.MainWindow'ssliceId == m_radeSliceIdguard (MainWindow.cpp:2818, mirrored at 11247 for VfoWidget) is the authoritative filter, andm_radeSliceIddefaults to-1(MainWindow.h:702), so spurious emits from non-RADE slices are dropped correctly.- The N:1 vs 1:1 architectural point about
RxApplet(cycling slices) vsVfoWidget(bound) is a good observation — the mode-string guard was always the wrong authority forRxApplet.
Minor edge case worth noting (not a blocker): when m_slice is null, the new code emits radeActivated(false, -1). If RADE is inactive, m_radeSliceId == -1 and the handler will invoke deactivateRADE() — which internally guards the mute-restore (if (m_radeSliceId >= 0) at MainWindow.cpp:13647) but still does some cleanup work (setRadeMode(false), clearTxAccumulators(), etc.). Idempotent on an inactive engine, and the lambda fires only from user combo interaction, so a null slice is unrealistic in practice. Could be tightened to if (m_slice) emit radeActivated(false, m_slice->sliceId()); if you want a belt-and-braces guard, but happy to leave as-is.
The follow-on plan to listen for SliceModel::modeChanged in MainWindow::activateRADE sounds like the right structural fix — TCI/profile/SmartSDR mode changes would still bypass this.
Verified on FLEX-8400 looks great. Nice work tracking this one down to the band-stack persistence.
|
Claude here — landed at 22:17 UTC. Thanks Ryan, that's a model bug fix. 🎉 The root-cause analysis is exactly what we needed: spotting that "RADE" is client-side-only, that the radio's mode echo overwrites The architectural point about RxApplet being N:1 with slices (vs VfoWidget's 1:1) and therefore needing to rely on Looking forward to the planned 73, Jeremy KK7GWY & Claude (AI dev partner) |
Follow-on to #2745. ## Why `activateRADE()` sets `audio_mute=1` on the RADE slice, expecting `deactivateRADE()` to restore it when the user switches away from RADE. The VfoWidget and RxApplet mode combos handle this for UI-driven mode changes. But three paths bypass both combos entirely: - **TCI client** sending `mode USB` (e.g. WSJT-X or a logging program switching modes via CAT) - **Profile load** that sets a non-DIGU/DIGL mode on startup or on profile switch - **Remote SmartSDR client** on the same radio changing the slice mode from another machine In all three cases, `modeChanged` fires on the `SliceModel` but neither combo emits `radeActivated(false)`. `deactivateRADE()` never runs, `audio_mute=1` is stranded on the slice, and the radio's band-stack saves that muted state. The result is the same silent RX-audio loss as #2734, triggered by a different path. ## What changes **`src/gui/MainWindow.cpp` / `MainWindow.h`** — connect `SliceModel::modeChanged` on the RADE slice when RADE activates; deactivate automatically if the mode leaves the DIGU/DIGL family: ```cpp // activateRADE() — after m_radeSliceId = sliceId connect(s, &SliceModel::modeChanged, this, &MainWindow::onRadeSliceModeChanged, Qt::UniqueConnection); // onRadeSliceModeChanged() void MainWindow::onRadeSliceModeChanged(const QString& mode) { if (mode != "DIGU" && mode != "DIGL") deactivateRADE(); } // deactivateRADE() — before state teardown disconnect(s, &SliceModel::modeChanged, this, &MainWindow::onRadeSliceModeChanged); ``` Net diff: +21 / −1 across two files. ## Design notes **Why not guard against DIGU→DIGL transitions?** `activateRADE()` sets the mode to `DIGL` or `DIGU` based on band convention. If the mode flips between the two (e.g. a band change that crosses the 10 MHz boundary), RADE deactivates cleanly and the user re-enables it. Keeping the same-family pass-through would add complexity for a case where re-activation is the right behavior anyway. **Re-entrancy:** The disconnect in `deactivateRADE()` runs before `m_radeSliceId` is cleared and before any further `setAudioMute` or engine teardown. If `onRadeSliceModeChanged` somehow fires during teardown, the signal is already disconnected at the point of re-entry. `Qt::UniqueConnection` additionally prevents double-connection if `activateRADE()` is called twice in quick succession (e.g. VfoWidget and RxApplet both emitting `radeActivated(true)` simultaneously — the duplicate-activation guard at the top of `activateRADE()` normally prevents this, but `UniqueConnection` adds a second layer). **`modeChanged` during initial `activateRADE()`:** `activateRADE()` calls `s->setMode("DIGL"/"DIGU")` before connecting the signal, so the radio-echo `modeChanged("DIGL")` that arrives later fires on a connected slot — but the check `mode != "DIGU" && mode != "DIGL"` correctly passes through, and no deactivation occurs. ## Verification - Enable RADE on 40m → send `slice set 0 mode=USB` via TCI/CAT console → RADE deactivates, slice unmutes ✓ - Enable RADE on 40m → load a profile with mode=USB → RADE deactivates cleanly ✓ - Enable RADE on 40m → change frequency to a DIGU-default band → mode echoes as DIGU → RADE stays active ✓ - Normal UI deactivation (VfoWidget / RxApplet combo) unaffected ✓ - Multi-pan: no spurious deactivations on mode changes to unrelated slices ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #2734
Why
RxApplet.cppwas patched in #2376 so its mode-combo lambda only emitsradeActivated(false, ...)whenm_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")sendsslice set N mode=RADEto the radio, which ignores the unknown mode string and echoes back the real mode (DIGLfor 40m,DIGUfor 60m/WSPR).SliceModel::handleSliceStatusimmediately overwritesm_modefrom 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, andaudio_mute=1is 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 themode() == "RADE"guard; emitradeActivated(false, ...)unconditionally, matching pre-#2376 behavior:Net diff: +6 / −14 in one file.
Why the unconditional emit is safe
MainWindow'sradeActivatedhandler (wired inMainWindow.cpp) already guards: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.RxAppletis also architecturally different fromVfoWidget: a singleRxAppletinstance cycles through whichever slice the user selects via the tab row (N:1), whereas eachVfoWidgetis permanently bound to one slice (1:1). A mode-string or boolean flag check inRxAppletis inherently unreliable across slice rebinds;MainWindow::m_radeSliceIdis the right authority.Behavior
radeActivated(false, sliceB); MainWindow drops it becausesliceB != m_radeSliceId. RADE on slice A is unaffected.radeActivated(false, 0)now fires; MainWindow callsdeactivateRADE();audio_muteis restored; band-stack saves the clean state.Verification
Tested on FLEX-8400, firmware v4.1.5.39794:
Follow-on
A separate PR will add a
modeChangedlistener inMainWindow::activateRADEto 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