Node SDRs: pin OP25 and each service to a dongle by serial (node-26#11)

Pairs with node-26 feat/sdr-pins. NodeRecord gains sdr_pins
(service -> serial), sdr_devices and op25_sdr_serial, mirrored from the
node's checkin. PATCH /nodes/{id} validates pins (known services, one
dongle per service) and sends priority/pins as a 'set_sdr_config'
command, never a config re-push. The node restarts OP25 only when OP25's
own dongle changes. The node page's section becomes 'SDRs' with an OP25
SDR dropdown ('Automatic (first SDR)' + detected dongles) and a
per-service dongle dropdown ('Any spare SDR'), plus duplicate-serial and
double-pin warnings.

Verified: c2-core pytest 490 passed; frontend tsc --noEmit clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-09-27 14:36:49 -04:00
co-authored by Claude Opus 5.5
parent c95498e7fe
commit fb2d737d6f
9 changed files with 315 additions and 167 deletions
+5 -1
View File
@@ -114,6 +114,9 @@ class MQTTHandler:
"secondary_sdr_mode": payload.get("secondary_sdr_mode", "none"),
"secondary_sdr_priority": payload.get("secondary_sdr_priority", []),
"secondary_sdr_running": payload.get("secondary_sdr_running"),
"sdr_pins": payload.get("sdr_pins", {}),
"sdr_devices": payload.get("sdr_devices"),
"op25_sdr_serial": payload.get("op25_sdr_serial"),
"sdr_count": payload.get("sdr_count"), # None until reported, never a guessed 1
"enforce_override_timeout": payload.get("enforce_override_timeout", True),
"is_overridden": False,
@@ -145,7 +148,8 @@ class MQTTHandler:
updates["node_type"] = node_type
updates["enforce_override_timeout"] = enforce_timeout
for key in ("secondary_sdr_mode", "secondary_sdr_priority", "secondary_sdr_running"):
for key in ("secondary_sdr_mode", "secondary_sdr_priority", "secondary_sdr_running",
"sdr_pins", "sdr_devices", "op25_sdr_serial"):
if key in payload:
updates[key] = payload[key]
if "sdr_count" in payload: