Second SDR: ADS-B (dump1090) / AIS (AIS-catcher) support #10

Open
logan wants to merge 0 commits from feat/second-sdr-adsb-ais into main
Owner

Closes node-26#9 (plumbing + ADS-B + AIS wiring; op25_2 mode and serial-based device binding remain open, see the issue).

Adds secondary-sdr-container, a new compose service that claims the node's SECOND physical SDR (RTL-SDR index 1 — op25 always claims index 0, no serial binding yet, same limitation op25 itself has). Its control API (mirroring op25_controller.py's start/stop/status shape) runs dump1090 for ADS-B or AIS-catcher for AIS, selected by the new secondary_sdr_mode node config field (none|adsb|ais|op25_2 — op25_2 not implemented yet). edge-node starts/stops/resumes it based on config and polls its /secondary/data every 10s, forwarding non-empty snapshots to the C2 server's new /telemetry/adsb and /telemetry/ais endpoints (server-26 PR, separate).

UNVERIFIED against real hardware — built from a sandboxed authoring machine with no docker/hardware access. dump1090's --write-json field names and AIS-catcher's JSON stdout field names are believed correct from published docs, not confirmed against a real capture. The AIS stdout-parsing merge logic (preserving a vessel's name across position-only updates) was unit-verified against a fake stdout stream, not real AIS-catcher output. Build + hardware smoke test before this reaches a real node.

Verified: drb-edge-node pytest 185/185, flake8 clean on both drb-edge-node/app and secondary-sdr-container/app.

Closes node-26#9 (plumbing + ADS-B + AIS wiring; op25_2 mode and serial-based device binding remain open, see the issue). Adds `secondary-sdr-container`, a new compose service that claims the node's SECOND physical SDR (RTL-SDR index 1 — op25 always claims index 0, no serial binding yet, same limitation op25 itself has). Its control API (mirroring op25_controller.py's start/stop/status shape) runs dump1090 for ADS-B or AIS-catcher for AIS, selected by the new `secondary_sdr_mode` node config field (none|adsb|ais|op25_2 — op25_2 not implemented yet). edge-node starts/stops/resumes it based on config and polls its `/secondary/data` every 10s, forwarding non-empty snapshots to the C2 server's new `/telemetry/adsb` and `/telemetry/ais` endpoints (server-26 PR, separate). **UNVERIFIED against real hardware** — built from a sandboxed authoring machine with no docker/hardware access. dump1090's `--write-json` field names and AIS-catcher's JSON stdout field names are believed correct from published docs, not confirmed against a real capture. The AIS stdout-parsing merge logic (preserving a vessel's name across position-only updates) was unit-verified against a fake stdout stream, not real AIS-catcher output. Build + hardware smoke test before this reaches a real node. Verified: drb-edge-node pytest 185/185, flake8 clean on both drb-edge-node/app and secondary-sdr-container/app.
logan added 3 commits 2026-09-20 19:11:24 -04:00
Adds a per-node secondary_sdr_mode config field (none|adsb|ais|op25_2),
applied the same way as hardware_preset/ppm_override so it survives system
reassignment. op25-container gets a GET /devices endpoint that counts
connected SDRs via lsusb; the edge-node checkin now reports sdr_count and
secondary_sdr_mode up to the server, the first node-initiated hardware
report (everything else was C2 pushing config down). Tracked as node-26#9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
node-26#9. New secondary-sdr-container claims the node's SECOND physical
SDR (RTL-SDR index 1 — op25 always claims index 0; no serial-based binding
yet, same gap op25 itself has). Its control API (start/stop/status/data,
mirroring op25_controller.py) launches dump1090 in adsb mode and exposes
the decoded aircraft.json snapshot; AIS mode 400s until it's wired next.

edge-node: on_config_push starts/stops it when secondary_sdr_mode changes,
lifespan resumes it after a restart if already configured, and a new
telemetry_uplink_loop polls its /secondary/data every 10s and POSTs
non-empty snapshots to C2's new /telemetry/adsb (same bearer-key pattern
call_recorder.py already uses for audio upload).

UNVERIFIED: this container has not been built or run against real hardware
in this session (sandboxed authoring machine, no docker) — dump1090's
--write-json field names are believed correct from its docs but not
confirmed against a real capture. Build + hardware smoke test before this
reaches a real node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire AIS end to end: AIS-catcher support in secondary-sdr container
CI / lint (push) Successful in 5s
CI / lint (pull_request) Successful in 5s
CI / test (push) Successful in 38s
CI / test (pull_request) Successful in 38s
52f31bbcc0
node-26#9. decoder_control.py's AIS mode now actually launches AIS-catcher
instead of 400ing: a background thread reads its stdout JSON stream
(one message per line) and keeps a live in-memory snapshot keyed by mmsi,
since AIS-catcher streams rather than writing a periodic file the way
dump1090's --write-json does. Messages are merged onto the existing entry
per mmsi rather than replacing it, since AIS-catcher emits static data
(name) and position reports (lat/lon) as separate message types — a naive
overwrite would blank the name back to null on every position-only update
(caught by a standalone unit check against a fake stdout stream before
this fix, not by pytest — this container has no test suite yet).

edge-node's telemetry_uplink_loop now posts non-empty vessel snapshots to
the new /telemetry/ais the same way it already does for aircraft.

UNVERIFIED against real hardware/binary in this session, same caveat as
the ADS-B commit — AIS-catcher's JSON field names are believed correct
from its docs, not confirmed against a real capture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Author
Owner

2026-09-27 review before first hardware test:

  • FIXED (uncommitted on feat/second-sdr-adsb-ais): antirez/dump1090 has no --write-json, so ADS-B mode would exit on start. Swapped to wiedehopf/readsb (--device-type rtlsdr --device 1), mapping now reads alt_baro/gs (dump1090-fa names) with old names as fallback.
  • SUSPECT, not fixed: AIS-catcher -d 1 selects by serial "1"; index selection is -d:1. -o JSON also unconfirmed. Check on hardware before AIS test.
  • Image still never built: docker not available to the agent session.
2026-09-27 review before first hardware test: - FIXED (uncommitted on feat/second-sdr-adsb-ais): antirez/dump1090 has no `--write-json`, so ADS-B mode would exit on start. Swapped to wiedehopf/readsb (`--device-type rtlsdr --device 1`), mapping now reads `alt_baro`/`gs` (dump1090-fa names) with old names as fallback. - SUSPECT, not fixed: AIS-catcher `-d 1` selects by *serial* "1"; index selection is `-d:1`. `-o JSON` also unconfirmed. Check on hardware before AIS test. - Image still never built: docker not available to the agent session.
logan added 1 commit 2026-09-27 12:34:21 -04:00
secondary-sdr: fix ADS-B on real hardware (tested on radio-box)
CI / lint (push) Successful in 6s
CI / lint (pull_request) Successful in 7s
CI / test (push) Successful in 39s
CI / test (pull_request) Successful in 40s
b54624e176
First hardware test of node-26#9/#10 found three blockers:
- antirez/dump1090 has no --write-json, so ADS-B mode exited on start.
  Swapped to wiedehopf/readsb; map alt_baro/gs (old names as fallback).
- op25 is not always on RTL-SDR index 0 (radio-box: op25 on 1, 0 free).
  start() now tries each index and keeps the first decoder that stays up.
  AIS-catcher index flag fixed to -d:N ("-d N" selects by serial).
- status() reported "running" for a decoder that died on startup: the
  zombie still answered killpg(pgid, 0). Liveness now via Popen.poll().

install.sh blacklists dvb_usb_rtl28xxu, which claimed the second dongle.

Verified on radio-box: 831 msgs/min, 6 aircraft (3 with position) on a
9cm whip; op25 unaffected. AIS still untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All checks were successful
CI / lint (push) Successful in 6s
CI / lint (pull_request) Successful in 7s
CI / test (push) Successful in 39s
CI / test (pull_request) Successful in 40s
Checking for merge conflicts…
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/second-sdr-adsb-ais:feat/second-sdr-adsb-ais
git checkout feat/second-sdr-adsb-ais
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#10