A real-time spectrum analyzer and signal detection tool leveraging RTL-SDR hardware for RF monitoring and jamming detection.
Features โข Installation โข Usage โข Configuration โข Commands โข Examples โข Contributing
- Features
- Installation
- Usage
- Commands
- Configuration
- Examples
- Signal Patterns
- Development
- Contributing
- License
- ๐ Real-time Visualization: Advanced spectrum analysis with waterfall display
- ๐ Smart Detection: Automatic signal anomaly and jamming detection
- ๐ Dynamic Analysis: Adaptive baseline calculation and threshold adjustment
- โ๏ธ Flexible Configuration: Validated YAML config, env vars, and CLI overrides
- ๐ Network Support: Built-in RTL-TCP compatibility for remote operation
- ๐ฅ๏ธ GUI & Headless: Interactive matplotlib plots or console-only daemon mode
- ๐ค Event Export: Save detections to CSV or JSON Lines
- ๐พ SQLite Storage: Persistent event storage with rich statistics dashboard
- ๐ป IQ Recording: Capture raw IQ samples for offline analysis
- ๐ Frequency Sweeping: Scan wide frequency ranges automatically
- ๐งช Well Tested: 60+ tests with 85%+ coverage
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.9+ | Required for modern type hints |
| RTL-SDR | Any | USB dongle with RTL2832U chipset |
| Docker | Latest | For RTL-TCP server container |
| Docker Compose | Latest | Included with Docker Desktop |
We recommend using uv for fast environment management:
# Clone the repository
git clone https://github.com/msalexms/rtl-sdr-analyzer.git
cd rtl-sdr-analyzer
# Create virtual environment and install
uv venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Install core dependencies + dev tools
uv pip install -e ".[dev,test]"
# Verify installation
rtl-sdr-analyzer --helpFor interactive matplotlib plots, install a GUI backend:
uv pip install -e ".[gui]" # Installs PyQt6If no GUI backend is available, the analyzer automatically falls back to headless mode with a warning.
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,test]"The server connects to your RTL-SDR hardware and streams IQ data over TCP:
cd docker
docker-compose up -dVerify it's running:
docker ps
nc -zv 127.0.0.1 1234| Mode | Use Case | Command |
|---|---|---|
| GUI | Interactive analysis with plots | rtl-sdr-analyzer analyze --freq 98e6 |
| Headless | Server/daemon, remote monitoring | rtl-sdr-analyzer analyze --headless --freq 98e6 |
| Export | Log detections to file | Add --export-format csv --export-path events.csv |
| Database | Persistent storage + stats | Add --db-path events.db |
# Basic GUI mode (local server)
rtl-sdr-analyzer analyze --freq 98e6 --host 127.0.0.1
# Headless mode with CSV logging
rtl-sdr-analyzer analyze --headless \
--freq 446e6 \
--host 127.0.0.1 \
--export-format csv \
--export-path events.csv
# With SQLite persistence for later stats
rtl-sdr-analyzer analyze --headless \
--freq 915e6 \
--db-path events.dbThe main command. Connects to RTL-TCP, processes IQ samples, detects signals, and visualizes or logs results.
Syntax:
rtl-sdr-analyzer analyze [OPTIONS]| Option | Short | Description | Default |
|---|---|---|---|
--host |
RTL-TCP server hostname/IP | 192.168.31.34 |
|
--port |
RTL-TCP server port | 1234 |
| Option | Short | Description | Default |
|---|---|---|---|
--freq |
Center frequency in Hz (e.g. 98e6, 446000000) |
915e6 |
|
--sample-rate |
Sample rate in Hz | 2.048e6 |
|
--fft-size |
FFT size (power of 2) | 2048 |
| Option | Description | Default |
|---|---|---|
--power-threshold |
Minimum signal power in dB | -70 |
--bandwidth-threshold |
Minimum signal bandwidth in Hz | 100000 |
--z-score-threshold |
Statistical deviation threshold | 1.5 |
--detection-window |
Frames for baseline calculation | 5 |
--min-duration |
Minimum event duration in seconds | 0.1 |
--test-mode |
Enable sensitive detection (any criterion triggers) | false |
| Option | Description | Default |
|---|---|---|
--waterfall-length |
Waterfall history length | 50 |
--update-interval |
Plot update interval in ms | 50 |
--headless |
Run without GUI (console only) | false |
| Option | Description | Default |
|---|---|---|
--export-format |
Export format: csv or json |
โ |
--export-path |
File path for exported events | โ |
| Option | Description | Default |
|---|---|---|
--db-path |
SQLite database path for persistent event storage | โ |
| Option | Description | Default |
|---|---|---|
--log-level |
Logging level: DEBUG, INFO, WARNING, ERROR |
INFO |
--log-format |
Log format: text or json |
text |
--config |
-c |
Path to YAML config file |
Examples:
# Monitor FM radio band with GUI
rtl-sdr-analyzer analyze --freq 98e6 --host 127.0.0.1
# Monitor walkie-talkies (446 MHz) headless with CSV export and SQLite
rtl-sdr-analyzer analyze --headless \
--freq 446e6 \
--host 127.0.0.1 \
--export-format csv \
--export-path walkie_events.csv \
--db-path events.db
# High-sensitivity detection for weak signals
rtl-sdr-analyzer analyze --headless \
--freq 915e6 \
--power-threshold -80 \
--z-score-threshold 1.0 \
--min-duration 0.05
# Test mode (more sensitive, good for debugging)
rtl-sdr-analyzer analyze --headless \
--freq 98e6 \
--test-mode \
--log-level DEBUG
# Remote server (not localhost)
rtl-sdr-analyzer analyze \
--host 192.168.1.50 \
--port 1234 \
--freq 446e6Output Example (Headless):
2026-04-27T09:19:07.821925Z [info ] Starting signal analyzer...
2026-04-27T09:19:07.822081Z [info ] Connecting to RTL-TCP server at 127.0.0.1:1234
2026-04-27T09:19:07.822307Z [info ] Successfully connected to RTL-TCP server
2026-04-27T09:19:08.123456Z [info ] Spectrum stats [10] | mean=-78.5 dB | min=-92.1 dB | max=-45.2 dB
2026-04-27T09:19:08.234567Z [info ] DETECTION | freq=445.987 MHz | power=-45.2 dB | bw=12500 Hz | dur=0.15 s | conf=2.34
Record raw IQ samples to a file for offline analysis with GNU Radio or custom scripts.
rtl-sdr-analyzer record output.raw --duration 30 --freq 915e6 --host 127.0.0.1| Option | Description | Default |
|---|---|---|
output |
Output file path (required) | โ |
--duration |
Recording duration in seconds | 10.0 |
--format |
Output format: raw or numpy |
raw |
--freq |
Center frequency | 915e6 |
--host |
RTL-TCP host | 192.168.31.34 |
--port |
RTL-TCP port | 1234 |
Formats:
raw: Interleaved uint8 I/Q data. Compatible withrtl_sdr, GNU Radio, and most SDR tools.numpy:.npzfile with complex64 array + metadata (sample rate, frequency, timestamp).
Examples:
# Record 60 seconds for later analysis
rtl-sdr-analyzer record capture.raw --duration 60 --freq 446e6 --host 127.0.0.1
# Record in NumPy format
rtl-sdr-analyzer record capture.npz --duration 30 --format numpy --freq 915e6Automatically scan a frequency range with configurable step and dwell time, detecting signals at each stop.
rtl-sdr-analyzer sweep START_HZ END_HZ [OPTIONS]| Option | Description | Default |
|---|---|---|
start_freq |
Start frequency in Hz (required) | โ |
end_freq |
End frequency in Hz (required) | โ |
--step |
Frequency step in Hz | 1e6 |
--dwell |
Dwell time per frequency in seconds | 2.0 |
--host |
RTL-TCP host | 192.168.31.34 |
--port |
RTL-TCP port | 1234 |
--sample-rate |
Sample rate | 2.048e6 |
--power-threshold |
Power threshold | -70 |
--headless |
Run without GUI | true |
--export-format |
Export format | โ |
--export-path |
Export file path | โ |
--db-path |
SQLite database path | โ |
Examples:
# Scan 400-500 MHz for walkie-talkies
rtl-sdr-analyzer sweep 400e6 500e6 --step 1e6 --dwell 2.0 --host 127.0.0.1
# Fine-grained sweep with CSV export
rtl-sdr-analyzer sweep 88e6 108e6 \
--step 0.5e6 \
--dwell 1.0 \
--export-format csv \
--export-path fm_sweep.csv
# Sweep with database persistence
rtl-sdr-analyzer sweep 400e6 500e6 --step 1e6 --db-path sweep.dbDisplay rich statistics from a SQLite database created with --db-path.
rtl-sdr-analyzer stats DB_PATH [OPTIONS]| Option | Description | Default |
|---|---|---|
db_path |
Path to SQLite database | rtl_sdr_analyzer.db |
--top-freqs |
Show top frequencies by activity | true |
--hourly |
Show hourly activity chart | true |
--recent |
Number of recent events to show | 10 |
--export-csv |
Export events to CSV file | โ |
--since-hours |
Filter events by last N hours | โ |
Examples:
# Show full dashboard
rtl-sdr-analyzer stats events.db
# Export last 24h to CSV
rtl-sdr-analyzer stats events.db --export-csv yesterday.csv --since-hours 24
# Show only summary and recent events
rtl-sdr-analyzer stats events.db --top-freqs=false --hourly=false --recent 20Dashboard Output:
๐ RTL-SDR Analyzer Statistics
==================================================
Total detection events: 1542
Events in last 24h: 87
Recent sessions: 3
๐ฅ Top Frequencies by Activity
โโโโโโโโโโโโโโโโโโโณโโโโโโโโโโโโโณโโโโโโโโโโโโโโโโโณโโโโโโโโโโโโโโโโโ
โ Frequency (MHz) โ Detections โ Avg Power (dB) โ Max Power (dB) โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ 445.987 โ 523 โ -52.3 โ -30.1 โ
โ 98.100 โ 412 โ -65.1 โ -45.2 โ
โ 446.125 โ 607 โ -48.7 โ -28.9 โ
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
๐ Hourly Activity (Last 24h)
โโโโโโโโโโโโโโโโโโโโโโโณโโโโโโโโโโโโโ
โ Hour โ Detections โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ 2026-04-27 10:00 โ 12 โ
โ 2026-04-27 11:00 โ 35 โ
โ 2026-04-27 12:00 โ 40 โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโ
Validate a YAML configuration file against the Pydantic schema.
# Validate default config
rtl-sdr-analyzer config-validate config/default.yml
# Validate custom config
rtl-sdr-analyzer config-validate my-config.ymlOutput:
Configuration is valid.
{
"rtl_tcp": {
"host": "192.168.31.34",
"port": 1234
},
"receiver": {
"frequency": 915000000.0,
...
}
}
If invalid, you'll see detailed error messages:
Configuration error: 1 validation error for Settings
receiver.frequency
Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
Shows the final configuration after merging all sources (defaults โ YAML โ env vars โ CLI).
# Show as YAML (default)
rtl-sdr-analyzer config-show
# Show as JSON
rtl-sdr-analyzer config-show --format json
# Show with custom config file
rtl-sdr-analyzer config-show --config my-config.yml --format jsonUseful for debugging to verify that your env vars and CLI flags are being applied correctly.
Configuration follows a cascading priority (lowest โ highest):
Code Defaults โ YAML File โ Environment Variables โ CLI Flags
Create a custom config file:
# my-config.yml
rtl_tcp:
host: "127.0.0.1"
port: 1234
receiver:
frequency: 98000000 # 98 MHz - FM Radio
sample_rate: 2048000 # 2.048 MHz
fft_size: 2048
detector:
power_threshold: -70.0
bandwidth_threshold: 100000 # 100 kHz
z_score_threshold: 1.5
detection_window: 5
min_duration: 0.1
test_mode: false
display:
waterfall_length: 50
update_interval: 50Use it:
rtl-sdr-analyzer analyze --config my-config.ymlPrefix with RTL_SDR__ and use double underscores for nesting:
# Override frequency
export RTL_SDR__RECEIVER__FREQUENCY=446000000
# Override host
export RTL_SDR__RTL_TCP__HOST=192.168.1.50
# Enable test mode
export RTL_SDR__DETECTOR__TEST_MODE=true
# Then run (picks up env vars automatically)
rtl-sdr-analyzer analyzeSee .env.example for all available variables.
Highest priority. Override any other source:
# Override just frequency and threshold
rtl-sdr-analyzer analyze \
--config my-config.yml \
--freq 915e6 \
--power-threshold -65# Monitor 98 MHz with GUI
rtl-sdr-analyzer analyze --freq 98e6 --host 127.0.0.1
# Monitor with CSV export for later analysis
rtl-sdr-analyzer analyze --headless \
--freq 98e6 \
--host 127.0.0.1 \
--export-format csv \
--export-path fm_events.csv \
--power-threshold -60# Headless monitoring with JSON export and SQLite
rtl-sdr-analyzer analyze --headless \
--freq 446e6 \
--host 127.0.0.1 \
--export-format json \
--export-path walkie_events.jsonl \
--db-path walkie.db \
--bandwidth-threshold 50000 \
--min-duration 0.2
# View stats afterward
rtl-sdr-analyzer stats walkie.db# Monitor GSM band (around 900-1800 MHz depending on region)
rtl-sdr-analyzer analyze --headless \
--freq 950e6 \
--sample-rate 2.4e6 \
--power-threshold -75 \
--detection-window 10# Very sensitive detection with debug logging
rtl-sdr-analyzer analyze --headless \
--freq 915e6 \
--test-mode \
--log-level DEBUG \
--log-format text# Server runs RTL-TCP on 192.168.1.50
# Client connects from another machine
rtl-sdr-analyzer analyze \
--host 192.168.1.50 \
--port 1234 \
--freq 446e6 \
--headlessCreate config/fm-radio.yml:
receiver:
frequency: 98000000
sample_rate: 2048000
detector:
power_threshold: -60
bandwidth_threshold: 150000Create config/walkie-talkie.yml:
receiver:
frequency: 446000000
sample_rate: 2048000
detector:
power_threshold: -70
bandwidth_threshold: 25000
min_duration: 0.2Switch between them:
rtl-sdr-analyzer analyze --config config/fm-radio.yml
rtl-sdr-analyzer analyze --config config/walkie-talkie.yml# Record 60 seconds of raw IQ data
rtl-sdr-analyzer record capture.raw \
--duration 60 \
--freq 446e6 \
--host 127.0.0.1
# Record in NumPy format for Python analysis
rtl-sdr-analyzer record capture.npz \
--duration 30 \
--format numpy \
--freq 915e6
# Load in Python later
python -c "import numpy as np; d = np.load('capture.npz'); print(d['iq'][:10])"# Scan 400-500 MHz for walkie-talkies
rtl-sdr-analyzer sweep 400e6 500e6 \
--step 1e6 \
--dwell 2.0 \
--host 127.0.0.1 \
--export-format csv \
--export-path sweep_results.csv
# Fine-grained FM radio sweep
rtl-sdr-analyzer sweep 88e6 108e6 \
--step 0.5e6 \
--dwell 1.0 \
--db-path fm_sweep.db
# View sweep stats
rtl-sdr-analyzer stats fm_sweep.db --top-freqs --hourly# Run analyzer with persistent storage
rtl-sdr-analyzer analyze --headless \
--freq 915e6 \
--host 127.0.0.1 \
--db-path monitoring.db
# Let it run for a while, then check stats
rtl-sdr-analyzer stats monitoring.db
# Export last 24 hours to CSV
rtl-sdr-analyzer stats monitoring.db \
--export-csv yesterday.csv \
--since-hours 24
# Show only top frequencies
rtl-sdr-analyzer stats monitoring.db --hourly=false --recent=0| Signal Type | Frequency Range | Characteristics |
|---|---|---|
| ๐ป FM Radio | 88-108 MHz | Strong, stable, wide bandwidth |
| ๐ฑ GSM/LTE | 700-2600 MHz | Wide-band, intermittent |
| ๐ Wi-Fi | 2.4/5 GHz | Periodic bursts (need downconverter) |
| ๐ฎ RF Remotes | 315/433/868 MHz | Brief, narrow-band |
| ๐ Walkie-Talkies | 446 MHz | Short bursts, medium bandwidth |
# Run all tests with coverage
pytest
# Run specific test file
pytest tests/unit/test_detector.py -v
# Linting
ruff check src tests
ruff format src tests
# Type checking
mypy --strict src
# Run with coverage report
pytest --cov=rtl_sdr_analyzer --cov-report=html- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'feat: add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Developed with โค๏ธ by RF enthusiasts