Skip to content

DAX + TCI re-assert storm: continuous "slice set N dax=<ch>" spam when WSJT-X connects via TCI with DAX active (Linux/macOS) #4009

Description

@NF0T

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

  1. Assign DAX channel 1 to slice 0 in the radio's saved profile.
  2. In AetherSDR, enable DAX for that slice.
  3. Launch WSJT-X and configure it to use TCI as the CAT/audio interface.
  4. Observe continuous slice set 0 dax=1 spam in
    ~/.config/AetherSDR/aethersdr.log (~12–15 Hz).
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    audioAudio engine and streamingbugSomething isn't workingexternal devicesFlexControl, MIDI, serial port, USB peripheralslinuxmacOSmacOS-specific issuemaintainer-reviewRequires maintainer review before any action is takenpriority: highHigh priorityprotocolSmartSDR protocol

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions