Is this report AI-assisted?
Yes — diagnosed and drafted with Claude Code.
Describe the bug
When WSJT-X (or any TCI client) connects via TCI while a DAX channel is already
assigned to a slice, AetherSDR enters a self-sustaining slice set N dax=<ch>
storm running at ~12–15 Hz. The spam persists even after WSJT-X is closed.
Root cause (traced through logs and source):
ensureDaxForTci() in TciServer.cpp sends slice set 0 dax=1 via direct
sendCommand to re-establish the radio's DAX client count. The FLEX-8000 series
always responds to any slice set dax=<ch> command with a transient broadcast:
slice 0 dax=0 dax_clients=0 then slice 0 dax=1 dax_clients=1. The dax=0
event updates m_daxSliceLastCh[0] to 0. The subsequent dax=1 event then sees
oldCh=0 ≠ newCh=1, treats it as a fresh channel assignment, and calls
onDaxChannelChanged(), which re-asserts — restarting the cycle.
Side effects:
- WSJT-X waterfall is smeared (audio buffer jitter from continuous stream churn)
- FT8 signals appear wider than expected in the waterfall (some still decode)
- Log scrolls continuously with
slice set 0 dax=1 entries even after WSJT-X exits
Platform: Linux and macOS only. Windows is immune — DaxBridge, m_daxBridge,
and the entire wireDaxSlice / onDaxChannelChanged block are inside
#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE).
Regression trace / PR history:
This is a direct regression from #2895 (June 3), partially masked by #3626 (June 25)
which fixed a related but distinct storm and explicitly noted its test environment
could not exercise the DAX bridge code path.
| Date |
Commit |
PR |
Effect |
| May 31 |
1d06f2d4 |
#3270 |
Added connectionStateChanged / sliceAdded re-arm hooks to ensureDaxForTci(), including the slice set dax=<ch> re-assert. Benign at this point — onDaxChannelChanged did not yet exist. |
| Jun 3 |
07a5c40c |
#2895 |
Bug introduced. Added onDaxChannelChanged() with its own slice set dax=<ch> re-assert (explicitly described in the PR as "the #1439 client-registration workaround"). When both the DAX bridge and TCI are active simultaneously, the two re-assert paths feed each other. |
| Jun 23 |
c7d93c05 |
#3669 |
Fixed audio silence during the transient dax=0 in TCI audio routing ("resilient to transient dax=0"). The transient was already known; this PR addressed only the dropped-frames symptom, not the re-assert loop. |
| Jun 25 |
d8c10f5b |
#3626 |
Fixed a related but distinct storm — the stream create → remove → create cycle on FLEX-8600M. Moved the reconciler from MainWindow.cpp to MainWindow_DigitalModes.cpp and added the comment: "does NOT re-enter this reconciler." The PR explicitly notes: "the DAX-bridge code path itself could not be exercised here" because the DAX HAL plugin was not installed on the test machine; m_daxBridge was always null and onDaxChannelChanged never ran. The comment holds only for the stream-create trigger path; it does not hold for the ensureDaxForTci() re-assert path, which uses sendCommand directly and leaves m_daxChannel unchanged in SliceModel before the radio echo arrives. |
Net: #3626 severed the stream create/remove link in the feedback loop but left
the re-assert link intact. This PR fixes the remaining link.
Log trace (pre-fix, aethersdr-20260703-190907.log):
Storm onset — WSJT-X audio_start triggers ensureDaxForTci(), which fires the
first re-assert at C173. Each cycle is ~65–80 ms (~12–15 Hz):
[19:09:22.567] DBG aether.protocol: RadioModel::sendCommand: "slice set 0 dax=1" connected: true wan: false
[19:09:22.567] DBG aether.connection: TX: "C173|slice set 0 dax=1" ← re-assert fires
[19:09:22.608] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=0 dax_clients=0" ← transient dax=0 corrupts m_daxSliceLastCh[0]→0
[19:09:22.608] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=1 dax_clients=1" ← sees oldCh=0≠1, fires onDaxChannelChanged → re-asserts
[19:09:22.617] DBG aether.protocol: RadioModel::sendCommand: "slice set 0 dax=1" connected: true wan: false
[19:09:22.617] DBG aether.connection: TX: "C174|slice set 0 dax=1" ← cycle 2
[19:09:22.689] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=0 dax_clients=0"
[19:09:22.689] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=1 dax_clients=1"
[19:09:22.701] DBG aether.connection: TX: "C175|slice set 0 dax=1" ← cycle 3
[19:09:22.770] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=0 dax_clients=0"
[19:09:22.770] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=1 dax_clients=1"
[19:09:22.784] DBG aether.connection: TX: "C176|slice set 0 dax=1" ← cycle 4
[19:09:22.851] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=0 dax_clients=0"
[19:09:22.851] DBG aether.connection: RX: "S2BA4E7D3|slice 0 dax=1 dax_clients=1"
[19:09:22.867] DBG aether.connection: TX: "C177|slice set 0 dax=1" ← cycle 5
... (continues indefinitely at ~12–15 Hz; FLEX-8400 fw v4.2.20.41343)
Expected behavior
A single slice set 0 dax=1 is sent when WSJT-X initiates TCI audio. No
repeating storm; WSJT-X waterfall and audio are stable.
Steps to reproduce
- Assign DAX channel 1 to slice 0 in the radio's saved profile.
- In AetherSDR, enable DAX for that slice.
- Launch WSJT-X and configure it to use TCI as the CAT/audio interface.
- Observe continuous
slice set 0 dax=1 spam in
~/.config/AetherSDR/aethersdr.log (~12–15 Hz).
- Close WSJT-X — spam continues indefinitely.
AetherSDR version
26.7.1 (git ada799f0)
Radio
FLEX-8400, firmware v4.2.20.41343
Operating System
Linux
OS details
Debian GNU/Linux 13 (trixie), kernel 6.12.90+deb13.1-amd64
Is this report AI-assisted?
Yes — diagnosed and drafted with Claude Code.
Describe the bug
When WSJT-X (or any TCI client) connects via TCI while a DAX channel is already
assigned to a slice, AetherSDR enters a self-sustaining
slice set N dax=<ch>storm running at ~12–15 Hz. The spam persists even after WSJT-X is closed.
Root cause (traced through logs and source):
ensureDaxForTci()inTciServer.cppsendsslice set 0 dax=1via directsendCommandto re-establish the radio's DAX client count. The FLEX-8000 seriesalways responds to any
slice set dax=<ch>command with a transient broadcast:slice 0 dax=0 dax_clients=0thenslice 0 dax=1 dax_clients=1. Thedax=0event updates
m_daxSliceLastCh[0]to 0. The subsequentdax=1event then seesoldCh=0 ≠ newCh=1, treats it as a fresh channel assignment, and callsonDaxChannelChanged(), which re-asserts — restarting the cycle.Side effects:
slice set 0 dax=1entries even after WSJT-X exitsPlatform: Linux and macOS only. Windows is immune —
DaxBridge,m_daxBridge,and the entire
wireDaxSlice/onDaxChannelChangedblock are inside#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE).Regression trace / PR history:
This is a direct regression from #2895 (June 3), partially masked by #3626 (June 25)
which fixed a related but distinct storm and explicitly noted its test environment
could not exercise the DAX bridge code path.
1d06f2d4connectionStateChanged/sliceAddedre-arm hooks toensureDaxForTci(), including theslice set dax=<ch>re-assert. Benign at this point —onDaxChannelChangeddid not yet exist.07a5c40conDaxChannelChanged()with its ownslice set dax=<ch>re-assert (explicitly described in the PR as "the #1439 client-registration workaround"). When both the DAX bridge and TCI are active simultaneously, the two re-assert paths feed each other.c7d93c05dax=0in TCI audio routing ("resilient to transient dax=0"). The transient was already known; this PR addressed only the dropped-frames symptom, not the re-assert loop.d8c10f5bMainWindow.cpptoMainWindow_DigitalModes.cppand added the comment: "does NOT re-enter this reconciler." The PR explicitly notes: "the DAX-bridge code path itself could not be exercised here" because the DAX HAL plugin was not installed on the test machine;m_daxBridgewas always null andonDaxChannelChangednever ran. The comment holds only for the stream-create trigger path; it does not hold for theensureDaxForTci()re-assert path, which usessendCommanddirectly and leavesm_daxChannelunchanged inSliceModelbefore the radio echo arrives.Net: #3626 severed the stream create/remove link in the feedback loop but left
the re-assert link intact. This PR fixes the remaining link.
Log trace (pre-fix,
aethersdr-20260703-190907.log):Storm onset — WSJT-X
audio_starttriggersensureDaxForTci(), which fires thefirst re-assert at C173. Each cycle is ~65–80 ms (~12–15 Hz):
Expected behavior
A single
slice set 0 dax=1is sent when WSJT-X initiates TCI audio. Norepeating storm; WSJT-X waterfall and audio are stable.
Steps to reproduce
slice set 0 dax=1spam in~/.config/AetherSDR/aethersdr.log(~12–15 Hz).AetherSDR version
26.7.1 (git
ada799f0)Radio
FLEX-8400, firmware v4.2.20.41343
Operating System
Linux
OS details
Debian GNU/Linux 13 (trixie), kernel 6.12.90+deb13.1-amd64