Every sdr.py subcommand, grouped by domain. Run from project root:
venv/bin/python3 src/sdr.py <subcommand> [options]
Add --gps --gps-port /dev/ttyACM<N> to any scanner to stamp detections with GPS coordinates. sudo is required for BLE and WiFi.
| Command | Frequency | Description |
|---|
pmr | 446 MHz | PMR446 — 8 analog channels, FM demod, audio recording, Whisper transcription. --digital adds dPMR/DMR energy detection. |
fm <band> | configurable | Generic FM scanner with band profiles, auto window hopping. --list shows all band profiles. |
fm frs | 462–467 MHz | 22 FRS/GMRS channels |
fm marine | 156–162 MHz | 27 maritime channels incl. CH16 distress |
fm murs | 151–154 MHz | 5 MURS channels |
fm 2m | 144–148 MHz | VHF amateur FM simplex |
fm 70cm | 430–446 MHz | UHF amateur FM simplex |
fm cb | 26.965–27.405 MHz | EU FM CB, 40 channels |
fm landmobile | 157–163 MHz | Rail, port ops, utilities, security (22 channels) |
fm tetra | 380–400 MHz | EU police/fire/EMS — energy detection only, no decode |
fm tetra-priv | 410–430 MHz | Utilities/private security — energy detection only |
fm p25 | VHF/UHF | US public-safety activity — energy detection only |
Common FM flags:
sdr.py pmr --transcribe # speech-to-text via Whisper / OpenAI
sdr.py pmr --transcribe --language es # force Spanish transcription
sdr.py pmr --digital # analog + dPMR/DMR energy detection
| Command | Frequency | Description |
|---|
keyfob | 315/433.92 MHz | OOK car keyfob + garage door signal detection. -f 315 for US. |
tpms | 315/433.92 MHz | Tire pressure sensor decoding — sensor IDs, Manchester OOK. |
ism | 433/868/915 MHz | rtl_433 wrapper, 200+ protocols (weather stations, smart home, remotes). --hop sweeps ISM bands. |
| Command | Frequency | Description |
|---|
gsm | 935–960 MHz (+ 850 MHz band) | GSM cell tower scanning, FCCH beacon detection. Reports ARFCN + SNR. |
lte | 700–2600 MHz | LTE uplink power density measurement above baseline. |
| Command | Frequency | Description |
|---|
adsb | 1090 MHz | Aircraft tracking (Mode S: ICAO, callsign, altitude, speed). Requires readsb built with RTLSDR=yes. Per-message RSSI is captured from the aircraft.json feed (dump1090-fa / readsb --write-json), so the ADS-B calibration source lights up automatically with sky view. |
ais | 161.975 / 162.025 MHz | Vessel tracking (MMSI, position, speed, course). Uses rtl_ais; native Python decoder is educational only. --rssi-device-index <N> optionally starts a parallel RSSI sampler on a second RTL-SDR so AIS detections carry real power_db (rtl_ais holds the primary SDR and has no NMEA RSSI field). Without the flag, power_db=0 and AIS calibration stays dormant. |
| Command | Frequency | Description |
|---|
pocsag | 152–929 MHz | Pager messages (numeric + alphanumeric). Uses multimon-ng. Most networks decommissioned. |
lora | 868/915 MHz | LoRa / Meshtastic chirp detection, bandwidth and duty cycle. Regional defaults via `--region us |
mesh | 868 MHz (EU) | Passive decode of Meshtastic mesh traffic (position, telemetry, node info, text). Requires a Meshtastic radio on USB. |
| Command | Frequency | Description |
|---|
wifi | 2.4 + 5 GHz | Probe-request sniffing, beacon capture, drone RemoteID. Monitor-mode adapter + sudo required. `--band 2.4 |
bt | 2.4 GHz | BLE advertisements, Apple Continuity, persona DB, drone RemoteID. HCI adapter + sudo. --adapter hci1. |
dv | 5.8 / 2.4 GHz | Drone video downlinks (DJI O4, OcuSync). HackRF wideband. |
fpv | 1.2 / 5.8 GHz | FPV analog video frame demodulation (PAL/NTSC). HackRF. |
| Command | Description |
|---|
scan | Wideband energy detection. --classify adds automatic modulation classification (FM, OOK, FSK, PSK, QAM, OFDM, FHSS, CW). |
jammer | Watch a configurable band list (GPS L1, Meshtastic EU, ISM 915, GSM-900 DL, Marine VHF by default) for broadband noise-floor elevation with spectral flatness > 0.5 — the signature of a cheap noise jammer, barrage jammer, or sweep jammer. Per-band baseline is acquired at startup and persisted to output/jammer_baseline.json. Override bands with --band LABEL:CENTER_MHZ:BW_MHZ (repeatable). Does not catch narrowband/spot jammers (loud legit emitters are common) or spoofing (fake-but-valid signals need protocol-level integrity checks — different feature). |
record | Capture raw IQ samples to disk. |
replay <file> | Re-run detection pipelines against a recorded IQ file; spectrogram/spectrum/IQ plots. |
| Command | Description |
|---|
server <config.json> | Multi-capture orchestrator — runs many captures + parsers in parallel from a JSON config. See configuration.md. Add --web for the dashboard. |
web | Standalone dashboard (reads detection DBs from output dir). -p 3000 for custom port. |
agent | Meshtastic C2 agent runtime. See c2.md. |
replay-c2 <db> --agent-id N99 | Replay a recorded detection .db over the Meshtastic link as if from a live agent. --rate <Hz>, --require-position, --require-power, --dry-run. See c2.md. |
| Command | Description |
|---|
tri a.db b.db c.db | RSSI multilateration from multi-node session DBs. See triangulation.md. Applies per-node calibration offsets automatically when output/calibration.db is present. |
heatmap output/*.db | RF activity density heatmap (KML + PNG for ATAK). Filter with -s <signal_type>. |
corr output/*.db | Cross-signal-type device co-occurrence analysis. Export with --json. |
calibrate {ingest,show,recompute,set-position,watch} | Solve per-node RSSI offsets from emitters whose position and TX power are known (surveyed APs / FM / cell + passive ADS-B / AIS). See triangulation.md. |
jammer-detect output/*.db | Post-hoc opportunistic jamming detection from stored noise_floor_db series. Groups by (device_id, signal_type), flags sustained elevation against a rolling 1 h baseline. Complements the live jammer scanner: no SDR needed, but only covers bands where a scanner was actively running. Writes synthetic signal_type="jamming-inferred" rows so the dashboard's Jamming tab picks them up alongside live results. |
- RTL-SDR Blog V4 has ~16 ppm frequency offset — may assign a transmission to an adjacent channel (audio still correct).
- HackRF has ~17 ppm offset and ~10 dB less sensitivity than RTL-SDR; better for wideband, worse for weak narrowband.
readsb from Debian apt doesn't include RTL-SDR support. Build from source (see install.md).
- Keyfob is presence-based (OOK burst analysis), not protocol-level decoding. 433 MHz ISM is noisy — some false positives remain.
- TPMS has no checksum validation on decoded packets. Fixed to 433.92 MHz (EU) or 315 MHz (US), no auto-scan between.
- GSM reports ARFCN + signal strength only, no cell ID decoding (no gr-gsm integration). GSM-900 and GSM-850 only.
- TETRA / P25 are activity-only detectors (no decode).
- POCSAG pipeline works, but most pager networks are decommissioned.
- RF loopback audio on consumer SDRs hits ~0.25 cross-correlation ceiling because of phase noise.
- Path-loss triangulation has opportunistic calibration built in (
sdr.py calibrate); the Map tab's live fixes and uncertainty rings use it automatically. Sub-10 m accuracy still needs TDoA. See triangulation.md for specifics.
Lab: RTL-SDR Blog V4 (RX) and HackRF One (TX) a few centimetres apart, no cable or attenuator, HackRF at minimum power (VGA 0–20, amp off).
Field: RTL-SDR Blog V4 on a Raspberry Pi 4, telescopic whip antenna on a rooftop. AIS, ADS-B, Marine VHF, GSM and POCSAG verified against real-world signals; TAK integration verified with live vessels and aircraft.
bash tests/run_tests.sh # software-only, ~60 s on a Pi 5
bash tests/run_tests.sh --hw # include HackRF + RTL-SDR loopback tests
python3 tests/run_tests.py --no-whisper # skip transcription