[tci] Add clicked_on_spot event support for Log4OM interoperability#3145
Conversation
Co-authored-by: Codex <noreply@openai.com>
There was a problem hiding this comment.
Thanks @jensenpat — this is a clean fix for a real interop gap. Traced the flow end-to-end and the approach is sound:
Verified
- TCI-injected spots are stamped
source="TCI"with synthetic ids from 10000+ inTciProtocol::cmdSpot, so they correctly bypass thespot triggerradio command (which would have produced no echo and noclicked_on_spot— exactly the Log4OM symptom described). SpotModel::spotTriggeredis only emitted fromRadioModelon a radio-sidespot N triggeredecho (RadioModel.cpp:4241), so there's no risk of double-broadcasting for the TCI-direct path.- The
!s->isLocked() && !m_swrSweep.runninggate on the TCI-direct notification matches the same guards used elsewhere on the tune path (MainWindow.cpp:10876), so theclicked_on_spotecho accurately reflects whether AetherSDR actually retuned. Good defensive choice for Log4OM logging accuracy. - Dual broadcast of
clicked_on_spot:+rx_clicked_on_spot:matches Thetis behavior and the receiver index reuses the existingtciTrxForSlicepolicy, which is consistent withvfo:andmodulation:emission. tciTrxForSlice(model, nullptr)returns 0 (TciProtocol.cpp:19-23), so thenotifySpotClickedfallback to first slice whensliceForPanIdcan't resolve a panId is safe.
Minor observations (non-blocking)
- In
MainWindow.cpp:12270, the early-return onSpotAutoSwitchMode != "True"happens beforeif (!s) return;is reached. The new code movesauto* s = preferredMemorySlice(...)above the auto-switch return — the new branch needssfor the TCI notify check, so the move is correct, but worth noting thatsis now computed even when auto-switch is off. No behavior change sincepreferredMemorySliceis cheap. - The pre-existing
static int idx = 10000;inTciProtocol::cmdSpot(out of scope for this PR) would technically wrap to negative on overflow and then collide withisPassiveLocalSpotId. Not a practical concern, just flagging for future awareness.
No conventions issues, no null/leak risks, scope is appropriate.
🤖 aethersdr-agent · cost: $9.7115 · model: claude-opus-4-7
|
Claude here — merged, thanks @jensenpat! Clean, surgical fix; the Codex draft + your real-world Log4OM validation made review easy. Filed #3152 to track three small polish nits surfaced during review (an explanatory comment about the TCI/radio gating asymmetry, a doc note on the dual-message wire-format change, and a 73, Jeremy KK7GWY & Claude (AI dev partner) |
Three small follow-ups to #3145, all observability/documentation — zero behavior change: 1. src/gui/MainWindow.cpp — comment the asymmetric gating in the spotTriggered lambda. The TCI branch suppresses on lock / SWR sweep / missing slice because it must mirror AetherSDR's local- tune outcome (Log4OM cannot diverge from the radio's actual state). The radio branch delegates gating to the firmware via `spot trigger`. Without the inline note a future "DRY this up" pass could silently regress Log4OM behavior under lock or SWR sweep. 2. docs/architecture/tci-receivers.md — note that both `clicked_on_spot:` and `rx_clicked_on_spot:` are emitted unconditionally for every spot click. #3145 made dual emission the v2 protocol baseline (no client-capability negotiation), which matters for anyone writing a TCI protocol parser. 3. src/core/TciServer.cpp — qCWarning when notifySpotClicked() falls back to slices.first() because the caller passed a null slice. The MainWindow path always supplies one (guarded on `s`); only wireSpotModel's sliceForPanId(panId) can return nullptr. Today that silently mis-attributes rx_clicked_on_spot:'s `trx` field to slice 0 — invisible in single-slice testing, broken in multi-pan. The warning makes the edge case diagnosable in the field. Principle I (FlexLib is the protocol authority) cited because the dual `clicked_on_spot:` / `rx_clicked_on_spot:` emission and `trx` indexing rules trace back to ExpertSDR3/TCI v2 spec authority that #3145 codified — this commit documents that decision, it does not revisit it. Blast radius: risk_score=0.1845 on TciServer::notifySpotClicked seed (4 high-risk callers: MainWindow::MainWindow, MainWindow::wirePanadapter, SliceModel::sliceId, RadioModel::slices). Verified the high-risk callers (MainWindow paths) only reach the function via the non-null-slice arm and never trigger the new warning branch; the only path that reaches the fallback is the in-file wireSpotModel lambda when sliceForPanId returns nullptr. No behavior change for any caller. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ethersdr#3145) ## Summary This fixes the Log4OM TCI spot-click flow where Log4OM can inject a spot into AetherSDR, AetherSDR tunes when the user clicks that spot on the panadapter, but Log4OM never receives a spot-click notification back to populate its logging fields. AetherSDR already accepted TCI `spot:` messages and rendered them locally, but those spots use synthetic local IDs. When the user clicked one, the UI attempted to send `spot trigger <id>` to the Flex radio. The radio does not know those local TCI spot IDs, so there was no radio-origin `spot ... triggered` status echo, and therefore the TCI server never emitted `clicked_on_spot` back to connected clients. This also adds spot event compatibility with RUMlogNG, Swisslog for Windows, LogHX and LogHX3, similar to Thetis implementation. ## What Changed - Added `TciServer::notifySpotClicked(...)` so UI paths can explicitly notify TCI clients when a locally rendered spot is clicked. - Centralized TCI spot-click formatting in the TCI server and now emits both forms used by Thetis / TCI clients: - `clicked_on_spot:<callsign>,<frequency_hz>;` - `rx_clicked_on_spot:<receiver>,0,<callsign>,<frequency_hz>;` - Updated the existing `SpotModel::spotTriggered` wiring so radio-origin spot trigger echoes also send the receiver-qualified `rx_clicked_on_spot` form. - Updated the panadapter spot-click path so TCI-origin spots skip the invalid Flex `spot trigger 10000...` command and notify TCI clients directly instead. - Documented the receiver/channel behavior in the TCI receiver architecture note. ## Protocol Behavior The implementation mirrors the Thetis behavior and the TCI standard shape: - TCI clients send spots with `spot:<callsign>,<mode>,<frequency_hz>,<color>,<text>;`. - AetherSDR stores those as local `source=TCI` spots. - When the user clicks the rendered spot, AetherSDR tunes the slice through the existing spectrum-click path. - AetherSDR then broadcasts the clicked spot notification to all connected TCI clients. The receiver value in `rx_clicked_on_spot` uses the same contiguous TCI `trx` index policy as `vfo:` and `modulation:`. The channel is currently `0`, matching AetherSDR's single-VFO path for the slice. ## Validation - `git diff --check` - Configured a fresh macOS build with the pinned Command Line Tools SDK: - `cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_OSX_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk` - Built the app successfully: - `cmake --build build --target AetherSDR -j22` 👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat Co-authored-by: Codex <noreply@openai.com>
Summary
This fixes the Log4OM TCI spot-click flow where Log4OM can inject a spot into AetherSDR, AetherSDR tunes when the user clicks that spot on the panadapter, but Log4OM never receives a spot-click notification back to populate its logging fields.
AetherSDR already accepted TCI
spot:messages and rendered them locally, but those spots use synthetic local IDs. When the user clicked one, the UI attempted to sendspot trigger <id>to the Flex radio. The radio does not know those local TCI spot IDs, so there was no radio-originspot ... triggeredstatus echo, and therefore the TCI server never emittedclicked_on_spotback to connected clients.This also adds spot event compatibility with RUMlogNG, Swisslog for Windows, LogHX and LogHX3, similar to Thetis implementation.
What Changed
TciServer::notifySpotClicked(...)so UI paths can explicitly notify TCI clients when a locally rendered spot is clicked.clicked_on_spot:<callsign>,<frequency_hz>;rx_clicked_on_spot:<receiver>,0,<callsign>,<frequency_hz>;SpotModel::spotTriggeredwiring so radio-origin spot trigger echoes also send the receiver-qualifiedrx_clicked_on_spotform.spot trigger 10000...command and notify TCI clients directly instead.Protocol Behavior
The implementation mirrors the Thetis behavior and the TCI standard shape:
spot:<callsign>,<mode>,<frequency_hz>,<color>,<text>;.source=TCIspots.The receiver value in
rx_clicked_on_spotuses the same contiguous TCItrxindex policy asvfo:andmodulation:. The channel is currently0, matching AetherSDR's single-VFO path for the slice.Validation
git diff --checkcmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_OSX_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdkcmake --build build --target AetherSDR -j22👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat