Files
trx-rs/MULTI.md
T
sjg 758dde97a2 [docs](trx-rs): add MULTI.md progress tracking for multi-rig support
Documents architecture, TOML format, protocol wire format, validation
rules, and task completion status (MR-01 through MR-09).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-02-25 08:04:23 +01:00

5.3 KiB
Raw Blame History

Multi-Rig Support

This document specifies the requirements for running N simultaneous rig backends in one trx-server process and the protocol/config changes required to support them.


Progress

For AI agents: This section is the single source of truth for implementation status. Each task has a unique ID (e.g. MR-01), a status badge, a description, the files it touches, and any blocking dependencies.

Status legend: [ ] not started · [~] in progress · [x] done · [!] blocked

Foundational (parallel)

ID Status Task Files Needs
MR-01 [x] Add rig_id: Option<String> to ClientEnvelope; add rig_id: Option<String> to ClientResponse; add ClientCommand::GetRigs; add GetRigsResponseBody + RigEntry; add sentinel arm in mapping.rs src/trx-protocol/src/types.rs, mapping.rs, lib.rs
MR-02 [x] Add RigInstanceConfig; add rigs: Vec<RigInstanceConfig> to ServerConfig; implement resolved_rigs(); extend validate() for unique IDs + unique audio ports src/trx-server/src/config.rs
MR-03 [x] Remove four OnceLock statics from audio.rs; add DecoderHistories { aprs, ft8, wspr } struct + new(); convert history free-fns to take &DecoderHistories; update decoder task signatures + run_audio_listener src/trx-server/src/audio.rs
MR-04 [x] Create src/trx-server/src/rig_handle.rs with RigHandle { rig_id, rig_tx, state_rx }; declare mod in main.rs src/trx-server/src/rig_handle.rs, main.rs

Sequential

ID Status Task Files Needs
MR-05 [x] Add rig_id: String + histories: Arc<DecoderHistories> to RigTaskConfig; fix clear_*_history calls in process_command src/trx-server/src/rig_task.rs MR-03
MR-06 [x] Rewrite run_listener to take Arc<HashMap<String, RigHandle>> + default_rig_id; route by envelope.rig_id; add GetRigs fast path; populate rig_id in every ClientResponse src/trx-server/src/listener.rs MR-01, MR-04
MR-07 [x] Rewrite main.rs spawn loop over resolved_rigs(); extract spawn_rig_audio_stack(); per-rig pskreporter + aprsfi; build HashMap<String, RigHandle>; pass to run_listener src/trx-server/src/main.rs MR-0206

Tests

ID Status Task Files Needs
MR-08 [x] Config tests: resolved_rigs() with multi-rig TOML and legacy TOML; duplicate ID/port rejection src/trx-server/src/config.rs MR-02
MR-09 [x] Protocol tests: ClientEnvelope absent rig_id parses; rig_id in responses; GetRigs round-trip; existing tests still pass src/trx-protocol/src/codec.rs MR-01

Goals

  • Run N simultaneous rig backends (SDR, transceivers, or any mix) in one server process
  • Route control commands to the correct rig via rig_id in the JSON protocol
  • Backward compatibility: single-rig configs ([rig]/[audio] at top level) continue to work unchanged
  • Per-rig audio streaming on separate TCP ports
  • New GetRigs command to enumerate all connected rigs and their states

Non-Goals

  • Load-balancing or failover between rigs
  • Sharing a single audio port across multiple rigs (each rig keeps its own port)

Architecture

Single [listen] port (4530)
  └─ listener.rs: Arc<HashMap<rig_id, RigHandle>>
       ├─ route by envelope.rig_id  (absent → first rig, backward compat)
       └─ GetRigs → aggregate all states

Per-rig:
  rig_task ←→ RigHandle (rig_tx + state_rx)
  audio capture → pcm_tx → decoder tasks → decode_tx
  run_audio_listener (own TCP port per rig)
  pskreporter + aprsfi tasks

TOML Format

Multi-rig ([[rigs]] array)

[general]
callsign = "W1AW"

[listen]
port = 4530

[[rigs]]
id = "hf"
[rigs.rig]
model = "ft450d"
initial_freq_hz = 14074000
[rigs.rig.access]
type = "serial"
port = "/dev/ttyUSB0"
baud = 9600
[rigs.audio]
port = 4531

[[rigs]]
id = "sdr"
[rigs.rig]
model = "soapysdr"
[rigs.rig.access]
type = "sdr"
args = "driver=rtlsdr"
[rigs.audio]
port = 4532
[rigs.sdr]
sample_rate = 1920000

Legacy (flat [rig] + [audio]) — continues to work unchanged

[rig]
model = "ft817"
[rig.access]
type = "serial"
port = "/dev/ttyUSB0"
baud = 9600
[audio]
port = 4531

Legacy configs are synthesised into a single-element [[rigs]] list with id = "default" via resolved_rigs().


Protocol Wire Format

Request (rig_id optional; absent = first rig):

{"rig_id": "hf", "cmd": "set_freq", "freq_hz": 14074000}
{"cmd": "get_state"}

Response (rig_id always present):

{"success": true, "rig_id": "hf", "state": {...}}
{"success": false, "rig_id": "default", "error": "Unknown rig_id: xyz"}

GetRigs response:

{"success": true, "rig_id": "server", "rigs": [
  {"rig_id": "hf",  "state": {...}},
  {"rig_id": "sdr", "state": {...}}
]}

Validation Rules (startup)

  • When [[rigs]] is non-empty: each id must be unique (case-sensitive).
  • When [[rigs]] is non-empty: each audio.port must be unique.
  • When [[rigs]] is empty: legacy flat fields are used with id = "default".
  • Mixing [[rigs]] and legacy flat [rig]/[audio] is undefined; [[rigs]] takes precedence.