Skip to main content

⚠️ UNTESTED — This implementation has not been verified against physical hardware. Code is complete but has not been bench-tested. Verify behavior before relying on output.

rf-bench-drivers-rtlsdr

RTL-SDR receiver driver for the rf-bench bench automation suite. Wraps pyrtlsdr (librtlsdr) with PPM calibration, consistent import patterns, workflow helpers, and a thread-safe streaming generator.

Supported hardware:

  • RTL-SDR Blog v4 (R828D tuner, 1 PPM TCXO, bias tee) — recommended
  • RTL-SDR Blog v3 (R820T2 tuner, bias tee)
  • Generic RTL2832U-based DVB-T dongles (no TCXO — calibrate PPM before use)

Installation

pip install rf-bench-drivers-rtlsdr
# or, for the full rf-bench suite:
pip install rf-bench

System dependency: librtlsdr must be installed.

# Arch Linux
pacman -S rtl-sdr

# Debian / Ubuntu
apt install rtl-sdr librtlsdr-dev

# macOS
brew install librtlsdr

Linux udev rule (required to access the device as a non-root user):

SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", \
    MODE="0664", GROUP="plugdev"

Save to /etc/udev/rules.d/99-rtlsdr.rules, then udevadm control --reload-rules and re-plug the dongle.


Quick start

from rf_bench.rtlsdr import RTLSDR

# Basic capture
with RTLSDR() as sdr:
    sdr.set_center_freq(144_390_000)   # 144.390 MHz — APRS
    sdr.set_sample_rate(2_400_000)
    sdr.set_gain(30)
    iq = sdr.capture_iq(262_144)
    print(f"Captured {len(iq)} samples, dtype={iq.dtype}")

# Power spectrum
with RTLSDR() as sdr:
    sdr.set_center_freq(433_920_000)
    sdr.set_sample_rate(2_400_000)
    iq = sdr.capture_iq(262_144)
    freq_hz, power_db = sdr.power_spectrum(iq, rbw_hz=1000)
    peak_idx = power_db.argmax()
    print(f"Strongest signal: {freq_hz[peak_idx]/1e6:.3f} MHz  ({power_db[peak_idx]:.1f} dB)")

# Streaming (ADS-B example)
with RTLSDR() as sdr:
    sdr.set_center_freq(1_090_000_000)
    sdr.set_sample_rate(2_000_000)
    sdr.set_gain(40)
    for block in sdr.stream_iq(block_size=65_536):
        process_modes_s(block)
        if should_stop:
            break
    sdr.stop_stream()

PPM calibration

The RTL-SDR v4 TCXO is nominally 1 PPM, but the actual offset should be measured against a known reference for best frequency accuracy:

  1. Connect the RTL-SDR to a signal of known frequency (e.g. the SDG1062X set to 10.000000 MHz, verified on the SSA).
  2. Tune the RTL-SDR to the same frequency.
  3. Compute the offset: ppm = (measured_hz - nominal_hz) / nominal_hz * 1e6
  4. Save it:
with RTLSDR() as sdr:
    sdr.save_calibration(ppm=0.8)    # adjust to your measured value

The correction is stored in ~/.rtlsdr_cal.json and loaded automatically on every subsequent RTLSDR() construction. Pass ppm_correction=0 explicitly to override.


Bias tee (RTL-SDR Blog v3/v4)

The bias tee supplies 5 V (~180 mA) on the SMA centre pin to power an inline LNA. Requires librtlsdr >= 2.0.0 (included in the Arch rtl-sdr package):

with RTLSDR() as sdr:
    sdr.set_bias_tee(True)    # power the LNA
    sdr.set_center_freq(137_620_000)
    sdr.set_sample_rate(2_400_000)
    iq = sdr.capture_iq(2_400_000 * 10)   # 10 s capture
    sdr.set_bias_tee(False)   # disable before disconnecting the LNA

Always disable the bias tee before removing the antenna or LNA to avoid back-feeding the RTL-SDR input with the bias voltage.


Device enumeration

If multiple dongles are attached, select by serial number for reproducibility:

# List all attached devices
for dev in RTLSDR.find_devices():
    print(dev)
# {'index': 0, 'serial': '00000001', 'name': 'Generic RTL2832U OEM'}

# Connect to a specific dongle
with RTLSDR(serial="00000001") as sdr:
    print(sdr.identify())

Set a permanent serial number with rtl_eeprom -s 00000001 (from the rtl-sdr package).


API reference

RTLSDR(serial=None, device_index=0, ppm_correction=None, sample_rate=2_400_000, gain='auto')

Opens the device. Raises RTLSDRError if the serial is not found. Raises RTLSDRBusyError if another process already has the device open.

Method Description
find_devices() Class method — list attached devices as [{'index', 'serial', 'name'}]
identify() Return device info dict: serial, tuner_type, valid_gains_db, sample_rate, center_freq, gain, ppm_correction
set_center_freq(freq_hz) Set tuning frequency; PPM correction applied automatically
set_sample_rate(rate) Set IQ sample rate in S/s (typical: 250_000 – 3_200_000)
set_gain(gain_db) Set gain in dB (snapped to nearest valid step) or 'auto'
set_bias_tee(enabled) Enable/disable bias tee (RTL-SDR Blog v3/v4 only)
capture_iq(num_samples) Capture a block; returns complex64 numpy array
power_spectrum(iq, rbw_hz) Welch PSD; returns (freq_hz, power_db) float32 arrays
scan_activity(threshold_db, num_samples) Quick scan; returns list of detected signals
stream_iq(block_size) Generator yielding complex64 blocks; call stop_stream() when done
stop_stream() Stop streaming and join the reader thread
save_calibration(ppm) Save PPM correction to ~/.rtlsdr_cal.json
close() Stop streaming and close device

Valid sample rates

The RTL2832U supports a continuous range from ~250 kS/s to 3.2 MS/s. Values outside 900 kS/s – 3.2 MS/s may have increased sample loss on some systems. Common choices:

Rate Bandwidth Typical use
250_000 200 kHz Narrow-band FM, single ISM channel
1_024_000 ~800 kHz ACARS, P25 channel bank
2_000_000 ~1.6 MHz ADS-B (1090 MHz)
2_048_000 ~1.6 MHz DVB-T, general use
2_400_000 ~2.0 MHz Weather satellites, wideband ISM
3_200_000 ~2.6 MHz Maximum reliable rate

Power spectrum note

power_spectrum() returns power values relative to the peak bin (0 dB = peak). The values are not calibrated absolute dBm — the RTL-SDR has no calibrated power reference. Use the SSA3032X Plus for absolute amplitude measurements; use the RTL-SDR for signal detection, classification, and protocol decode where relative power and frequency are sufficient.


Hardware connection notes

The RTL-SDR Blog v4 appears as a single USB device (RTL2832U, USB VID 0x0bda PID 0x2838 or variant). Only one process may open it at a time; RTLSDR() raises RTLSDRBusyError if it is already in use.

For ADS-B (1090 MHz), a dedicated 1090 MHz bandpass filter (FA 1090 MHz or similar) before the LNA significantly reduces out-of-band noise and improves decode rate.

For weather satellites (137 MHz), a V-dipole antenna (~54 cm elements, 120° spread) is optimal — omnidirectional in elevation, no tracking required for LEO passes.


License

GPL-3.0-or-later

Metadata

Release files for rf-bench-drivers-rtlsdr 0.1.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 rf-bench-drivers-rtlsdr 0.1.2
File Size Uploaded Provenance Actions
rf_bench_drivers_rtlsdr-0.1.2.tar.gz 25.3 kB Without provenance Details for rf_bench_drivers_rtlsdr-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rf-bench-drivers-rtlsdr 0.1.2
File Interpreter ABI Platform Actions
rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl Python 3 none any Details for rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl

Total release size: 49.1 kB

Release files / rf_bench_drivers_rtlsdr-0.1.2.tar.gz

Download URL rf_bench_drivers_rtlsdr-0.1.2.tar.gz
Size 25.3 kB
Tags Source
SHA-256 checksum
How to use checksums
cfcf47865fc9132503be4b23e7d76ff4aecbb6c7e8e0bba032887a14006ef3d7
BLAKE2b-256 checksum
How to use checksums
12154d461a6dbf5dd43fed35c6cf874db1e0eed9ee15dd9d6324344974003e7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl

Download URL rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl
Size 23.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
400071da536a9027b99e96e932b46d9373ab6c1ba4eeb9d092a72c5e403e09b1
BLAKE2b-256 checksum
How to use checksums
16046eaf7866aa1873c0cfd2929c2a8bf43f3d304fd2aa3e44514d68b765d5e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Security for rf-bench-drivers-rtlsdr 0.1.2

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files No provenance

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