Skip to main content

Substation

An SDR band scanner that detects, demodulates, and records radio transmissions automatically.

Connect a USB SDR receiver, point it at a frequency band - Amateur, CB, Airband, PMR, Maritime, or any conventional analogue band - and Substation monitors every radio channel simultaneously, detecting each transmission and recording it to its own audio file with full metadata. Out of the box it records only the bands that UK law opens to anyone, such as amateur and CB radio, and detects activity on the rest: see Reception and the law.

The scanner is designed for unattended, long-running operation. It handles the entire signal processing chain from raw IQ samples through to clean, archive-ready audio files: signal detection, demodulation (NFM, AM, USB, LSB), noise reduction, carrier transient removal, soft limiting, and automatic file management. Noise rejection checks each activation's RF power variance and audio spectral flatness, and each finished recording's length and spectral flatness, and discards what looks like hiss. At present the flatness checks reject hiss only on AM bands: see Limitations. Recordings include embedded metadata - frequency, timestamp, modulation, and any CTCSS or DCS tone detected - so every file is self-documenting.

Substation runs as a command-line tool or as a Python module in your own applications, including on low-power hardware such as a Raspberry Pi scanning a narrower band. The widest shipped bands, at 12.5 MHz, have not yet been shown to keep up in real time, and CTCSS and DCS tone detection has not yet been thoroughly tested with real radios: see Limitations.

Full documentation: https://subsystem.co/substation

The guide starts from what a software-defined radio is and goes as far as a scanner left running, feeding recordings to Subsample and events to Subsequence. The references are generated from Substation's own code, so they describe the release the guide installs.

For changing Substation's code, see docs/architecture.md.

Supported receivers

  • RTL-SDR Blog V4 or V3: low cost, for general VHF and UHF listening.
  • HackRF One: wideband, from 1 MHz to 6 GHz.
  • Airspy R2: high-quality VHF and UHF.
  • Airspy HF+ Discovery: HF, and VHF from 64 to 260 MHz, with high sensitivity.
  • Any other receiver with a SoapySDR driver module, as --device-type soapy:<driver>.

Your first receiver compares them, and Setting up your receiver shows how to set each one up.

Quick start

Substation needs your receiver's driver installed first, which the guide's install chapter shows for each receiver. Then:

pip install substation       # or "substation[hackrf]" for a HackRF One
substation --init            # optional: writes ./config.yaml, the fully commented defaults
substation --band amateur_2m # scans the 2m amateur band with an RTL-SDR

The scanner logs each radio channel on the 2m amateur band as it becomes active, and records each transmission to its own file. The scan is running once the log says Detection enabled. The 2m band can be quiet, so a first recording may take a while. Out of the box only amateur and CB bands record: the rest, such as airband and PMR446, only detect, as the next section explains. Recordings are written to:

./audio/YYYY-MM-DD/<band>/<date>_<time>_<band>_<channel>_<freq>_<snr>dB_<device>_<index>.wav

To install the latest code from GitHub instead, run pip install git+https://github.com/simonholliday/substation.git. From here, the guide goes from a first scan to a scanner left running.

Reception and the law

Many radio services may not lawfully be listened to without permission, and the law differs from country to country. Every band Substation ships carries a reception_class saying how UK law treats it, and the class decides whether the band records out of the box:

Class What it covers Out of the box
general What Ofcom calls general reception: licensed broadcasting, amateur and CB radio, and weather and navigation transmissions Records
not_general Services outside general reception, such as PMR446, business radio, marine, military airband, and emergency services, which Ofcom says it is illegal to listen to Detects activity without recording
unsettled Bands where the position is unclear, such as civil airband, where Ofcom will not say that listening is an offence Detects activity without recording

In the UK, using a receiver to learn the contents, sender, or addressee of a transmission that is not general reception is an offence under the Wireless Telegraphy Act 2006, even if you tell no one. Elsewhere the law differs: the United States, for example, allows receiving unencrypted public-safety, marine, and air radio, and Germany forbids it. The classes describe UK law only, and are not legal advice: the law where you are decides what you may receive and record.

Where your law allows it, switch recording on for a band in your config.yaml:

bands:
  air_civil_bristol:
    recording_enabled: true

--list-bands shows each band's class and whether it records. A band you define yourself takes its template's class when it sets none of its own, so a band of type: CB records and one of type: PMR only detects. A band with no class records only if you give it reception_class: general or recording_enabled: true.

Limitations

  • Processing time grows with the number of IQ samples a second a band takes, and most of it runs on one CPU core. The three shipped bands at 12.5 MHz, air_civil_1, air_civil_2, and dmr, have not yet been shown to keep up in real time: on the one desktop computer they have been tested on, processing fell behind and IQ samples were dropped, and faster computers are still to be tested. On a Raspberry Pi, or wherever Processing overrun warnings appear, scan a narrower band, such as one of dmr_1 to dmr_5.
  • CTCSS and DCS tone detection has not yet been thoroughly tested with real radios, so treat a reported tone as a guide rather than a certainty, and the absence of one as inconclusive.
  • The spectral flatness checks reject hiss only on AM bands at present. The NFM, USB, and LSB demodulators filter their audio to the voice band before the checks measure it, and the checks measure the whole audio spectrum, so the empty frequencies outside the voice band make hiss measure as peaked, far below the threshold of 0.15. On those bands, which include every band that records out of the box, both checks keep hiss, and noise rejection rests on the SNR threshold and the RF power variance check. Fixing this needs a measure and a threshold chosen against real recordings of voice and hiss. Recordings of nothing explains each check.
  • If you enable apply_noisereduce (requires a code change and the noisereduce extra), it is CPU-intensive for long chunks; on constrained devices, stick with the default apply_spectral_subtraction or reduce disk_flush_interval_seconds.

Author

Written by Simon Holliday (https://simonholliday.com/)

This project is managed with Subroutine.

Licence

Substation is released under the GNU Affero General Public License v3.0 (AGPLv3).

You are free to use, modify, and distribute this software under the terms of the AGPL. If you run a modified version of Substation as part of a network service, you must make the source code available to its users.

Metadata

Release files for substation 0.8.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for substation 0.8.2
File Size Uploaded
substation-0.8.2.tar.gz 252.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for substation 0.8.2
File Interpreter ABI Platform
substation-0.8.2-py3-none-any.whl Python 3 none any Details

Total release size: 430.9 kB

Release files / substation-0.8.2.tar.gz

Download URL substation-0.8.2.tar.gz
Size 252.9 kB
Tags Source
SHA-256 checksum
How to use checksums
05c368c27768004c76c12a9a943883f71524754c57b472ef415675bde93ae238
BLAKE2b-256 checksum
How to use checksums
475844a8761b7e43971f99360182a55aa97fe4998c33a666dcad17550240a2b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / substation-0.8.2-py3-none-any.whl

Download URL substation-0.8.2-py3-none-any.whl
Size 178.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
54b253bd080c3b22f33300b8fa43670bb69ff1cfd0ca06803b8708825a50124e
BLAKE2b-256 checksum
How to use checksums
597b3414a2e6c751e2497c415ba52a067165ae047e122f9e935d72dffc208f76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.2 This release

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page