You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have read GOVERNANCE.md and confirmed this change requires an RFC (architecture change — process model, threading, signal routing)
I have searched existing issues and this RFC has not been proposed before
I have not opened a PR for this change yet
Full design doc drafted:docs/aetherd-headless-engine-design.md (house *-design.md format). It will land with the implementation PR if/when this RFC is approved — per the RFC process, no PR until sign-off. This issue is the discussion surface for the open questions at the bottom.
Problem
The UI cannot be changed without rebuilding the whole app, and there is no way to run a different UI technology (web, remote thin client, TUI) against the engine.
AetherSDR is today a single monolithic executable — add_executable(AetherSDR ${ALL_SOURCES}) compiles models/+core/+gui/ into one binary — and the UI is 100% imperative QWidget (128 QWidget subclasses, 0 QML). Any UI change relinks everything, and "a different UI" isn't expressible at all.
Proposal
Split AetherSDR into a headless engine daemon (aetherd) that owns the radio connection and all of core/+models/ (extracted into a new libaethercore), plus thin UI clients (Qt desktop, browser/web, TUI, scripting) that speak a versioned protocol.
FlexRadio ──VITA-49 / SmartLink──► aetherd (RadioModel, PanadapterStream,
AudioEngine, all DSP, TX guard)
│
┌───────────────┼────────────────┐
control plane data plane (both versioned)
JSON-RPC binary frames
│ │
┌────────┴──────┬────────┴────────┐
Qt desktop UI web UI (browser) TUI / scripts
Why this is viable now — four of five hard pieces already exist:
Clean engine boundary already enforced — core/ includes zerogui/ headers.
Model-aware control protocol already exists — AutomationServer speaks JSON ({"cmd":"get|set|invoke", "model","selector","property","value"}) over QLocalServer, with dumpTree introspection.
Streaming radio data over a wire already solved — PanadapterStream decodes VITA-49 (PCC 0x8003 → FFT bins → spectrumReady()); SmartLinkClient/WanConnection/OpusCodec already relay spectrum+audio over a network.
The models are the state — 29 QObject models with change signals.
Missing fifth piece (the bulk of the work): the GUI reaches into the engine through 116 distinct core//models/ headers — each becomes an enumerated protocol message.
Two channels:
Control/state plane (~80% built): extend AutomationServer with a hello/welcome version handshake, subscribe, snapshot-on-connect, and event deltas. Transport: QLocalServer (local) + WebSocket (remote/browser; Qt6::WebSockets already linked).
None expected — QLocalServer, WebSocket, and shared memory are all Qt-portable; no platform-specific surface in the boundary. The split is mechanical and the dependency direction is already clean.
TX safety (Principle VI — non-negotiable)
A decoupled/scriptable UI is a new path to the transmitter. Mitigations are load-bearing:
The entire guard lives in aetherd, below the protocol boundary — the existing kTxKeyingProperty marker + ALLOW_TX logic gates every TX-keying verb; the client is never trusted.
TX arbitration — exactly one client may hold the TX lock; others read-only/denied. Explicit, audited acquire/release verb.
Per-client auth — remote clients authenticate; TX capability is a separate grant from RX/observe.
Acceptance bar: a malicious or buggy client must not be able to key the transmitter.
Alternatives considered
A — QML on libaethercore (in-process): extract the engine lib, expose models via Q_PROPERTY/Q_INVOKABLE, rebuild UI in runtime-loaded QML (hot-reloadable, no C++ recompile to restyle). Far cheaper — skips the entire data-plane serialization problem. Recommended if the goal is desktop re-styling only. (Models aren't QML-ready yet: 2/29 Q_PROPERTY, 0 Q_INVOKABLE.)
B — UI plugin .so via QPluginLoader: swap a compiled UI plugin without rebuilding core; still compiled C++, weak "no rebuild" payoff.
aetherd (this RFC): the only option delivering web/remote/multi-client/headless. Cost concentrated in the data plane.
Decision rule: choose aetherdiff the goal includes web/remote/multi-client/headless. Otherwise choose Alternative A.
Implementation scope (staged)
Extract libaethercore — split monolith into engine lib + thin gui shell (low-risk; shared prerequisite for both this RFC and Alternative A; speeds incremental builds immediately).
Promote AutomationServer into the versioned control protocol (handshake/subscribe/snapshot/deltas).
Engine-side TX arbitration + per-client auth — landed with step 2, never after.
Binary data plane — shm ring buffer (local) then binary-over-WebSocket (remote).
One reference thin client — port a slice of the existing Qt UI to prove the surface is complete.
Web UI / additional clients fall out of the proven protocol.
Open questions for reviewers
Is the real goal remote/web/multi-client (→ aetherd) or desktop re-styling (→ Alternative A)? This gates everything.
Pan/slice creation by clients — shared mutation, or attach-only?
Auth model for remote clients — reuse SmartLink identity, or a separate aetherd credential?
Is a headless-at-the-shack topology (run aetherd next to the radio, thin clients anywhere) a target use case, or is local-only sufficient for v1?
Preflight
Problem
The UI cannot be changed without rebuilding the whole app, and there is no way to run a different UI technology (web, remote thin client, TUI) against the engine.
AetherSDR is today a single monolithic executable —
add_executable(AetherSDR ${ALL_SOURCES})compilesmodels/+core/+gui/into one binary — and the UI is 100% imperative QWidget (128QWidgetsubclasses, 0 QML). Any UI change relinks everything, and "a different UI" isn't expressible at all.Proposal
Split AetherSDR into a headless engine daemon (
aetherd) that owns the radio connection and all ofcore/+models/(extracted into a newlibaethercore), plus thin UI clients (Qt desktop, browser/web, TUI, scripting) that speak a versioned protocol.Why this is viable now — four of five hard pieces already exist:
core/includes zerogui/headers.AutomationServerspeaks JSON ({"cmd":"get|set|invoke", "model","selector","property","value"}) overQLocalServer, withdumpTreeintrospection.PanadapterStreamdecodes VITA-49 (PCC 0x8003 → FFT bins → spectrumReady());SmartLinkClient/WanConnection/OpusCodecalready relay spectrum+audio over a network.QObjectmodels with change signals.Missing fifth piece (the bulk of the work): the GUI reaches into the engine through 116 distinct
core//models/headers — each becomes an enumerated protocol message.Two channels:
AutomationServerwith ahello/welcomeversion handshake,subscribe, snapshot-on-connect, andeventdeltas. Transport:QLocalServer(local) + WebSocket (remote/browser;Qt6::WebSocketsalready linked).Cross-platform impact
None expected —
QLocalServer, WebSocket, and shared memory are all Qt-portable; no platform-specific surface in the boundary. The split is mechanical and the dependency direction is already clean.TX safety (Principle VI — non-negotiable)
A decoupled/scriptable UI is a new path to the transmitter. Mitigations are load-bearing:
aetherd, below the protocol boundary — the existingkTxKeyingPropertymarker +ALLOW_TXlogic gates every TX-keying verb; the client is never trusted.forceUnkey()(incl. CWX-buffer abort, R&D: agent-drivable test surface for cross-OS panadapter/applet UX testing #3646).Acceptance bar: a malicious or buggy client must not be able to key the transmitter.
Alternatives considered
libaethercore(in-process): extract the engine lib, expose models viaQ_PROPERTY/Q_INVOKABLE, rebuild UI in runtime-loaded QML (hot-reloadable, no C++ recompile to restyle). Far cheaper — skips the entire data-plane serialization problem. Recommended if the goal is desktop re-styling only. (Models aren't QML-ready yet: 2/29Q_PROPERTY, 0Q_INVOKABLE.).soviaQPluginLoader: swap a compiled UI plugin without rebuilding core; still compiled C++, weak "no rebuild" payoff.aetherd(this RFC): the only option delivering web/remote/multi-client/headless. Cost concentrated in the data plane.Decision rule: choose
aetherdiff the goal includes web/remote/multi-client/headless. Otherwise choose Alternative A.Implementation scope (staged)
libaethercore— split monolith into engine lib + thin gui shell (low-risk; shared prerequisite for both this RFC and Alternative A; speeds incremental builds immediately).AutomationServerinto the versioned control protocol (handshake/subscribe/snapshot/deltas).Open questions for reviewers
aetherd) or desktop re-styling (→ Alternative A)? This gates everything.aetherdcredential?aetherdnext to the radio, thin clients anywhere) a target use case, or is local-only sufficient for v1?cc @jensenpat @chibondking @NF0T @rfoust — interested in your take on Q1 especially.
🤖 Generated with Claude Code