[dax] Add support for DAX2 and TCI multi-stream routing#2140
[dax] Add support for DAX2 and TCI multi-stream routing#2140ten9876 merged 1 commit intoten9876:mainfrom
Conversation
There was a problem hiding this comment.
Thanks @jensenpat for tackling this — the DAX ownership filtering and contiguous TCI receiver mapping are exactly the right approach for firmware 4.2.18 compatibility, and the backwards-compat fallbacks are well thought through. The manual WSJT-X validation across both DAX and TCI paths is appreciated.
A few things worth addressing before merge:
1. Duplicated helper functions across three files
parseStatusHandle() and streamStatusBelongsToUs() are copy-pasted identically into both TciServer.cpp and MainWindow.cpp. A third near-duplicate parseStreamToken() lives in RadioModel.cpp. Similarly, tciTrxForSlice() is duplicated between TciProtocol.cpp (taking a QList<SliceModel*>) and TciServer.cpp (taking a RadioModel*).
These should live in one place — either a small shared header or on RadioModel itself. Duplicated parsing logic is a maintenance risk if the handle format evolves with future firmware.
2. Duplicate log lines at registration
In TciServer.cpp around the DAX RX stream registration, the new code adds a qCDebug(lcCat) log immediately followed by the existing qCInfo(lcCat) that logs the same event:
qCDebug(lcCat) << "TCI: registered DAX RX stream"
<< "0x" + QString::number(streamId, 16)
<< "for channel" << ch;
qCInfo(lcCat) << "TCI: registered DAX RX stream" << Qt::hex << streamId
<< "for channel" << ch << "(#1331)";One of these should be removed — the debug line is sufficient for diagnostics and the info line already existed.
3. const_cast in tciTrxForSlice
Both copies of tciTrxForSlice use const_cast<SliceModel*>(slice) to call QList::indexOf. Since slices() returns QList<SliceModel*> (non-const pointers), and the parameter is const SliceModel*, the cast is technically needed — but it would be cleaner to accept SliceModel* directly (both callers already have non-const pointers) or use std::find to avoid the cast.
4. m_rxAudioPackets incremented but timer never started
TciServer.h adds m_rxAudioLogTimer, m_rxAudioPackets, and m_rxAudioFramesSent, and onDaxAudioReady() increments m_rxAudioPackets. However, I don't see where m_rxAudioLogTimer is started or where these counters are ever read/logged. If the periodic summary log was intended, it looks like it didn't make it into the diff. If it's scaffolding for future work, consider removing the unused members until they're wired up — dead state is confusing.
5. MainWindow.cpp changes are borderline out-of-scope
The MainWindow.cpp diff adds parseStatusHandle/streamStatusBelongsToUs helpers plus rewires sliceAdded → wireSlice to use contiguous TCI indexes. The TCI rewiring makes sense here, but the status-handle parsing helpers duplicate TciServer.cpp. If MainWindow doesn't use them yet (they're only defined, not called from any visible diff context), they could be deferred until actually needed.
6. Minor: m_daxStreamDebug map grows without bound
RadioModel.h adds QMap<quint32, DaxStreamDebugState> m_daxStreamDebug for tracing. The streamStatusRemoved() helper exists but I don't see cleanup code that erases entries from this map when streams are removed. Over a long session with many stream create/destroy cycles, this could grow. Consider erasing entries on removal.
Overall the core logic — ownership filtering, contiguous receiver mapping, per-client audio_start:<receiver> routing — is solid and well-motivated. The compatibility fallbacks are careful. The main ask is to consolidate the duplicated helpers and clean up the unused timer/counter scaffolding. Nice work on a tricky firmware compat area.
…CI receiver-index policy (#2142) - Lift parseStatusHandle() and streamStatusBelongsToUs() into shared StreamStatus.h; remove duplicate copies from MainWindow.cpp and TciServer.cpp. Alias parseStreamToken() in RadioModel.cpp to call the shared parseStatusHandle(). - Move tciTrxForSlice() to TciProtocol as a public static method, unifying the two slightly different signatures from TciServer.cpp and TciProtocol.cpp. - Annotate m_daxTxClientHandle as diagnostics-only (set but never consulted in routing logic). - Add docs/tci-receivers.md documenting the contiguous 0..N-1 receiver-index policy introduced in #2140. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Community-driven release. WAVE Phase 2 visualization (#2124), DAX-aware TCI multi-stream routing for FlexRadio firmware 4.2.18 (#2140), TCXO frequency-offset calibration (#2119), VFO marker tri-state UX (#2141), v4.2.18 discovery beacon parsing (#2138). Bug fixes from the community: r8b heap corruption (#2114, NF0T), serial PTT triple-fix (#2125, chibondking), slice-audio mute on band change (#2128, jensenpat), CWX Live toggle (#2122, jensenpat), connect-radio dialog polish (#2121, jensenpat). Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
…CI receiver-index policy (#2142) (#2145) - Lift parseStatusHandle() and streamStatusBelongsToUs() into shared StreamStatus.h; remove duplicate copies from MainWindow.cpp and TciServer.cpp. Alias parseStreamToken() in RadioModel.cpp to call the shared parseStatusHandle(). - Move tciTrxForSlice() to TciProtocol as a public static method, unifying the two slightly different signatures from TciServer.cpp and TciProtocol.cpp. - Annotate m_daxTxClientHandle as diagnostics-only (set but never consulted in routing logic). - Add docs/tci-receivers.md documenting the contiguous 0..N-1 receiver-index policy introduced in #2140. Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <[email protected]>
Summary
This PR updates the DAX/TCI audio path for the newer FlexRadio 4.2.18 firmware behavior while preserving compatibility with earlier firmware status formats.
client_handlewhen the radio reports stream ownership, while accepting ownership-less status lines from older firmware.RadioModelso the logs identify stream IDs, ownership, DAX channel, slice, active/TX state, and removal events.0..N-1) rather than raw Flex slice IDs, with a raw-slice fallback for older clients.audio_start:<receiver>selection so multi-slice WSJT-X instances receive the intended audio stream.Why
Firmware 4.2.18 reports DAX stream ownership more explicitly, and multiple clients can now expose DAX-related stream status for the same DAX channel. The previous path could register another client's DAX stream or expose raw Flex slice IDs through TCI, which breaks clients like WSJT-X when this AetherSDR instance owns slice
1but advertises only one TCI receiver.The fix makes stream ownership explicit where the firmware provides it, keeps older firmware working when
client_handleis absent, and keeps TCI receiver numbering aligned with what clients actually see intrx_count.Compatibility
Older firmware remains supported:
client_handleis still accepted.audio_startkeeps the existing all-receiver audio behavior.channel - 1if a slice-to-DAX mapping is unavailable.Multi-Slice Behavior
TCI now treats the current owned slice list as the receiver namespace:
0.1.audio_start:<receiver>, while legacy clients still receive all enabled audio.Validation
cmake --build build -j10ctest --test-dir build --output-on-failure -j10reports no registered tests in this build tree.Notes
The added logging is intentionally targeted around DAX/TCI stream ownership and first-packet/audio-routing diagnostics. It should make future firmware behavior changes easier to identify without changing the core protocol behavior for older radios.
👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat