Second SDR support: ADS-B (dump1090) / AIS (AIS-catcher) / dual-OP25, configurable client+server #9

Open
opened 2026-09-20 15:41:59 -04:00 by logan · 1 comment
Owner

Motivation: one SDR (op25) stays the required baseline. When a second SDR is physically present, unlock additional capability, configurable per-node from both the edge dashboard and the C2 server: ADS-B, AIS, or a second OP25 instance (dual P25/analog).

Greenfield when scoped — confirmed via full sweep, zero existing mentions in DEFERRED.md/SAAS_PLAN.md/GOALS.md or code comments in either repo. server-26#111 is the related but separate pre-existing bug (primary-SDR hardware config gets clobbered on system reassignment) — worth reading first so the new secondary_sdr_mode field doesn't repeat that mistake.

Plumbing — done, this session:

  • NodeConfig.secondary_sdr_mode field (none|adsb|ais|op25_2) added, Client/drb-edge-node/app/models.py
  • on_config_push (Client/drb-edge-node/app/main.py) applies it independently of system reassignment, same pattern as hardware_preset/ppm_override — does NOT get wiped when a system is (re)assigned
  • New GET /op25/devices endpoint (Client/op25-container/app/routers/op25_controller.py) — lsusb-based SDR count, same match heuristic as install.sh's existing one-shot check. Requires usbutils added to op25-container/Dockerfile
  • Edge-node checkin (_publish_checkin, Client/drb-edge-node/app/internal/mqtt_manager.py) now reports sdr_count + secondary_sdr_mode up to the server — first node-initiated hardware-report channel (everything before this was C2 pushing config down)
  • Server NodeRecord (Server/drb-c2-core/app/models.py) gains both fields; _handle_checkin (Server/drb-c2-core/app/internal/mqtt_handler.py) ingests them; PATCH /nodes/{id} (Server/drb-c2-core/app/routers/nodes.py, NodeUpdateBody) accepts and re-pushes secondary_sdr_mode
  • Verified: server-26 pytest 401/401, node-26 pytest 185/185, node-26 flake8 clean

Not built yet:

  • Actual dump1090 container (ADS-B) — decoder choice made: dump1090
  • Actual AIS-catcher container (AIS) — decoder choice made: AIS-catcher
  • Second op25 instance for op25_2 mode
  • Serial-level device binding — op25's DeviceConfig.args still defaults to the literal string "rtl" (Client/op25-container/app/models.py:92), no per-device serial assignment exists; the new /op25/devices endpoint counts SDRs but doesn't yet let you pin one to a specific consumer (op25 vs the new decoder)
  • Server/C2 UI toggle for secondary_sdr_mode (API accepts it now, no frontend control yet)
  • docker-compose services for the new decoders, spun up conditionally on secondary_sdr_mode

Cross-repo: server-26 side (schema + API) already covered above; this is the tracking issue for the client-side hardware/container work plus the frontend toggle.

Motivation: one SDR (op25) stays the required baseline. When a second SDR is physically present, unlock additional capability, configurable per-node from both the edge dashboard and the C2 server: ADS-B, AIS, or a second OP25 instance (dual P25/analog). Greenfield when scoped — confirmed via full sweep, zero existing mentions in DEFERRED.md/SAAS_PLAN.md/GOALS.md or code comments in either repo. server-26#111 is the *related but separate* pre-existing bug (primary-SDR hardware config gets clobbered on system reassignment) — worth reading first so the new secondary_sdr_mode field doesn't repeat that mistake. **Plumbing — done, this session:** - `NodeConfig.secondary_sdr_mode` field (none|adsb|ais|op25_2) added, Client/drb-edge-node/app/models.py - `on_config_push` (Client/drb-edge-node/app/main.py) applies it independently of system reassignment, same pattern as hardware_preset/ppm_override — does NOT get wiped when a system is (re)assigned - New `GET /op25/devices` endpoint (Client/op25-container/app/routers/op25_controller.py) — lsusb-based SDR count, same match heuristic as install.sh's existing one-shot check. Requires `usbutils` added to op25-container/Dockerfile - Edge-node checkin (`_publish_checkin`, Client/drb-edge-node/app/internal/mqtt_manager.py) now reports `sdr_count` + `secondary_sdr_mode` up to the server — first node-initiated hardware-report channel (everything before this was C2 pushing config down) - Server `NodeRecord` (Server/drb-c2-core/app/models.py) gains both fields; `_handle_checkin` (Server/drb-c2-core/app/internal/mqtt_handler.py) ingests them; `PATCH /nodes/{id}` (Server/drb-c2-core/app/routers/nodes.py, NodeUpdateBody) accepts and re-pushes `secondary_sdr_mode` - Verified: server-26 pytest 401/401, node-26 pytest 185/185, node-26 flake8 clean **Not built yet:** - Actual dump1090 container (ADS-B) — decoder choice made: dump1090 - Actual AIS-catcher container (AIS) — decoder choice made: AIS-catcher - Second op25 instance for op25_2 mode - Serial-level device binding — op25's DeviceConfig.args still defaults to the literal string "rtl" (Client/op25-container/app/models.py:92), no per-device serial assignment exists; the new /op25/devices endpoint counts SDRs but doesn't yet let you pin one to a specific consumer (op25 vs the new decoder) - Server/C2 UI toggle for secondary_sdr_mode (API accepts it now, no frontend control yet) - docker-compose services for the new decoders, spun up conditionally on secondary_sdr_mode Cross-repo: server-26 side (schema + API) already covered above; this is the tracking issue for the client-side hardware/container work plus the frontend toggle.
Author
Owner

ADS-B and AIS are now wired end to end (both node-side and server-side), decision made to also push to the map, not just node-local:

node-26 commits: 523a948 (plumbing), e880d50 (ADS-B/dump1090 container), da7cec7 (AIS-catcher support)
server-26 commits: 8892e82 (plumbing), 5537b09 (ADS-B telemetry+map), 3d2b722 (AIS telemetry+map)

What's live: secondary-sdr-container (new) runs dump1090 or AIS-catcher against SDR index 1, controlled by secondary_sdr_mode (none|adsb|ais|op25_2) same as hardware_preset/ppm_override so it survives system reassignment. edge-node starts/stops/resumes it and polls its /secondary/data every 10s, POSTing non-empty snapshots to new C2 endpoints POST /telemetry/adsb and /telemetry/ais (node-key authed). Those upsert one Firestore doc per icao/mmsi into new aircraft/vessels collections (org_id stamped, docInMyOrg() firestore.rules added — manual deploy required, see infra/firestore/firestore.rules header). Frontend MapView has two new opt-in overlays (unchecked by default, like Weather Radar) fed by useAircraft()/useVessels() Firestore listeners.

Still open, not done this pass:

  • Serial-level device binding (SDR index 1 is hardcoded — hot-unplug/replug can swap which physical dongle is index 0 vs 1)
  • op25_2 mode (second op25 instance for dual P25/analog) — secondary_sdr_mode accepts it but nothing handles it yet
  • C2/admin UI control to actually SET secondary_sdr_mode on a node (the API accepts PATCH /nodes/{id} with it, no frontend button yet)
  • UNVERIFIED against real hardware: the secondary-sdr-container has not been built or run in this session (sandboxed authoring box, no docker). dump1090's aircraft.json field names and AIS-catcher's JSON stdout field names are believed correct from published docs, not confirmed against a real capture. Build + hardware smoke test before this reaches a real node.
  • Frontend changes are unverified via typecheck — no Node.js/npm on this authoring box currently (see CLAUDE.md Testing reality).
ADS-B and AIS are now wired end to end (both node-side and server-side), decision made to also push to the map, not just node-local: node-26 commits: 523a948 (plumbing), e880d50 (ADS-B/dump1090 container), da7cec7 (AIS-catcher support) server-26 commits: 8892e82 (plumbing), 5537b09 (ADS-B telemetry+map), 3d2b722 (AIS telemetry+map) What's live: secondary-sdr-container (new) runs dump1090 or AIS-catcher against SDR index 1, controlled by secondary_sdr_mode (none|adsb|ais|op25_2) same as hardware_preset/ppm_override so it survives system reassignment. edge-node starts/stops/resumes it and polls its /secondary/data every 10s, POSTing non-empty snapshots to new C2 endpoints POST /telemetry/adsb and /telemetry/ais (node-key authed). Those upsert one Firestore doc per icao/mmsi into new `aircraft`/`vessels` collections (org_id stamped, docInMyOrg() firestore.rules added — manual deploy required, see infra/firestore/firestore.rules header). Frontend MapView has two new opt-in overlays (unchecked by default, like Weather Radar) fed by useAircraft()/useVessels() Firestore listeners. Still open, not done this pass: - Serial-level device binding (SDR index 1 is hardcoded — hot-unplug/replug can swap which physical dongle is index 0 vs 1) - op25_2 mode (second op25 instance for dual P25/analog) — secondary_sdr_mode accepts it but nothing handles it yet - C2/admin UI control to actually SET secondary_sdr_mode on a node (the API accepts PATCH /nodes/{id} with it, no frontend button yet) - UNVERIFIED against real hardware: the secondary-sdr-container has not been built or run in this session (sandboxed authoring box, no docker). dump1090's aircraft.json field names and AIS-catcher's JSON stdout field names are believed correct from published docs, not confirmed against a real capture. Build + hardware smoke test before this reaches a real node. - Frontend changes are unverified via typecheck — no Node.js/npm on this authoring box currently (see CLAUDE.md Testing reality).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#9