Skip to content

[codex] Add MQTT antenna display names#2881

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
s53zo:codex/mqtt-antenna-aliases
May 23, 2026
Merged

[codex] Add MQTT antenna display names#2881
ten9876 merged 2 commits into
aethersdr:mainfrom
s53zo:codex/mqtt-antenna-aliases

Conversation

@s53zo

@s53zo s53zo commented May 19, 2026

Copy link
Copy Markdown
Contributor

Fixes #2880

What changed

  • Added a small MQTT antenna alias parser/queue helper for display-name updates.
  • Added per-port topics under aethersdr/antenna/name/<TOKEN> and a bulk JSON topic at aethersdr/antenna/names.
  • Wired MQTT messages into RadioModel antenna aliases only after a connected radio has a stable identity, and clear pending updates on disconnect.
  • Documented the topic contract in the in-app help and added focused regression tests.

Topic design

Fixed AetherSDR-owned default topics are intentional for this first version. They provide a stable automation contract and keep the existing MQTT applet settings small. If operators later need multiple independent AetherSDR instances or station namespaces, a configurable base topic such as aethersdr/<station>/... can be added without changing the alias parser semantics.

Verification

  • cmake --build build --target AetherSDR mqtt_antenna_alias_test antenna_alias_test -j4
  • ctest --test-dir build -R 'antenna_alias_test|mqtt_antenna_alias_test' --output-on-failure
  • git diff --check HEAD~1..HEAD
  • Manual MQTT publish to aethersdr/antenna/name/ANT1 through broker 10.0.10.9:4883; confirmed the running app updated the local antenna name.
  • Independent code review pass completed; blocking findings were addressed before this PR.

External coax switches can change which physical antenna is attached to
a fixed radio port, so MQTT needs a small display-name contract that
updates AetherSDR local aliases without changing radio protocol tokens.

The parser is broker-independent and testable: per-port topics update one
canonical token, bulk JSON updates a snapshot, invalid tokens are ignored,
and empty/null values clear aliases. The delivery path waits for a stable
connected radio identity before persisting aliases, and drops disconnected
updates so names cannot leak into a later radio.

Constraint: Radio commands must keep using canonical Flex antenna tokens
Constraint: Existing MQTT applet has a user-managed subscription list
Rejected: Configurable MQTT topic prefix in this PR | broader UI/settings surface; fixed defaults give a stable first contract
Confidence: high
Scope-risk: narrow
Directive: Do not route antenna display names into SliceModel rxant/txant commands
Tested: ctest --test-dir build -R antenna_alias_test\|mqtt_antenna_alias_test --output-on-failure
Tested: cmake --build build --target AetherSDR mqtt_antenna_alias_test antenna_alias_test -j4
Tested: Manual MQTT publish to aethersdr/antenna/name/ANT1 via broker 10.0.10.9:4883
Not-tested: MQTT broker authentication/TLS variants
Related: #2880
@s53zo s53zo marked this pull request as ready for review May 19, 2026 21:30

@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 for the careful work here, @s53zo — the design is tidy and the test coverage on the parser/queue is exactly the kind of focused verification this codebase rewards. A few observations:

Design / conventions — Looks good:

  • Routes through RadioModel::setAntennaAlias/clearAntennaAlias, so the existing AntennaAliasStore per-radio persistence (chassis-serial-keyed) does the right thing automatically.
  • The MqttAntennaAliasQueue separation makes the identity-stability rule (isConnected && (chassisSerial || serial)) cleanly testable, and the tests cover the disconnect-drop and stable-flush paths.
  • The shared_ptr<MqttAntennaAliasQueue> capture in the three lambdas (slot + connection state + infoChanged) is the right call since m_radioModel and the applet outlive each other and Qt's this-receiver semantics handle teardown.
  • Help doc clearly states these are display names only and AetherSDR still sends canonical radio tokens — important distinction for operators.

Minor things worth thinking about (none blocking):

  1. Stability check vs. antennaAliasRadioKey() fallback chain. hasStableRadioAliasKey requires chassisSerial OR serial, but RadioModel::antennaAliasRadioKey() (src/models/RadioModel.cpp:530) falls back further to model/name/nickname. So a radio that reports identity only via model/name will queue forever and never flush. Intentional (avoids polluting non-canonical keys), but worth a one-line comment in the lambda so the next reader doesn't try to "fix" it.

  2. infoChanged fires often. setFullDuplex and other non-identity changes will all retrigger flushPendingMqttAntennaAliases. The flush is a cheap empty-map iteration when there's nothing pending, so this is fine — but if the queue ever grows, every infoChanged would re-emit each alias. Coalescing via QMap::insert in receive() keeps the queue bounded per-token, so OK in practice.

  3. Bulk JSON ignores non-string/null values silently. {"ANT1": 42} produces no update and no log line. Probably fine since this is operator-side automation, but a qWarning for unexpected types would help debugging a misconfigured Node-RED flow. Defer if you'd rather not expand scope.

  4. Topic-prefix design. The fixed aethersdr/antenna/name/* prefix is fine for v1 as you noted in the PR body. If/when a configurable base ships, the parser's API (takes topic + prefix function) makes that a clean extension.

Nothing here is blocking — the implementation is clean, scoped, and tested. Nice work.

@ten9876

ten9876 commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Claude here — pushed merge commit `3b8dda86` resolving the
`MqttApplet.cpp` conflict against current main.

The conflict was purely adjacent-add in the includes block: this
PR added `#include "core/MqttAntennaAlias.h"` on the same line range
that an unrelated recent change (#2958 MQTT keychain) added
`#include "core/LogManager.h"`. Kept both, no semantic change.

All three intended PR additions verified post-merge:

  • Include of MqttAntennaAlias.h (line 4)
  • Tooltip line at `MqttApplet.cpp:132` ("Antenna names: …")
  • onMessageReceived alias-parsing block at lines 321-322

Local Release build clean. The new `mqtt_antenna_alias_test`
runs 25/25 green including the queue-lifecycle cases —
cross-radio replay prevention, identity-stable gating, and
disconnect-clear behavior all verified.

On the two non-blocking items I noted in review:

  • The `infoChanged` flush hook fires on every RadioModel property
    change, but the work-when-empty path is sub-microsecond
    (empty-map iteration). Skipping — pure noise.
  • Auto-subscribing to `aethersdr/antenna/name/+` would be a useful
    ergonomic improvement but is a feature-request-sized change that
    needs its own design pass (when to subscribe? unsubscribe on
    applet disconnect? collision with user-managed Topics field?).
    Worth a follow-up issue if users actually trip on it.

Will admin-merge once CI lands green.

@s53zo — really clean module decomposition. The
`MqttAntennaAliasQueue` separation in particular handles the
retained-MQTT-message-before-radio-identity case cleanly — that's
the kind of detail that's invisible to most users but matters a
lot in real station-automation deployments where the broker sits
between AetherSDR and the antenna switch.

73,
Jeremy KK7GWY & Claude (AI dev partner)

@ten9876 ten9876 merged commit c1b9ec9 into aethersdr:main May 23, 2026
5 checks passed
@ten9876

ten9876 commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Claude here — merged. Thanks @s53zo, this is exactly the kind of
station-automation integration the project benefits from.

The implementation discipline stood out:

The AppSettings key-constraint workaround was specifically called
out in code comments (`per-IP keys would be invalid in AppSettings'
XML/dotted path — IPs start with a digit and IPv6 has ':'`). That's
load-bearing detail that disappears into tribal knowledge if not
documented at the call site.

The IP normalization (`::ffff:a.b.c.d` → IPv4, `::1` → `127.0.0.1`)
prevents the same physical client from being filed under two
different alias spellings depending on which address family it
happened to connect over.

The identity-stable queueing handles the retained-MQTT-message
case cleanly — the broker delivers retained messages immediately
on subscribe, before AetherSDR knows which radio it's talking to;
the queue holds those updates until the radio's serial is known,
preventing alias leakage across reconnects to different radios.

25/25 parser tests including the queue-lifecycle suite, plus
real-hardware verification against your `10.0.10.9:4883` broker —
that thoroughness shows.

Ships in v26.5.3. Operators with Antenna Genius / external switch
automation can now publish current antenna labels via MQTT and
have AetherSDR's display reflect them in real time.

73,
Jeremy KK7GWY & Claude (AI dev partner)

G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
Fixes aethersdr#2880

## What changed
- Added a small MQTT antenna alias parser/queue helper for display-name
updates.
- Added per-port topics under `aethersdr/antenna/name/<TOKEN>` and a
bulk JSON topic at `aethersdr/antenna/names`.
- Wired MQTT messages into `RadioModel` antenna aliases only after a
connected radio has a stable identity, and clear pending updates on
disconnect.
- Documented the topic contract in the in-app help and added focused
regression tests.

## Topic design
Fixed AetherSDR-owned default topics are intentional for this first
version. They provide a stable automation contract and keep the existing
MQTT applet settings small. If operators later need multiple independent
AetherSDR instances or station namespaces, a configurable base topic
such as `aethersdr/<station>/...` can be added without changing the
alias parser semantics.

## Verification
- `cmake --build build --target AetherSDR mqtt_antenna_alias_test
antenna_alias_test -j4`
- `ctest --test-dir build -R
'antenna_alias_test|mqtt_antenna_alias_test' --output-on-failure`
- `git diff --check HEAD~1..HEAD`
- Manual MQTT publish to `aethersdr/antenna/name/ANT1` through broker
`10.0.10.9:4883`; confirmed the running app updated the local antenna
name.
- Independent code review pass completed; blocking findings were
addressed before this PR.

Co-authored-by: Jeremy Fielder <kk7gwy@aethersdr.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.

Allow MQTT to update local antenna display names

2 participants