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
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.
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).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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_modefield (none|adsb|ais|op25_2) added, Client/drb-edge-node/app/models.pyon_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)assignedGET /op25/devicesendpoint (Client/op25-container/app/routers/op25_controller.py) — lsusb-based SDR count, same match heuristic as install.sh's existing one-shot check. Requiresusbutilsadded to op25-container/Dockerfile_publish_checkin, Client/drb-edge-node/app/internal/mqtt_manager.py) now reportssdr_count+secondary_sdr_modeup to the server — first node-initiated hardware-report channel (everything before this was C2 pushing config down)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-pushessecondary_sdr_modeNot built yet:
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.
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/vesselscollections (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: