Scanners

April 19, 2026 · View on GitHub

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.

Voice / FM

CommandFrequencyDescription
pmr446 MHzPMR446 — 8 analog channels, FM demod, audio recording, Whisper transcription. --digital adds dPMR/DMR energy detection.
fm <band>configurableGeneric FM scanner with band profiles, auto window hopping. --list shows all band profiles.
fm frs462–467 MHz22 FRS/GMRS channels
fm marine156–162 MHz27 maritime channels incl. CH16 distress
fm murs151–154 MHz5 MURS channels
fm 2m144–148 MHzVHF amateur FM simplex
fm 70cm430–446 MHzUHF amateur FM simplex
fm cb26.965–27.405 MHzEU FM CB, 40 channels
fm landmobile157–163 MHzRail, port ops, utilities, security (22 channels)
fm tetra380–400 MHzEU police/fire/EMS — energy detection only, no decode
fm tetra-priv410–430 MHzUtilities/private security — energy detection only
fm p25VHF/UHFUS 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

Short-burst / OOK

CommandFrequencyDescription
keyfob315/433.92 MHzOOK car keyfob + garage door signal detection. -f 315 for US.
tpms315/433.92 MHzTire pressure sensor decoding — sensor IDs, Manchester OOK.
ism433/868/915 MHzrtl_433 wrapper, 200+ protocols (weather stations, smart home, remotes). --hop sweeps ISM bands.

Cellular

CommandFrequencyDescription
gsm935–960 MHz (+ 850 MHz band)GSM cell tower scanning, FCCH beacon detection. Reports ARFCN + SNR.
lte700–2600 MHzLTE uplink power density measurement above baseline.

Transport layer

CommandFrequencyDescription
adsb1090 MHzAircraft 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.
ais161.975 / 162.025 MHzVessel 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.

Paging / mesh

CommandFrequencyDescription
pocsag152–929 MHzPager messages (numeric + alphanumeric). Uses multimon-ng. Most networks decommissioned.
lora868/915 MHzLoRa / Meshtastic chirp detection, bandwidth and duty cycle. Regional defaults via `--region us
mesh868 MHz (EU)Passive decode of Meshtastic mesh traffic (position, telemetry, node info, text). Requires a Meshtastic radio on USB.

2.4 / 5 GHz

CommandFrequencyDescription
wifi2.4 + 5 GHzProbe-request sniffing, beacon capture, drone RemoteID. Monitor-mode adapter + sudo required. `--band 2.4
bt2.4 GHzBLE advertisements, Apple Continuity, persona DB, drone RemoteID. HCI adapter + sudo. --adapter hci1.
dv5.8 / 2.4 GHzDrone video downlinks (DJI O4, OcuSync). HackRF wideband.
fpv1.2 / 5.8 GHzFPV analog video frame demodulation (PAL/NTSC). HackRF.

Wideband / recording

CommandDescription
scanWideband energy detection. --classify adds automatic modulation classification (FM, OOK, FSK, PSK, QAM, OFDM, FHSS, CW).
jammerWatch 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).
recordCapture raw IQ samples to disk.
replay <file>Re-run detection pipelines against a recorded IQ file; spectrogram/spectrum/IQ plots.

Central server

CommandDescription
server <config.json>Multi-capture orchestrator — runs many captures + parsers in parallel from a JSON config. See configuration.md. Add --web for the dashboard.
webStandalone dashboard (reads detection DBs from output dir). -p 3000 for custom port.
agentMeshtastic C2 agent runtime. See c2.md.
replay-c2 <db> --agent-id N99Replay 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.

Analysis / post-hoc

CommandDescription
tri a.db b.db c.dbRSSI multilateration from multi-node session DBs. See triangulation.md. Applies per-node calibration offsets automatically when output/calibration.db is present.
heatmap output/*.dbRF activity density heatmap (KML + PNG for ATAK). Filter with -s <signal_type>.
corr output/*.dbCross-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/*.dbPost-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.

Known quirks and limitations

  • 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.

Bench test setup

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.

Running the test suite

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