Skip to content

[tci] Add clicked_on_spot event support for Log4OM interoperability#3145

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:aether/tci-spot-click-log4om
May 25, 2026
Merged

[tci] Add clicked_on_spot event support for Log4OM interoperability#3145
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:aether/tci-spot-click-log4om

Conversation

@jensenpat

@jensenpat jensenpat commented May 25, 2026

Copy link
Copy Markdown
Collaborator

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>
@jensenpat jensenpat changed the title Fix TCI spot click notifications for Log4OM [tci] Add clicked_on_spot event support for Log4OM interoperability May 25, 2026
@jensenpat jensenpat marked this pull request as ready for review May 25, 2026 17:26
@jensenpat jensenpat requested review from a team as code owners May 25, 2026 17:26

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+ in TciProtocol::cmdSpot, so they correctly bypass the spot trigger radio command (which would have produced no echo and no clicked_on_spot — exactly the Log4OM symptom described).
  • SpotModel::spotTriggered is only emitted from RadioModel on a radio-side spot N triggered echo (RadioModel.cpp:4241), so there's no risk of double-broadcasting for the TCI-direct path.
  • The !s->isLocked() && !m_swrSweep.running gate on the TCI-direct notification matches the same guards used elsewhere on the tune path (MainWindow.cpp:10876), so the clicked_on_spot echo 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 existing tciTrxForSlice policy, which is consistent with vfo: and modulation: emission.
  • tciTrxForSlice(model, nullptr) returns 0 (TciProtocol.cpp:19-23), so the notifySpotClicked fallback to first slice when sliceForPanId can't resolve a panId is safe.

Minor observations (non-blocking)

  • In MainWindow.cpp:12270, the early-return on SpotAutoSwitchMode != "True" happens before if (!s) return; is reached. The new code moves auto* s = preferredMemorySlice(...) above the auto-switch return — the new branch needs s for the TCI notify check, so the move is correct, but worth noting that s is now computed even when auto-switch is off. No behavior change since preferredMemorySlice is cheap.
  • The pre-existing static int idx = 10000; in TciProtocol::cmdSpot (out of scope for this PR) would technically wrap to negative on overflow and then collide with isPassiveLocalSpotId. 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

@ten9876 ten9876 merged commit f83e5be into aethersdr:main May 25, 2026
5 checks passed
@ten9876

ten9876 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

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 qWarning on the slices.first() fallback path). All low-priority — nothing regresses anything; pure documentation + observability. Feel free to grab them or leave them for whoever.

73, Jeremy KK7GWY & Claude (AI dev partner)

aethersdr-agent Bot added a commit that referenced this pull request May 25, 2026
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>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants