@nigelfenton dropped a thoughtful design brief on multi-radio support in a single AetherSDR instance — opening the discussion here so the team can sound off, the recommendation can be sharpened, and any agreed direction has somewhere to live as a tracking issue.
The full v0.1 brief follows below verbatim. My review notes (suggestions for v0.2) will follow in a separate comment so they're clearly separable from Nigel's text.
AetherSDR — One Instance, Two Radios: a brief for the dev group
- Status: v0.1, discussion-starter — not a spec, not a PR, not a commitment.
- Date: 2026-06-06
- From: Nigel — G0JKN (with Claude as foundations-first reading partner)
- Audience: Jeremy [KK7GWY], Pat [jensenpat], CJ [WT2P], Ryan [NF0T], Robbie [rfoust] — share more widely as the team sees fit.
- Companion docs (Windows dev box, available on request):
aethersdr-multi-radio-research-v0.1-2026-05-28.md — verified architecture findings, [verified] vs [open] tags.
aethersdr-multi-radio-study-notes.md — bottom-up 7-layer syllabus through the codebase.
What this is
Jeremy asked in dev chat 2026-05-28 for "a single instance of AetherSDR to be able to connect to multiple radios at the same time." CJ flagged QA surface doubling. The two companion docs above are the deep-read that came out of that thread.
This brief boils them down to one decision the team would need to make first, and the reasoning that arrived at a recommendation.
The crux: how does the protocol surface expose a second radio?
Multi-radio is largely tractable except for one design choice that has to be made upfront, because it has knock-on effects on every existing AE-aware client (WSJT-X, JTDX, aether-pad, Stream Deck / Ulanzi plugins, ShackController, RF2K-S driver, ShackLog, third-party TCI bridges). Two camps:
Camp A — multiplex inside one port
Extend TCI (and CAT) to carry a radio identity. e.g. repurpose trx to encode (radio_index, receiver_index), or add a new radio field.
- Pro: one port. AE feels like one cohesive process from the network side.
- Con: every existing TCI/CAT client breaks until updated by its respective maintainer. That's not "let's coordinate a flag day" — that's "lives unmerged in dev chat for two years."
- Con: redesigning a public protocol is a multi-month design-by-committee path. The cost is mostly not writing AE code; it's writing the spec everyone can agree on.
Camp B — namespace by port
Run a second TCI server (and a second rigctld) on a different port. e.g. Radio 1 on :40001 / :4532, Radio 2 on :40011 / :4533. Each server is the existing unchanged single-radio server class, just bound to a different RadioModel*.
- Pro: zero breakage for existing clients. WSJT-X pointed at
:4533 thinks it's the only thing in the world. aether-pad on :40001 still works. Plugins still work. The whole TCI ecosystem keeps functioning, untouched.
- Pro: internally, two copies of code that already exists and is already tested. No new protocol surface. No "what does
trx mean now" discussions.
- Pro: consistent with the existing data plane. AE already opens one VITA-49 spectrum stream per radio with no protocol multiplexing — control should follow.
- Pro: incremental & opt-in. The second TCI server only exists when the user enables a second radio. Existing single-radio setups are byte-for-byte unchanged.
- Pro: Hamlib has always worked this way for CAT. One
rigctld per rig, on a port the user picks. Every CAT-using app already has a "rigctld port" field. There is literally no client-side change needed for CAT — the user types the second port number.
- Con: discovery: how does a client know
:40011 exists? Convention + an enumerator call somewhere. Small problem, not a blocking one.
- Con: two server instances inside AE means a small amount of bookkeeping (lifetime, shutdown order, cleanup on disconnect). Real but well-contained.
- Con: UI orchestration in MainWindow grows — "which radio is the active one for which panel" becomes a real concept. (See architecture section.)
Recommendation: Camp B
The argument that closes it: AE's data plane already does "one stream per radio, no multiplexing." Doing the same on the control plane is consistency, not novelty.
What the architecture actually demands (the encouraging part)
The 7-layer bottom-up read says the atom is already right:
RadioModel is not a singleton. Construct as many as you want. The header literally says it's "the central data model for a connected radio" — singular by intent, not by hidden assumption.
- Per-radio capability state (slice caps, max pans, pan bandwidth — 6700 vs. 6300 etc.) already lives on the model, seeded from the radio's model string at connect. Two sessions get correct independent limits automatically.
- Most dialogs/helpers take
RadioModel* by pointer. They're already dependency-injected, not reaching for a global. The DI shape is right.
- Panels use composition + Qt signal/slot — they each hold a
RadioModel* and connect their slots to its signals. Hand a panel a different RadioModel* and it just works. Zero changes to panel internals.
The walls are concentrated and identifiable:
MainWindow.h:420 — RadioModel m_radioModel; embedded by value, not a list. This is the single biggest assumption to lift; once it's a QList<RadioModel*> (or keyed map), the cascade is contained.
MainWindow.cpp:4612 — TciServer is constructed with &m_radioModel. Multi-radio = construct N of them, each with the right model and port.
- Same shape for the CAT side.
There is no global RadioModel::instance(). There is no inheritance refactor to do. The work is MainWindow orchestration + a thin "active radio" UI concept + the two-server lifetime bookkeeping, not a thousand-cuts patch across panels.
Honest estimate
Solo focused dev, broken into a PR series:
- Plumbing (RadioModel collection, MainWindow ownership): a couple of weeks.
- UI orchestration ("active radio" concept, tab/split, status chrome): a few weeks.
- TCI/CAT lifetime (two server instances per AE process, clean up on disconnect): a week-ish.
- Settings/persistence (per-radio namespacing of UUID, station, slice positions): a few days.
- QA matrix (every existing TCI integration validated with two radios): as long as a piece of string. Probably weeks.
Order-of-magnitude: 1–3 months for a usable v1, shipped as 6–10 atomic PRs. CJ's "2× QA" concern is real and built into the estimate.
What week 1 looks like
Week 1 is not code. It is:
- Agree this brief (or the next revision of it) with the team.
- Decide the discovery convention for "client wants to enumerate AE's radios." Probably a small TCI call that returns
[{radio_id, label, scu_count, ports}, …]. Small surface; needs design + agreement, not weeks of work.
- Sketch the AE-side config for "AE knows it should host a second TCI/rigctld for the second radio" (config file or UI flow).
If week 1 lands well, weeks 2–N are well-defined incremental PRs.
What this brief deliberately does not do
- Doesn't propose code.
- Doesn't claim multi-radio is "easy" — it's not, especially the QA matrix.
- Doesn't pretend Camp A is impossible — it's just costly enough that the path-of-least-pain (B) wins on engineering grounds.
- Doesn't presume to volunteer anyone's time. The team picks who and when.
Open questions for the team
- Does the consistency argument (data plane already does N-per-radio) feel right as the framing?
- Is there a TCI design constraint we've missed — something that forces multiplexing — that would push us back to Camp A?
- Are there other paths (Camp C?) worth thinking about that we haven't seen?
- If Camp B is the path, who's the natural lead on the discovery-protocol design conversation?
— Nigel · G0JKN
Happy to share the underlying research docs and the architecture syllabus we built before writing this. Both are on a Windows dev box and easy to send.
@nigelfenton dropped a thoughtful design brief on multi-radio support in a single AetherSDR instance — opening the discussion here so the team can sound off, the recommendation can be sharpened, and any agreed direction has somewhere to live as a tracking issue.
The full v0.1 brief follows below verbatim. My review notes (suggestions for v0.2) will follow in a separate comment so they're clearly separable from Nigel's text.
AetherSDR — One Instance, Two Radios: a brief for the dev group
aethersdr-multi-radio-research-v0.1-2026-05-28.md— verified architecture findings, [verified] vs [open] tags.aethersdr-multi-radio-study-notes.md— bottom-up 7-layer syllabus through the codebase.What this is
Jeremy asked in dev chat 2026-05-28 for "a single instance of AetherSDR to be able to connect to multiple radios at the same time." CJ flagged QA surface doubling. The two companion docs above are the deep-read that came out of that thread.
This brief boils them down to one decision the team would need to make first, and the reasoning that arrived at a recommendation.
The crux: how does the protocol surface expose a second radio?
Multi-radio is largely tractable except for one design choice that has to be made upfront, because it has knock-on effects on every existing AE-aware client (WSJT-X, JTDX, aether-pad, Stream Deck / Ulanzi plugins, ShackController, RF2K-S driver, ShackLog, third-party TCI bridges). Two camps:
Camp A — multiplex inside one port
Extend TCI (and CAT) to carry a radio identity. e.g. repurpose
trxto encode(radio_index, receiver_index), or add a newradiofield.Camp B — namespace by port
Run a second TCI server (and a second rigctld) on a different port. e.g. Radio 1 on
:40001/:4532, Radio 2 on:40011/:4533. Each server is the existing unchanged single-radio server class, just bound to a differentRadioModel*.:4533thinks it's the only thing in the world. aether-pad on:40001still works. Plugins still work. The whole TCI ecosystem keeps functioning, untouched.trxmean now" discussions.rigctldper rig, on a port the user picks. Every CAT-using app already has a "rigctld port" field. There is literally no client-side change needed for CAT — the user types the second port number.:40011exists? Convention + an enumerator call somewhere. Small problem, not a blocking one.Recommendation: Camp B
The argument that closes it: AE's data plane already does "one stream per radio, no multiplexing." Doing the same on the control plane is consistency, not novelty.
What the architecture actually demands (the encouraging part)
The 7-layer bottom-up read says the atom is already right:
RadioModelis not a singleton. Construct as many as you want. The header literally says it's "the central data model for a connected radio" — singular by intent, not by hidden assumption.RadioModel*by pointer. They're already dependency-injected, not reaching for a global. The DI shape is right.RadioModel*and connect their slots to its signals. Hand a panel a differentRadioModel*and it just works. Zero changes to panel internals.The walls are concentrated and identifiable:
MainWindow.h:420—RadioModel m_radioModel;embedded by value, not a list. This is the single biggest assumption to lift; once it's aQList<RadioModel*>(or keyed map), the cascade is contained.MainWindow.cpp:4612—TciServeris constructed with&m_radioModel. Multi-radio = construct N of them, each with the right model and port.There is no global
RadioModel::instance(). There is no inheritance refactor to do. The work is MainWindow orchestration + a thin "active radio" UI concept + the two-server lifetime bookkeeping, not a thousand-cuts patch across panels.Honest estimate
Solo focused dev, broken into a PR series:
Order-of-magnitude: 1–3 months for a usable v1, shipped as 6–10 atomic PRs. CJ's "2× QA" concern is real and built into the estimate.
What week 1 looks like
Week 1 is not code. It is:
[{radio_id, label, scu_count, ports}, …]. Small surface; needs design + agreement, not weeks of work.If week 1 lands well, weeks 2–N are well-defined incremental PRs.
What this brief deliberately does not do
Open questions for the team
— Nigel · G0JKN
Happy to share the underlying research docs and the architecture syllabus we built before writing this. Both are on a Windows dev box and easy to send.