Secondary SDR priority: every spare SDR runs the next decoder in an ordered list
Replaces the single secondary_sdr_mode with secondary_sdr_priority, e.g. ["adsb", "ais"]. OP25 always keeps its own dongle; the secondary-sdr container starts decoders top-down until it runs out of free SDRs, so a 3-SDR node runs ADS-B and AIS at once and a 2-SDR node runs the top pick. - secondary-sdr: one decoder per mode, POST /secondary/apply(priority) (no-op when the right prefix is already running), orphaned decoders from a uvicorn reload are reaped on start, /status reports sdr_count via lsusb (op25's :stable image has none). - edge-node: one apply path (set_secondary_priority) for the local dashboard, a new C2 'set_secondary_priority' MQTT command, and config pushes; it never restarts op25. Legacy mode migrates on load. Checkin reports priority, what's running, and sdr_count. Uplink forwards aircraft and vessels whenever either is present. - Local dashboard: 'Secondary SDRs' card to enable/reorder/save. Verified: edge-node pytest 190 passed, flake8 clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1c74edffea
commit
9b6c64fbbf
+14
-12
@@ -158,6 +158,9 @@ async def on_command(payload: dict):
|
||||
)
|
||||
elif action == "discord_leave":
|
||||
await radio_bot.leave()
|
||||
elif action == "set_secondary_priority":
|
||||
from app.internal.secondary_priority import set_secondary_priority
|
||||
await set_secondary_priority(payload.get("priority") or [])
|
||||
elif action == "op25_restart":
|
||||
from app.internal.op25_client import op25_client
|
||||
await op25_client.stop()
|
||||
@@ -224,7 +227,8 @@ async def on_config_push(payload: dict):
|
||||
hardware_preset = payload.pop("hardware_preset", None)
|
||||
ppm_override = payload.pop("ppm_override", None)
|
||||
node_type = payload.pop("node_type", None)
|
||||
secondary_sdr_mode = payload.pop("secondary_sdr_mode", None)
|
||||
secondary_sdr_priority = payload.pop("secondary_sdr_priority", None)
|
||||
secondary_sdr_mode = payload.pop("secondary_sdr_mode", None) # legacy single-mode C2
|
||||
enforce_override_timeout = payload.pop("enforce_override_timeout", None)
|
||||
try:
|
||||
config = SystemConfig(**payload)
|
||||
@@ -244,8 +248,6 @@ async def on_config_push(payload: dict):
|
||||
node_cfg.ppm_override = float(ppm_override)
|
||||
if node_type is not None:
|
||||
node_cfg.node_type = node_type
|
||||
if secondary_sdr_mode is not None:
|
||||
node_cfg.secondary_sdr_mode = secondary_sdr_mode
|
||||
if enforce_override_timeout is not None:
|
||||
node_cfg.enforce_override_timeout = bool(enforce_override_timeout)
|
||||
save_node_config(node_cfg)
|
||||
@@ -260,12 +262,11 @@ async def on_config_push(payload: dict):
|
||||
await op25_client.start()
|
||||
logger.info(f"Config push applied: {config.name}")
|
||||
|
||||
if secondary_sdr_mode is not None:
|
||||
from app.internal.secondary_sdr_client import secondary_sdr_client
|
||||
if secondary_sdr_mode in ("adsb", "ais"):
|
||||
await secondary_sdr_client.start(secondary_sdr_mode)
|
||||
else:
|
||||
await secondary_sdr_client.stop()
|
||||
if secondary_sdr_priority is None and secondary_sdr_mode is not None:
|
||||
secondary_sdr_priority = [secondary_sdr_mode]
|
||||
if secondary_sdr_priority is not None:
|
||||
from app.internal.secondary_priority import set_secondary_priority
|
||||
await set_secondary_priority(secondary_sdr_priority)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -322,10 +323,11 @@ async def lifespan(app: FastAPI):
|
||||
logger.warning(f"OP25 not ready yet (attempt {attempt + 1}/10), retrying in 3s…")
|
||||
await asyncio.sleep(3)
|
||||
|
||||
if node_cfg.secondary_sdr_mode in ("adsb", "ais"):
|
||||
# After op25 has claimed its dongle, so the decoders only get the spares.
|
||||
if node_cfg.secondary_sdr_priority:
|
||||
from app.internal.secondary_sdr_client import secondary_sdr_client
|
||||
logger.info(f"Resuming secondary SDR (mode={node_cfg.secondary_sdr_mode!r}) after restart.")
|
||||
await secondary_sdr_client.start(node_cfg.secondary_sdr_mode)
|
||||
logger.info(f"Resuming secondary SDRs (priority={node_cfg.secondary_sdr_priority!r}) after restart.")
|
||||
await secondary_sdr_client.apply(node_cfg.secondary_sdr_priority)
|
||||
|
||||
heartbeat_task = asyncio.create_task(mqtt_manager.heartbeat_loop())
|
||||
from app.internal.telemetry_uplink import telemetry_uplink_loop
|
||||
|
||||
Reference in New Issue
Block a user