mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Add multi-SDR hardware support (LimeSDR, HackRF) and setup script
- Add SDR hardware abstraction layer (utils/sdr/) with support for: - RTL-SDR (existing, using native rtl_* tools) - LimeSDR (via SoapySDR) - HackRF (via SoapySDR) - Add hardware type selector to UI with capabilities display - Add automatic device detection across all supported hardware - Add hardware-specific parameter validation (frequency/gain ranges) - Add setup.sh script for automated dependency installation - Update README with multi-SDR docs, installation guide, troubleshooting - Add SoapySDR/LimeSDR/HackRF to dependency definitions - Fix dump1090 detection for Homebrew on Apple Silicon Macs - Remove defunct NOAA-15/18/19 satellites, add NOAA-21 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
151
README.md
151
README.md
@@ -172,7 +172,9 @@ Instead of running command-line tools manually, INTERCEPT handles the process ma
|
||||
- **Message export** to CSV/JSON
|
||||
- **Signal activity meter** and waterfall display
|
||||
- **Message logging** to file with timestamps
|
||||
- **RTL-SDR device detection** and selection
|
||||
- **Multi-SDR hardware support** - RTL-SDR, LimeSDR, HackRF
|
||||
- **Automatic device detection** across all supported hardware
|
||||
- **Hardware-specific validation** - frequency/gain ranges per device type
|
||||
- **Configurable gain and PPM correction**
|
||||
- **Device intelligence** dashboard with tracking
|
||||
- **Disclaimer acceptance** on first use
|
||||
@@ -183,7 +185,10 @@ Instead of running command-line tools manually, INTERCEPT handles the process ma
|
||||
## Requirements
|
||||
|
||||
### Hardware
|
||||
- RTL-SDR compatible dongle (RTL2832U based)
|
||||
- **SDR Device** (one of the following):
|
||||
- RTL-SDR compatible dongle (RTL2832U based) - most common, budget-friendly
|
||||
- LimeSDR / LimeSDR Mini - wider frequency range (100 kHz - 3.8 GHz)
|
||||
- HackRF One - ultra-wide frequency range (1 MHz - 6 GHz)
|
||||
- WiFi adapter capable of monitor mode (for WiFi features)
|
||||
- Bluetooth adapter (for Bluetooth features)
|
||||
|
||||
@@ -203,17 +208,41 @@ Instead of running command-line tools manually, INTERCEPT handles the process ma
|
||||
|
||||
Install the tools for the features you need:
|
||||
|
||||
#### Core SDR Tools
|
||||
|
||||
| Tool | macOS | Ubuntu/Debian | Purpose |
|
||||
|------|-------|---------------|---------|
|
||||
| rtl-sdr | `brew install rtl-sdr` | `sudo apt install rtl-sdr` | Required for all SDR features |
|
||||
| multimon-ng | `Use MacPorts for now sudo ports install multimon-ng` | `sudo apt install multimon-ng` | Pager decoding |
|
||||
| rtl-sdr | `brew install librtlsdr` | `sudo apt install rtl-sdr` | RTL-SDR support |
|
||||
| multimon-ng | `sudo port install multimon-ng` | `sudo apt install multimon-ng` | Pager decoding |
|
||||
| rtl_433 | `brew install rtl_433` | `sudo apt install rtl-433` | 433MHz sensors |
|
||||
| dump1090 | `brew install dump1090-mutability` | `sudo apt install dump1090-mutability` | ADS-B aircraft |
|
||||
| aircrack-ng | `brew install aircrack-ng` | `sudo apt install aircrack-ng` | WiFi reconnaissance |
|
||||
| bluez | Built-in (limited) | `sudo apt install bluez bluetooth` | Bluetooth scanning |
|
||||
|
||||
#### Additional SDR Hardware (Optional)
|
||||
|
||||
For LimeSDR or HackRF support, install SoapySDR and the appropriate driver:
|
||||
|
||||
| Tool | macOS | Ubuntu/Debian | Purpose |
|
||||
|------|-------|---------------|---------|
|
||||
| SoapySDR | `brew install soapysdr` | `sudo apt install soapysdr-tools` | Universal SDR abstraction |
|
||||
| LimeSDR | `brew install limesuite soapylms7` | `sudo apt install limesuite soapysdr-module-lms7` | LimeSDR support |
|
||||
| HackRF | `brew install hackrf soapyhackrf` | `sudo apt install hackrf soapysdr-module-hackrf` | HackRF support |
|
||||
| readsb | Build from source | Build from source | ADS-B with SoapySDR |
|
||||
|
||||
> **Note:** RTL-SDR works out of the box. LimeSDR and HackRF require SoapySDR plus the hardware-specific driver.
|
||||
|
||||
### Install and run
|
||||
|
||||
**Option 1: Automated setup (recommended)**
|
||||
```bash
|
||||
git clone https://github.com/smittix/intercept.git
|
||||
cd intercept
|
||||
./setup.sh # Installs Python deps and checks for external tools
|
||||
sudo python3 intercept.py
|
||||
```
|
||||
|
||||
**Option 2: Manual setup**
|
||||
```bash
|
||||
git clone https://github.com/smittix/intercept.git
|
||||
cd intercept
|
||||
@@ -221,10 +250,30 @@ pip install -r requirements.txt
|
||||
sudo python3 intercept.py
|
||||
```
|
||||
|
||||
**Option 3: Using a virtual environment**
|
||||
```bash
|
||||
git clone https://github.com/smittix/intercept.git
|
||||
cd intercept
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Linux/macOS
|
||||
pip install -r requirements.txt
|
||||
sudo venv/bin/python intercept.py
|
||||
```
|
||||
|
||||
Open `http://localhost:5050` in your browser.
|
||||
|
||||
> **Note:** Running as root/sudo is recommended for full functionality (monitor mode, raw sockets, etc.)
|
||||
|
||||
### Check dependencies
|
||||
|
||||
Run the built-in dependency checker to see what's installed and what's missing:
|
||||
|
||||
```bash
|
||||
python3 intercept.py --check-deps
|
||||
```
|
||||
|
||||
This will show the status of all required tools and provide installation instructions for your platform.
|
||||
|
||||
### Command-line options
|
||||
|
||||
```
|
||||
@@ -241,11 +290,12 @@ python3 intercept.py --help
|
||||
## Usage
|
||||
|
||||
### Pager Mode
|
||||
1. **Select Device** - Choose your RTL-SDR device from the dropdown
|
||||
2. **Set Frequency** - Enter a frequency in MHz or use a preset
|
||||
3. **Choose Protocols** - Select which protocols to decode (POCSAG/FLEX)
|
||||
4. **Adjust Settings** - Set gain, squelch, and PPM correction as needed
|
||||
5. **Start Decoding** - Click the green "Start Decoding" button
|
||||
1. **Select Hardware** - Choose your SDR type (RTL-SDR, LimeSDR, or HackRF)
|
||||
2. **Select Device** - Choose your SDR device from the dropdown
|
||||
3. **Set Frequency** - Enter a frequency in MHz or use a preset
|
||||
4. **Choose Protocols** - Select which protocols to decode (POCSAG/FLEX)
|
||||
5. **Adjust Settings** - Set gain, squelch, and PPM correction as needed
|
||||
6. **Start Decoding** - Click the green "Start Decoding" button
|
||||
|
||||
### WiFi Mode
|
||||
1. **Select Interface** - Choose a WiFi adapter capable of monitor mode
|
||||
@@ -262,14 +312,15 @@ python3 intercept.py --help
|
||||
4. **View Devices** - Devices appear with name, address, and classification
|
||||
|
||||
### Aircraft Mode
|
||||
1. **Check Tools** - Ensure dump1090 or rtl_adsb is installed
|
||||
2. **Set Location** - Enter observer coordinates or click "Use GPS Location"
|
||||
3. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception
|
||||
4. **View Map** - Aircraft appear on the interactive Leaflet map
|
||||
5. **Click Aircraft** - Click markers for detailed information
|
||||
6. **Display Options** - Toggle callsigns, altitude, trails, range rings, clustering
|
||||
7. **Filter Aircraft** - Use dropdown to show all, military, civil, or emergency only
|
||||
8. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view
|
||||
1. **Select Hardware** - Choose your SDR type (RTL-SDR uses dump1090, others use readsb)
|
||||
2. **Check Tools** - Ensure dump1090 or readsb is installed
|
||||
3. **Set Location** - Enter observer coordinates or click "Use GPS Location"
|
||||
4. **Start Tracking** - Click "Start Tracking" to begin ADS-B reception
|
||||
5. **View Map** - Aircraft appear on the interactive Leaflet map
|
||||
6. **Click Aircraft** - Click markers for detailed information
|
||||
7. **Display Options** - Toggle callsigns, altitude, trails, range rings, clustering
|
||||
8. **Filter Aircraft** - Use dropdown to show all, military, civil, or emergency only
|
||||
9. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated radar view
|
||||
|
||||
### Satellite Mode
|
||||
1. **Set Location** - Enter observer coordinates or click "Use My Location"
|
||||
@@ -291,10 +342,46 @@ python3 intercept.py --help
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No devices found
|
||||
- Ensure your RTL-SDR is plugged in
|
||||
- Check `rtl_test` works from command line
|
||||
- On Linux, you may need to blacklist the DVB-T driver
|
||||
### Python/pip installation issues
|
||||
|
||||
**"externally-managed-environment" error (Ubuntu 23.04+, Debian 12+):**
|
||||
```bash
|
||||
# Option 1: Use a virtual environment (recommended)
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Option 2: Use pipx for isolated install
|
||||
pipx install flask skyfield
|
||||
|
||||
# Option 3: Override the restriction (not recommended)
|
||||
pip install -r requirements.txt --break-system-packages
|
||||
```
|
||||
|
||||
**"pip: command not found":**
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install python3-pip
|
||||
|
||||
# macOS
|
||||
python3 -m ensurepip --upgrade
|
||||
```
|
||||
|
||||
**Permission denied errors:**
|
||||
```bash
|
||||
# Install to user directory instead
|
||||
pip install --user -r requirements.txt
|
||||
```
|
||||
|
||||
### No SDR devices found
|
||||
- Ensure your SDR device is plugged in
|
||||
- Check `rtl_test` (RTL-SDR) or `SoapySDRUtil --find` (LimeSDR/HackRF)
|
||||
- On Linux, you may need udev rules (see setup.sh output)
|
||||
- On Linux, blacklist the DVB-T driver:
|
||||
```bash
|
||||
echo "blacklist dvb_usb_rtl28xxu" | sudo tee /etc/modprobe.d/blacklist-rtl.conf
|
||||
sudo modprobe -r dvb_usb_rtl28xxu
|
||||
```
|
||||
|
||||
### No messages appearing
|
||||
- Verify the frequency is correct for your area
|
||||
@@ -309,7 +396,13 @@ python3 intercept.py --help
|
||||
|
||||
### Device busy error
|
||||
- Click "Kill All Processes" to stop any stale processes
|
||||
- Unplug and replug the RTL-SDR device
|
||||
- Unplug and replug the SDR device
|
||||
- Check if another application is using the device: `lsof | grep rtl`
|
||||
|
||||
### LimeSDR/HackRF not detected
|
||||
- Ensure SoapySDR is installed: `SoapySDRUtil --info`
|
||||
- Check the driver module is loaded: `SoapySDRUtil --find`
|
||||
- Verify permissions (may need udev rules or run as root)
|
||||
|
||||
---
|
||||
|
||||
@@ -337,12 +430,26 @@ MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
Created by **smittix** - [GitHub](https://github.com/smittix)
|
||||
|
||||
## Supported SDR Hardware
|
||||
|
||||
| Hardware | Frequency Range | Gain Range | TX | Notes |
|
||||
|----------|-----------------|------------|-----|-------|
|
||||
| **RTL-SDR** | 24 - 1766 MHz | 0 - 50 dB | No | Most common, budget-friendly (~$25) |
|
||||
| **LimeSDR** | 0.1 - 3800 MHz | 0 - 73 dB | Yes | Wide range, requires SoapySDR |
|
||||
| **HackRF** | 1 - 6000 MHz | 0 - 62 dB | Yes | Ultra-wide range, requires SoapySDR |
|
||||
|
||||
The application automatically detects connected devices and shows hardware-specific capabilities (frequency limits, gain ranges) in the UI.
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [rtl-sdr](https://osmocom.org/projects/rtl-sdr/wiki) - RTL-SDR drivers
|
||||
- [multimon-ng](https://github.com/EliasOenal/multimon-ng) - Multi-protocol pager decoder
|
||||
- [rtl_433](https://github.com/merbanan/rtl_433) - 433MHz sensor decoder
|
||||
- [dump1090](https://github.com/flightaware/dump1090) - ADS-B decoder for aircraft tracking
|
||||
- [SoapySDR](https://github.com/pothosware/SoapySDR) - Universal SDR abstraction layer
|
||||
- [LimeSuite](https://github.com/myriadrf/LimeSuite) - LimeSDR driver and tools
|
||||
- [aircrack-ng](https://www.aircrack-ng.org/) - WiFi security auditing tools
|
||||
- [BlueZ](http://www.bluez.org/) - Official Linux Bluetooth protocol stack
|
||||
- [Leaflet.js](https://leafletjs.com/) - Interactive maps for aircraft tracking
|
||||
|
||||
9
app.py
9
app.py
@@ -26,7 +26,8 @@ from typing import Any
|
||||
from flask import Flask, render_template, jsonify, send_file, Response, request
|
||||
|
||||
from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES
|
||||
from utils.process import detect_devices, cleanup_stale_processes
|
||||
from utils.process import cleanup_stale_processes
|
||||
from utils.sdr import SDRFactory
|
||||
|
||||
|
||||
# Create Flask app
|
||||
@@ -105,7 +106,7 @@ def index() -> str:
|
||||
'multimon': check_tool('multimon-ng'),
|
||||
'rtl_433': check_tool('rtl_433')
|
||||
}
|
||||
devices = detect_devices()
|
||||
devices = [d.to_dict() for d in SDRFactory.detect_devices()]
|
||||
return render_template('index.html', tools=tools, devices=devices)
|
||||
|
||||
|
||||
@@ -116,7 +117,9 @@ def favicon() -> Response:
|
||||
|
||||
@app.route('/devices')
|
||||
def get_devices() -> Response:
|
||||
return jsonify(detect_devices())
|
||||
"""Get all detected SDR devices with hardware type info."""
|
||||
devices = SDRFactory.detect_devices()
|
||||
return jsonify([d.to_dict() for d in devices])
|
||||
|
||||
|
||||
@app.route('/dependencies')
|
||||
|
||||
@@ -3,18 +3,12 @@ TLE_SATELLITES = {
|
||||
'ISS': ('ISS (ZARYA)',
|
||||
'1 25544U 98067A 24001.00000000 .00000000 00000-0 00000-0 0 0000',
|
||||
'2 25544 51.6400 0.0000 0000000 0.0000 0.0000 15.50000000000000'),
|
||||
'NOAA-15': ('NOAA 15',
|
||||
'1 25338U 98030A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 25338 98.7300 0.0000 0010000 0.0000 0.0000 14.26000000000000'),
|
||||
'NOAA-18': ('NOAA 18',
|
||||
'1 28654U 05018A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 28654 98.8800 0.0000 0014000 0.0000 0.0000 14.12000000000000'),
|
||||
'NOAA-19': ('NOAA 19',
|
||||
'1 33591U 09005A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 33591 99.1900 0.0000 0014000 0.0000 0.0000 14.12000000000000'),
|
||||
'NOAA-20': ('NOAA 20 (JPSS-1)',
|
||||
'1 43013U 17073A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 43013 98.7400 0.0000 0001000 0.0000 0.0000 14.19000000000000'),
|
||||
'NOAA-21': ('NOAA 21 (JPSS-2)',
|
||||
'1 54234U 22150A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 54234 98.7100 0.0000 0001000 0.0000 0.0000 14.19000000000000'),
|
||||
'METEOR-M2': ('METEOR-M 2',
|
||||
'1 40069U 14037A 24001.00000000 .00000-0 00000-0 00000-0 0 0000',
|
||||
'2 40069 98.5400 0.0000 0005000 0.0000 0.0000 14.21000000000000'),
|
||||
|
||||
@@ -18,6 +18,7 @@ import app as app_module
|
||||
from utils.logging import adsb_logger as logger
|
||||
from utils.validation import validate_device_index, validate_gain
|
||||
from utils.sse import format_sse
|
||||
from utils.sdr import SDRFactory, SDRType
|
||||
|
||||
adsb_bp = Blueprint('adsb', __name__, url_prefix='/adsb')
|
||||
|
||||
@@ -29,9 +30,15 @@ adsb_last_message_time = None
|
||||
|
||||
# Common installation paths for dump1090 (when not in PATH)
|
||||
DUMP1090_PATHS = [
|
||||
# Homebrew on Apple Silicon (M1/M2/M3)
|
||||
'/opt/homebrew/bin/dump1090',
|
||||
'/opt/homebrew/bin/dump1090-fa',
|
||||
'/opt/homebrew/bin/dump1090-mutability',
|
||||
# Homebrew on Intel Mac
|
||||
'/usr/local/bin/dump1090',
|
||||
'/usr/local/bin/dump1090-fa',
|
||||
'/usr/local/bin/dump1090-mutability',
|
||||
# Linux system paths
|
||||
'/usr/bin/dump1090',
|
||||
'/usr/bin/dump1090-fa',
|
||||
'/usr/bin/dump1090-mutability',
|
||||
@@ -240,11 +247,23 @@ def start_adsb():
|
||||
thread.start()
|
||||
return jsonify({'status': 'started', 'message': 'Connected to existing dump1090 service'})
|
||||
|
||||
# No existing service, need to start dump1090 ourselves
|
||||
dump1090_path = find_dump1090()
|
||||
# Get SDR type from request
|
||||
sdr_type_str = data.get('sdr_type', 'rtlsdr')
|
||||
try:
|
||||
sdr_type = SDRType(sdr_type_str)
|
||||
except ValueError:
|
||||
sdr_type = SDRType.RTL_SDR
|
||||
|
||||
if not dump1090_path:
|
||||
return jsonify({'status': 'error', 'message': 'dump1090 not found. Install dump1090/dump1090-fa or ensure it is in /usr/local/bin/'})
|
||||
# For RTL-SDR, use dump1090. For other hardware, need readsb with SoapySDR
|
||||
if sdr_type == SDRType.RTL_SDR:
|
||||
dump1090_path = find_dump1090()
|
||||
if not dump1090_path:
|
||||
return jsonify({'status': 'error', 'message': 'dump1090 not found. Install dump1090/dump1090-fa or ensure it is in /usr/local/bin/'})
|
||||
else:
|
||||
# For LimeSDR/HackRF, check for readsb (dump1090 with SoapySDR support)
|
||||
dump1090_path = shutil.which('readsb') or find_dump1090()
|
||||
if not dump1090_path:
|
||||
return jsonify({'status': 'error', 'message': f'readsb or dump1090 not found for {sdr_type.value}. Install readsb with SoapySDR support.'})
|
||||
|
||||
# Kill any stale app-started process
|
||||
if app_module.adsb_process:
|
||||
@@ -255,7 +274,19 @@ def start_adsb():
|
||||
pass
|
||||
app_module.adsb_process = None
|
||||
|
||||
cmd = [dump1090_path, '--net', '--gain', str(gain), '--device-index', str(device), '--quiet']
|
||||
# Create device object and build command via abstraction layer
|
||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||
builder = SDRFactory.get_builder(sdr_type)
|
||||
|
||||
# Build ADS-B decoder command
|
||||
cmd = builder.build_adsb_command(
|
||||
device=sdr_device,
|
||||
gain=float(gain)
|
||||
)
|
||||
|
||||
# For RTL-SDR, ensure we use the found dump1090 path
|
||||
if sdr_type == SDRType.RTL_SDR:
|
||||
cmd[0] = dump1090_path
|
||||
|
||||
try:
|
||||
app_module.adsb_process = subprocess.Popen(
|
||||
|
||||
@@ -23,6 +23,7 @@ import app as app_module
|
||||
from utils.logging import iridium_logger as logger
|
||||
from utils.validation import validate_frequency, validate_device_index, validate_gain
|
||||
from utils.sse import format_sse
|
||||
from utils.sdr import SDRFactory, SDRType
|
||||
|
||||
iridium_bp = Blueprint('iridium', __name__, url_prefix='/iridium')
|
||||
|
||||
@@ -103,21 +104,45 @@ def start_iridium():
|
||||
except (ValueError, AttributeError):
|
||||
return jsonify({'status': 'error', 'message': 'Invalid sample rate format'}), 400
|
||||
|
||||
if not shutil.which('iridium-extractor') and not shutil.which('rtl_fm'):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'Iridium tools not found. Requires rtl_fm or iridium-extractor.'
|
||||
}), 503
|
||||
# Get SDR type from request
|
||||
sdr_type_str = data.get('sdr_type', 'rtlsdr')
|
||||
try:
|
||||
sdr_type = SDRType(sdr_type_str)
|
||||
except ValueError:
|
||||
sdr_type = SDRType.RTL_SDR
|
||||
|
||||
# Check for required tools based on SDR type
|
||||
if sdr_type == SDRType.RTL_SDR:
|
||||
if not shutil.which('iridium-extractor') and not shutil.which('rtl_fm'):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'Iridium tools not found. Requires rtl_fm or iridium-extractor.'
|
||||
}), 503
|
||||
else:
|
||||
if not shutil.which('rx_fm'):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'rx_fm not found for {sdr_type.value}. Install SoapySDR tools.'
|
||||
}), 503
|
||||
|
||||
try:
|
||||
cmd = [
|
||||
'rtl_fm',
|
||||
'-f', f'{float(freq)}M',
|
||||
'-g', str(gain),
|
||||
'-s', sample_rate,
|
||||
'-d', str(device),
|
||||
'-'
|
||||
]
|
||||
# Create device object and build command via abstraction layer
|
||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||
builder = SDRFactory.get_builder(sdr_type)
|
||||
|
||||
# Parse sample rate
|
||||
sample_rate_hz = int(float(sample_rate))
|
||||
|
||||
# Build FM demodulation command
|
||||
cmd = builder.build_fm_demod_command(
|
||||
device=sdr_device,
|
||||
frequency_mhz=float(freq),
|
||||
sample_rate=sample_rate_hz,
|
||||
gain=float(gain),
|
||||
ppm=None,
|
||||
modulation='fm',
|
||||
squelch=None
|
||||
)
|
||||
|
||||
app_module.satellite_process = subprocess.Popen(
|
||||
cmd,
|
||||
|
||||
@@ -21,6 +21,7 @@ from utils.logging import pager_logger as logger
|
||||
from utils.validation import validate_frequency, validate_device_index, validate_gain, validate_ppm
|
||||
from utils.sse import format_sse
|
||||
from utils.process import safe_terminate, register_process
|
||||
from utils.sdr import SDRFactory, SDRType, SDRValidationError
|
||||
|
||||
pager_bp = Blueprint('pager', __name__)
|
||||
|
||||
@@ -201,25 +202,27 @@ def start_decoding() -> Response:
|
||||
elif proto == 'FLEX':
|
||||
decoders.extend(['-a', 'FLEX'])
|
||||
|
||||
# Build rtl_fm command
|
||||
rtl_cmd = [
|
||||
'rtl_fm',
|
||||
'-d', str(device),
|
||||
'-f', f'{freq}M',
|
||||
'-M', 'fm',
|
||||
'-s', '22050',
|
||||
]
|
||||
# Get SDR type and build command via abstraction layer
|
||||
sdr_type_str = data.get('sdr_type', 'rtlsdr')
|
||||
try:
|
||||
sdr_type = SDRType(sdr_type_str)
|
||||
except ValueError:
|
||||
sdr_type = SDRType.RTL_SDR
|
||||
|
||||
if gain and gain != '0':
|
||||
rtl_cmd.extend(['-g', str(gain)])
|
||||
# Create device object and get command builder
|
||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||
builder = SDRFactory.get_builder(sdr_type)
|
||||
|
||||
if ppm and ppm != '0':
|
||||
rtl_cmd.extend(['-p', str(ppm)])
|
||||
|
||||
if squelch and squelch != '0':
|
||||
rtl_cmd.extend(['-l', str(squelch)])
|
||||
|
||||
rtl_cmd.append('-')
|
||||
# Build FM demodulation command
|
||||
rtl_cmd = builder.build_fm_demod_command(
|
||||
device=sdr_device,
|
||||
frequency_mhz=freq,
|
||||
sample_rate=22050,
|
||||
gain=float(gain) if gain and gain != '0' else None,
|
||||
ppm=int(ppm) if ppm and ppm != '0' else None,
|
||||
modulation='fm',
|
||||
squelch=squelch if squelch and squelch != 0 else None
|
||||
)
|
||||
|
||||
multimon_cmd = ['multimon-ng', '-t', 'raw'] + decoders + ['-f', 'alpha', '-']
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from utils.logging import sensor_logger as logger
|
||||
from utils.validation import validate_frequency, validate_device_index, validate_gain, validate_ppm
|
||||
from utils.sse import format_sse
|
||||
from utils.process import safe_terminate, register_process
|
||||
from utils.sdr import SDRFactory, SDRType
|
||||
|
||||
sensor_bp = Blueprint('sensor', __name__)
|
||||
|
||||
@@ -82,19 +83,24 @@ def start_sensor() -> Response:
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
# Build rtl_433 command
|
||||
cmd = [
|
||||
'rtl_433',
|
||||
'-d', str(device),
|
||||
'-f', f'{freq}M',
|
||||
'-F', 'json'
|
||||
]
|
||||
# Get SDR type and build command via abstraction layer
|
||||
sdr_type_str = data.get('sdr_type', 'rtlsdr')
|
||||
try:
|
||||
sdr_type = SDRType(sdr_type_str)
|
||||
except ValueError:
|
||||
sdr_type = SDRType.RTL_SDR
|
||||
|
||||
if gain and gain != 0:
|
||||
cmd.extend(['-g', str(int(gain))])
|
||||
# Create device object and get command builder
|
||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||
builder = SDRFactory.get_builder(sdr_type)
|
||||
|
||||
if ppm and ppm != 0:
|
||||
cmd.extend(['-p', str(ppm)])
|
||||
# Build ISM band decoder command
|
||||
cmd = builder.build_ism_command(
|
||||
device=sdr_device,
|
||||
frequency_mhz=freq,
|
||||
gain=float(gain) if gain and gain != 0 else None,
|
||||
ppm=int(ppm) if ppm and ppm != 0 else None
|
||||
)
|
||||
|
||||
full_cmd = ' '.join(cmd)
|
||||
logger.info(f"Running: {full_cmd}")
|
||||
|
||||
247
setup.sh
Executable file
247
setup.sh
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# INTERCEPT Setup Script
|
||||
# Installs Python dependencies and checks for external tools
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}"
|
||||
echo " ___ _ _ _____ _____ ____ ____ _____ ____ _____ "
|
||||
echo " |_ _| \\ | |_ _| ____| _ \\ / ___| ____| _ \\_ _|"
|
||||
echo " | || \\| | | | | _| | |_) | | | _| | |_) || | "
|
||||
echo " | || |\\ | | | | |___| _ <| |___| |___| __/ | | "
|
||||
echo " |___|_| \\_| |_| |_____|_| \\_\\\\____|_____|_| |_| "
|
||||
echo -e "${NC}"
|
||||
echo "Signal Intelligence Platform - Setup Script"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Detect OS
|
||||
detect_os() {
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
OS="macos"
|
||||
PKG_MANAGER="brew"
|
||||
elif [[ -f /etc/debian_version ]]; then
|
||||
OS="debian"
|
||||
PKG_MANAGER="apt"
|
||||
elif [[ -f /etc/redhat-release ]]; then
|
||||
OS="redhat"
|
||||
PKG_MANAGER="dnf"
|
||||
elif [[ -f /etc/arch-release ]]; then
|
||||
OS="arch"
|
||||
PKG_MANAGER="pacman"
|
||||
else
|
||||
OS="unknown"
|
||||
PKG_MANAGER="unknown"
|
||||
fi
|
||||
echo -e "${BLUE}Detected OS:${NC} $OS (package manager: $PKG_MANAGER)"
|
||||
}
|
||||
|
||||
# Check if a command exists
|
||||
check_cmd() {
|
||||
command -v "$1" &> /dev/null
|
||||
}
|
||||
|
||||
# Install Python dependencies
|
||||
install_python_deps() {
|
||||
echo ""
|
||||
echo -e "${BLUE}[1/3] Installing Python dependencies...${NC}"
|
||||
|
||||
if ! check_cmd python3; then
|
||||
echo -e "${RED}Error: Python 3 is not installed${NC}"
|
||||
echo "Please install Python 3.7 or later"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||
echo "Python version: $PYTHON_VERSION"
|
||||
|
||||
if check_cmd pip3; then
|
||||
pip3 install -r requirements.txt
|
||||
elif check_cmd pip; then
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
python3 -m pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Python dependencies installed successfully${NC}"
|
||||
}
|
||||
|
||||
# Check external tools
|
||||
check_tools() {
|
||||
echo ""
|
||||
echo -e "${BLUE}[2/3] Checking external tools...${NC}"
|
||||
echo ""
|
||||
|
||||
MISSING_TOOLS=()
|
||||
|
||||
# Core SDR tools
|
||||
echo "Core SDR Tools:"
|
||||
check_tool "rtl_fm" "RTL-SDR FM demodulator"
|
||||
check_tool "rtl_test" "RTL-SDR device detection"
|
||||
check_tool "multimon-ng" "Pager decoder"
|
||||
check_tool "rtl_433" "433MHz sensor decoder"
|
||||
check_tool "dump1090" "ADS-B decoder"
|
||||
|
||||
echo ""
|
||||
echo "Additional SDR Hardware (optional):"
|
||||
check_tool "SoapySDRUtil" "SoapySDR (for LimeSDR/HackRF)"
|
||||
check_tool "LimeUtil" "LimeSDR tools"
|
||||
check_tool "hackrf_info" "HackRF tools"
|
||||
|
||||
echo ""
|
||||
echo "WiFi Tools:"
|
||||
check_tool "airmon-ng" "WiFi monitor mode"
|
||||
check_tool "airodump-ng" "WiFi scanner"
|
||||
|
||||
echo ""
|
||||
echo "Bluetooth Tools:"
|
||||
check_tool "bluetoothctl" "Bluetooth controller"
|
||||
check_tool "hcitool" "Bluetooth HCI tool"
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Some tools are missing. See installation instructions below.${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
check_tool() {
|
||||
local cmd=$1
|
||||
local desc=$2
|
||||
if check_cmd "$cmd"; then
|
||||
echo -e " ${GREEN}✓${NC} $cmd - $desc"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $cmd - $desc ${YELLOW}(not found)${NC}"
|
||||
MISSING_TOOLS+=("$cmd")
|
||||
fi
|
||||
}
|
||||
|
||||
# Show installation instructions
|
||||
show_install_instructions() {
|
||||
echo ""
|
||||
echo -e "${BLUE}[3/3] Installation instructions for missing tools${NC}"
|
||||
echo ""
|
||||
|
||||
if [ ${#MISSING_TOOLS[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}All tools are installed!${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Run the following commands to install missing tools:"
|
||||
echo ""
|
||||
|
||||
if [[ "$OS" == "macos" ]]; then
|
||||
echo -e "${YELLOW}macOS (Homebrew):${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if Homebrew is installed
|
||||
if ! check_cmd brew; then
|
||||
echo "First, install Homebrew:"
|
||||
echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "# Core SDR tools"
|
||||
echo "brew install librtlsdr multimon-ng rtl_433 dump1090-mutability"
|
||||
echo ""
|
||||
echo "# LimeSDR support (optional)"
|
||||
echo "brew install soapysdr limesuite soapylms7"
|
||||
echo ""
|
||||
echo "# HackRF support (optional)"
|
||||
echo "brew install hackrf soapyhackrf"
|
||||
echo ""
|
||||
echo "# WiFi tools"
|
||||
echo "brew install aircrack-ng"
|
||||
|
||||
elif [[ "$OS" == "debian" ]]; then
|
||||
echo -e "${YELLOW}Ubuntu/Debian:${NC}"
|
||||
echo ""
|
||||
echo "# Core SDR tools"
|
||||
echo "sudo apt update"
|
||||
echo "sudo apt install rtl-sdr multimon-ng rtl-433 dump1090-mutability"
|
||||
echo ""
|
||||
echo "# LimeSDR support (optional)"
|
||||
echo "sudo apt install soapysdr-tools limesuite soapysdr-module-lms7"
|
||||
echo ""
|
||||
echo "# HackRF support (optional)"
|
||||
echo "sudo apt install hackrf soapysdr-module-hackrf"
|
||||
echo ""
|
||||
echo "# WiFi tools"
|
||||
echo "sudo apt install aircrack-ng"
|
||||
echo ""
|
||||
echo "# Bluetooth tools"
|
||||
echo "sudo apt install bluez bluetooth"
|
||||
|
||||
elif [[ "$OS" == "arch" ]]; then
|
||||
echo -e "${YELLOW}Arch Linux:${NC}"
|
||||
echo ""
|
||||
echo "# Core SDR tools"
|
||||
echo "sudo pacman -S rtl-sdr multimon-ng"
|
||||
echo "yay -S rtl_433 dump1090"
|
||||
echo ""
|
||||
echo "# LimeSDR/HackRF support (optional)"
|
||||
echo "sudo pacman -S soapysdr limesuite hackrf"
|
||||
|
||||
elif [[ "$OS" == "redhat" ]]; then
|
||||
echo -e "${YELLOW}Fedora/RHEL:${NC}"
|
||||
echo ""
|
||||
echo "# Core SDR tools"
|
||||
echo "sudo dnf install rtl-sdr"
|
||||
echo "# multimon-ng, rtl_433, dump1090 may need to be built from source"
|
||||
|
||||
else
|
||||
echo "Please install the following tools manually:"
|
||||
for tool in "${MISSING_TOOLS[@]}"; do
|
||||
echo " - $tool"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# RTL-SDR udev rules (Linux only)
|
||||
setup_udev_rules() {
|
||||
if [[ "$OS" != "macos" ]] && [[ "$OS" != "unknown" ]]; then
|
||||
echo ""
|
||||
echo -e "${BLUE}RTL-SDR udev rules (Linux only):${NC}"
|
||||
echo ""
|
||||
echo "If your RTL-SDR is not detected, you may need to add udev rules:"
|
||||
echo ""
|
||||
echo "sudo bash -c 'cat > /etc/udev/rules.d/20-rtlsdr.rules << EOF"
|
||||
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE="0666"'
|
||||
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2832", MODE="0666"'
|
||||
echo "EOF'"
|
||||
echo ""
|
||||
echo "sudo udevadm control --reload-rules"
|
||||
echo "sudo udevadm trigger"
|
||||
echo ""
|
||||
echo "Then unplug and replug your RTL-SDR device."
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
detect_os
|
||||
install_python_deps
|
||||
check_tools
|
||||
show_install_instructions
|
||||
setup_udev_rules
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo -e "${GREEN}Setup complete!${NC}"
|
||||
echo ""
|
||||
echo "To start INTERCEPT:"
|
||||
echo " sudo python3 intercept.py"
|
||||
echo ""
|
||||
echo "Then open http://localhost:5050 in your browser"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -3105,18 +3105,33 @@
|
||||
</div>
|
||||
|
||||
<div class="section" id="rtlDeviceSection">
|
||||
<h3>RTL-SDR Device</h3>
|
||||
<h3>SDR Device</h3>
|
||||
<div class="form-group">
|
||||
<label style="font-size: 11px; color: #888; margin-bottom: 4px;">Hardware Type</label>
|
||||
<select id="sdrTypeSelect" onchange="onSDRTypeChanged()">
|
||||
<option value="rtlsdr">RTL-SDR</option>
|
||||
<option value="limesdr">LimeSDR</option>
|
||||
<option value="hackrf">HackRF</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="font-size: 11px; color: #888; margin-bottom: 4px;">Device</label>
|
||||
<select id="deviceSelect">
|
||||
{% if devices %}
|
||||
{% for device in devices %}
|
||||
<option value="{{ device.index }}">{{ device.index }}: {{ device.name }}</option>
|
||||
<option value="{{ device.index }}" data-sdr-type="{{ device.sdr_type | default('rtlsdr') }}">{{ device.index }}: {{ device.name }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="0">No devices found</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div id="deviceCapabilities" class="info-text" style="font-size: 11px; margin-bottom: 8px; padding: 6px; background: #0a0a1a; border-radius: 4px;">
|
||||
<div style="display: grid; grid-template-columns: auto 1fr; gap: 2px 8px;">
|
||||
<span style="color: #888;">Freq:</span><span id="capFreqRange">24-1766 MHz</span>
|
||||
<span style="color: #888;">Gain:</span><span id="capGainRange">0-50 dB</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="preset-btn" onclick="refreshDevices()" style="width: 100%;">
|
||||
Refresh Devices
|
||||
</button>
|
||||
@@ -4730,7 +4745,8 @@
|
||||
frequency: freq,
|
||||
gain: gain,
|
||||
ppm: ppm,
|
||||
device: device
|
||||
device: device,
|
||||
sdr_type: getSelectedSDRType()
|
||||
};
|
||||
|
||||
fetch('/start_sensor', {
|
||||
@@ -5139,19 +5155,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
// SDR hardware capabilities
|
||||
const sdrCapabilities = {
|
||||
'rtlsdr': { name: 'RTL-SDR', freq_min: 24, freq_max: 1766, gain_min: 0, gain_max: 50 },
|
||||
'limesdr': { name: 'LimeSDR', freq_min: 0.1, freq_max: 3800, gain_min: 0, gain_max: 73 },
|
||||
'hackrf': { name: 'HackRF', freq_min: 1, freq_max: 6000, gain_min: 0, gain_max: 62 }
|
||||
};
|
||||
|
||||
// Current device list with SDR type info
|
||||
let currentDeviceList = [];
|
||||
|
||||
function onSDRTypeChanged() {
|
||||
const sdrType = document.getElementById('sdrTypeSelect').value;
|
||||
const select = document.getElementById('deviceSelect');
|
||||
|
||||
// Filter devices by selected SDR type
|
||||
const filteredDevices = currentDeviceList.filter(d =>
|
||||
(d.sdr_type || 'rtlsdr') === sdrType
|
||||
);
|
||||
|
||||
if (filteredDevices.length === 0) {
|
||||
select.innerHTML = `<option value="0">No ${sdrCapabilities[sdrType]?.name || sdrType} devices found</option>`;
|
||||
} else {
|
||||
select.innerHTML = filteredDevices.map(d =>
|
||||
`<option value="${d.index}" data-sdr-type="${d.sdr_type || 'rtlsdr'}">${d.index}: ${d.name}</option>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Update capabilities display
|
||||
updateCapabilitiesDisplay(sdrType);
|
||||
}
|
||||
|
||||
function updateCapabilitiesDisplay(sdrType) {
|
||||
const caps = sdrCapabilities[sdrType];
|
||||
if (caps) {
|
||||
document.getElementById('capFreqRange').textContent = `${caps.freq_min}-${caps.freq_max} MHz`;
|
||||
document.getElementById('capGainRange').textContent = `${caps.gain_min}-${caps.gain_max} dB`;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDevices() {
|
||||
fetch('/devices')
|
||||
.then(r => r.json())
|
||||
.then(devices => {
|
||||
// Store full device list with SDR type info
|
||||
currentDeviceList = devices;
|
||||
deviceList = devices;
|
||||
const select = document.getElementById('deviceSelect');
|
||||
if (devices.length === 0) {
|
||||
select.innerHTML = '<option value="0">No devices found</option>';
|
||||
} else {
|
||||
select.innerHTML = devices.map(d =>
|
||||
`<option value="${d.index}">${d.index}: ${d.name}</option>`
|
||||
).join('');
|
||||
|
||||
// Auto-select SDR type if devices found
|
||||
if (devices.length > 0) {
|
||||
const firstType = devices[0].sdr_type || 'rtlsdr';
|
||||
document.getElementById('sdrTypeSelect').value = firstType;
|
||||
}
|
||||
|
||||
// Trigger filter update
|
||||
onSDRTypeChanged();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to refresh devices:', err);
|
||||
const select = document.getElementById('deviceSelect');
|
||||
select.innerHTML = '<option value="0">Error loading devices</option>';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5159,6 +5222,10 @@
|
||||
return document.getElementById('deviceSelect').value;
|
||||
}
|
||||
|
||||
function getSelectedSDRType() {
|
||||
return document.getElementById('sdrTypeSelect').value;
|
||||
}
|
||||
|
||||
function getSelectedProtocols() {
|
||||
const protocols = [];
|
||||
if (document.getElementById('proto_pocsag512').checked) protocols.push('POCSAG512');
|
||||
@@ -5187,6 +5254,7 @@
|
||||
squelch: squelch,
|
||||
ppm: ppm,
|
||||
device: device,
|
||||
sdr_type: getSelectedSDRType(),
|
||||
protocols: protocols
|
||||
};
|
||||
|
||||
@@ -8759,11 +8827,12 @@
|
||||
function startAdsbScan() {
|
||||
const gain = document.getElementById('adsbGain').value;
|
||||
const device = getSelectedDevice();
|
||||
const sdr_type = getSelectedSDRType();
|
||||
|
||||
fetch('/adsb/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ gain, device })
|
||||
body: JSON.stringify({ gain, device, sdr_type })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
@@ -9759,11 +9828,12 @@
|
||||
const gain = document.getElementById('iridiumGain').value;
|
||||
const sampleRate = document.getElementById('iridiumSampleRate').value;
|
||||
const device = getSelectedDevice();
|
||||
const sdr_type = getSelectedSDRType();
|
||||
|
||||
fetch('/iridium/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ freq, gain, sampleRate, device })
|
||||
body: JSON.stringify({ freq, gain, sampleRate, device, sdr_type })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
|
||||
@@ -200,6 +200,72 @@ TOOL_DEPENDENCIES = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'sdr_hardware': {
|
||||
'name': 'SDR Hardware Support',
|
||||
'tools': {
|
||||
'SoapySDRUtil': {
|
||||
'required': False,
|
||||
'description': 'Universal SDR abstraction (required for LimeSDR, HackRF)',
|
||||
'install': {
|
||||
'apt': 'sudo apt install soapysdr-tools',
|
||||
'brew': 'brew install soapysdr',
|
||||
'manual': 'https://github.com/pothosware/SoapySDR'
|
||||
}
|
||||
},
|
||||
'rx_fm': {
|
||||
'required': False,
|
||||
'description': 'SoapySDR FM receiver (for non-RTL hardware)',
|
||||
'install': {
|
||||
'manual': 'Part of SoapySDR utilities or build from source'
|
||||
}
|
||||
},
|
||||
'LimeUtil': {
|
||||
'required': False,
|
||||
'description': 'LimeSDR native utilities',
|
||||
'install': {
|
||||
'apt': 'sudo apt install limesuite',
|
||||
'brew': 'brew install limesuite',
|
||||
'manual': 'https://github.com/myriadrf/LimeSuite'
|
||||
}
|
||||
},
|
||||
'SoapyLMS7': {
|
||||
'required': False,
|
||||
'description': 'SoapySDR plugin for LimeSDR',
|
||||
'install': {
|
||||
'apt': 'sudo apt install soapysdr-module-lms7',
|
||||
'brew': 'brew install soapylms7',
|
||||
'manual': 'https://github.com/myriadrf/LimeSuite'
|
||||
}
|
||||
},
|
||||
'hackrf_info': {
|
||||
'required': False,
|
||||
'description': 'HackRF native utilities',
|
||||
'install': {
|
||||
'apt': 'sudo apt install hackrf',
|
||||
'brew': 'brew install hackrf',
|
||||
'manual': 'https://github.com/greatscottgadgets/hackrf'
|
||||
}
|
||||
},
|
||||
'SoapyHackRF': {
|
||||
'required': False,
|
||||
'description': 'SoapySDR plugin for HackRF',
|
||||
'install': {
|
||||
'apt': 'sudo apt install soapysdr-module-hackrf',
|
||||
'brew': 'brew install soapyhackrf',
|
||||
'manual': 'https://github.com/pothosware/SoapyHackRF'
|
||||
}
|
||||
},
|
||||
'readsb': {
|
||||
'required': False,
|
||||
'description': 'ADS-B decoder with SoapySDR support',
|
||||
'install': {
|
||||
'apt': 'Build from source with SoapySDR support',
|
||||
'brew': 'Build from source with SoapySDR support',
|
||||
'manual': 'https://github.com/wiedehopf/readsb'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
196
utils/sdr/__init__.py
Normal file
196
utils/sdr/__init__.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
SDR Hardware Abstraction Layer.
|
||||
|
||||
This module provides a unified interface for multiple SDR hardware types
|
||||
including RTL-SDR, LimeSDR, and HackRF. Use SDRFactory to detect devices
|
||||
and get appropriate command builders.
|
||||
|
||||
Example usage:
|
||||
from utils.sdr import SDRFactory, SDRType
|
||||
|
||||
# Detect all connected devices
|
||||
devices = SDRFactory.detect_devices()
|
||||
|
||||
# Get a command builder for a specific device
|
||||
builder = SDRFactory.get_builder_for_device(devices[0])
|
||||
|
||||
# Or get a builder by type
|
||||
builder = SDRFactory.get_builder(SDRType.RTL_SDR)
|
||||
|
||||
# Build commands
|
||||
cmd = builder.build_fm_demod_command(device, frequency_mhz=153.35)
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||
from .detection import detect_all_devices
|
||||
from .rtlsdr import RTLSDRCommandBuilder
|
||||
from .limesdr import LimeSDRCommandBuilder
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
from .validation import (
|
||||
SDRValidationError,
|
||||
validate_frequency,
|
||||
validate_gain,
|
||||
validate_sample_rate,
|
||||
validate_ppm,
|
||||
validate_device_index,
|
||||
validate_squelch,
|
||||
get_capabilities_for_type,
|
||||
)
|
||||
|
||||
|
||||
class SDRFactory:
|
||||
"""Factory for creating SDR command builders and detecting devices."""
|
||||
|
||||
_builders: dict[SDRType, type[CommandBuilder]] = {
|
||||
SDRType.RTL_SDR: RTLSDRCommandBuilder,
|
||||
SDRType.LIME_SDR: LimeSDRCommandBuilder,
|
||||
SDRType.HACKRF: HackRFCommandBuilder,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_builder(cls, sdr_type: SDRType) -> CommandBuilder:
|
||||
"""
|
||||
Get a command builder for the specified SDR type.
|
||||
|
||||
Args:
|
||||
sdr_type: The SDR hardware type
|
||||
|
||||
Returns:
|
||||
CommandBuilder instance for the specified type
|
||||
|
||||
Raises:
|
||||
ValueError: If the SDR type is not supported
|
||||
"""
|
||||
builder_class = cls._builders.get(sdr_type)
|
||||
if not builder_class:
|
||||
raise ValueError(f"Unsupported SDR type: {sdr_type}")
|
||||
return builder_class()
|
||||
|
||||
@classmethod
|
||||
def get_builder_for_device(cls, device: SDRDevice) -> CommandBuilder:
|
||||
"""
|
||||
Get a command builder for a specific device.
|
||||
|
||||
Args:
|
||||
device: The SDR device
|
||||
|
||||
Returns:
|
||||
CommandBuilder instance for the device's type
|
||||
"""
|
||||
return cls.get_builder(device.sdr_type)
|
||||
|
||||
@classmethod
|
||||
def detect_devices(cls) -> list[SDRDevice]:
|
||||
"""
|
||||
Detect all available SDR devices.
|
||||
|
||||
Returns:
|
||||
List of detected SDR devices
|
||||
"""
|
||||
return detect_all_devices()
|
||||
|
||||
@classmethod
|
||||
def get_supported_types(cls) -> list[SDRType]:
|
||||
"""
|
||||
Get list of supported SDR types.
|
||||
|
||||
Returns:
|
||||
List of supported SDRType values
|
||||
"""
|
||||
return list(cls._builders.keys())
|
||||
|
||||
@classmethod
|
||||
def get_capabilities(cls, sdr_type: SDRType) -> SDRCapabilities:
|
||||
"""
|
||||
Get capabilities for an SDR type.
|
||||
|
||||
Args:
|
||||
sdr_type: The SDR hardware type
|
||||
|
||||
Returns:
|
||||
SDRCapabilities for the specified type
|
||||
"""
|
||||
builder = cls.get_builder(sdr_type)
|
||||
return builder.get_capabilities()
|
||||
|
||||
@classmethod
|
||||
def get_all_capabilities(cls) -> dict[str, dict]:
|
||||
"""
|
||||
Get capabilities for all supported SDR types.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping SDR type names to capability dicts
|
||||
"""
|
||||
capabilities = {}
|
||||
for sdr_type in cls._builders:
|
||||
caps = cls.get_capabilities(sdr_type)
|
||||
capabilities[sdr_type.value] = {
|
||||
'name': sdr_type.name.replace('_', ' '),
|
||||
'freq_min_mhz': caps.freq_min_mhz,
|
||||
'freq_max_mhz': caps.freq_max_mhz,
|
||||
'gain_min': caps.gain_min,
|
||||
'gain_max': caps.gain_max,
|
||||
'sample_rates': caps.sample_rates,
|
||||
'supports_bias_t': caps.supports_bias_t,
|
||||
'supports_ppm': caps.supports_ppm,
|
||||
'tx_capable': caps.tx_capable,
|
||||
}
|
||||
return capabilities
|
||||
|
||||
@classmethod
|
||||
def create_default_device(
|
||||
cls,
|
||||
sdr_type: SDRType,
|
||||
index: int = 0,
|
||||
serial: str = 'N/A'
|
||||
) -> SDRDevice:
|
||||
"""
|
||||
Create a default device object for a given SDR type.
|
||||
|
||||
Useful when device detection didn't provide full details but
|
||||
you know the hardware type.
|
||||
|
||||
Args:
|
||||
sdr_type: The SDR hardware type
|
||||
index: Device index (default 0)
|
||||
serial: Device serial (default 'N/A')
|
||||
|
||||
Returns:
|
||||
SDRDevice with default capabilities for the type
|
||||
"""
|
||||
caps = cls.get_capabilities(sdr_type)
|
||||
return SDRDevice(
|
||||
sdr_type=sdr_type,
|
||||
index=index,
|
||||
name=f'{sdr_type.name.replace("_", " ")} Device {index}',
|
||||
serial=serial,
|
||||
driver=sdr_type.value,
|
||||
capabilities=caps
|
||||
)
|
||||
|
||||
|
||||
# Export commonly used items at package level
|
||||
__all__ = [
|
||||
# Factory
|
||||
'SDRFactory',
|
||||
# Types and classes
|
||||
'SDRType',
|
||||
'SDRDevice',
|
||||
'SDRCapabilities',
|
||||
'CommandBuilder',
|
||||
# Builders
|
||||
'RTLSDRCommandBuilder',
|
||||
'LimeSDRCommandBuilder',
|
||||
'HackRFCommandBuilder',
|
||||
# Validation
|
||||
'SDRValidationError',
|
||||
'validate_frequency',
|
||||
'validate_gain',
|
||||
'validate_sample_rate',
|
||||
'validate_ppm',
|
||||
'validate_device_index',
|
||||
'validate_squelch',
|
||||
'get_capabilities_for_type',
|
||||
]
|
||||
149
utils/sdr/base.py
Normal file
149
utils/sdr/base.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Base classes and types for SDR hardware abstraction.
|
||||
|
||||
This module provides the core abstractions for supporting multiple SDR hardware
|
||||
types (RTL-SDR, LimeSDR, HackRF, etc.) through a unified interface.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SDRType(Enum):
|
||||
"""Supported SDR hardware types."""
|
||||
RTL_SDR = "rtlsdr"
|
||||
LIME_SDR = "limesdr"
|
||||
HACKRF = "hackrf"
|
||||
# Future support
|
||||
# USRP = "usrp"
|
||||
# BLADE_RF = "bladerf"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SDRCapabilities:
|
||||
"""Hardware capabilities for an SDR device."""
|
||||
sdr_type: SDRType
|
||||
freq_min_mhz: float # Minimum frequency in MHz
|
||||
freq_max_mhz: float # Maximum frequency in MHz
|
||||
gain_min: float # Minimum gain in dB
|
||||
gain_max: float # Maximum gain in dB
|
||||
sample_rates: list[int] = field(default_factory=list) # Supported sample rates
|
||||
supports_bias_t: bool = False # Bias-T support
|
||||
supports_ppm: bool = True # PPM correction support
|
||||
tx_capable: bool = False # Can transmit
|
||||
|
||||
|
||||
@dataclass
|
||||
class SDRDevice:
|
||||
"""Detected SDR device."""
|
||||
sdr_type: SDRType
|
||||
index: int
|
||||
name: str
|
||||
serial: str
|
||||
driver: str # e.g., "rtlsdr", "lime", "hackrf"
|
||||
capabilities: SDRCapabilities
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'index': self.index,
|
||||
'name': self.name,
|
||||
'serial': self.serial,
|
||||
'sdr_type': self.sdr_type.value,
|
||||
'driver': self.driver,
|
||||
'capabilities': {
|
||||
'freq_min_mhz': self.capabilities.freq_min_mhz,
|
||||
'freq_max_mhz': self.capabilities.freq_max_mhz,
|
||||
'gain_min': self.capabilities.gain_min,
|
||||
'gain_max': self.capabilities.gain_max,
|
||||
'sample_rates': self.capabilities.sample_rates,
|
||||
'supports_bias_t': self.capabilities.supports_bias_t,
|
||||
'supports_ppm': self.capabilities.supports_ppm,
|
||||
'tx_capable': self.capabilities.tx_capable,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CommandBuilder(ABC):
|
||||
"""Abstract base class for building SDR commands."""
|
||||
|
||||
@abstractmethod
|
||||
def build_fm_demod_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float,
|
||||
sample_rate: int = 22050,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None,
|
||||
modulation: str = "fm",
|
||||
squelch: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build FM demodulation command (for pager, iridium).
|
||||
|
||||
Args:
|
||||
device: The SDR device to use
|
||||
frequency_mhz: Center frequency in MHz
|
||||
sample_rate: Audio sample rate (default 22050 for pager)
|
||||
gain: Gain in dB (None for auto)
|
||||
ppm: PPM frequency correction
|
||||
modulation: Modulation type (fm, am, etc.)
|
||||
squelch: Squelch level
|
||||
|
||||
Returns:
|
||||
Command as list of strings for subprocess
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_adsb_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
gain: Optional[float] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build ADS-B decoder command.
|
||||
|
||||
Args:
|
||||
device: The SDR device to use
|
||||
gain: Gain in dB (None for auto)
|
||||
|
||||
Returns:
|
||||
Command as list of strings for subprocess
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_ism_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float = 433.92,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build ISM band decoder command (433MHz sensors).
|
||||
|
||||
Args:
|
||||
device: The SDR device to use
|
||||
frequency_mhz: Center frequency in MHz (default 433.92)
|
||||
gain: Gain in dB (None for auto)
|
||||
ppm: PPM frequency correction
|
||||
|
||||
Returns:
|
||||
Command as list of strings for subprocess
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_capabilities(self) -> SDRCapabilities:
|
||||
"""Return hardware capabilities for this SDR type."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_sdr_type(cls) -> SDRType:
|
||||
"""Return the SDR type this builder handles."""
|
||||
pass
|
||||
306
utils/sdr/detection.py
Normal file
306
utils/sdr/detection.py
Normal file
@@ -0,0 +1,306 @@
|
||||
"""
|
||||
Multi-hardware SDR device detection.
|
||||
|
||||
Detects RTL-SDR devices via rtl_test and other SDR hardware via SoapySDR.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Optional
|
||||
|
||||
from .base import SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_tool(name: str) -> bool:
|
||||
"""Check if a tool is available in PATH."""
|
||||
return shutil.which(name) is not None
|
||||
|
||||
|
||||
def _get_capabilities_for_type(sdr_type: SDRType) -> SDRCapabilities:
|
||||
"""Get default capabilities for an SDR type."""
|
||||
# Import here to avoid circular imports
|
||||
from .rtlsdr import RTLSDRCommandBuilder
|
||||
from .limesdr import LimeSDRCommandBuilder
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
|
||||
builders = {
|
||||
SDRType.RTL_SDR: RTLSDRCommandBuilder,
|
||||
SDRType.LIME_SDR: LimeSDRCommandBuilder,
|
||||
SDRType.HACKRF: HackRFCommandBuilder,
|
||||
}
|
||||
|
||||
builder_class = builders.get(sdr_type)
|
||||
if builder_class:
|
||||
return builder_class.CAPABILITIES
|
||||
|
||||
# Fallback generic capabilities
|
||||
return SDRCapabilities(
|
||||
sdr_type=sdr_type,
|
||||
freq_min_mhz=1.0,
|
||||
freq_max_mhz=6000.0,
|
||||
gain_min=0.0,
|
||||
gain_max=50.0,
|
||||
sample_rates=[2048000],
|
||||
supports_bias_t=False,
|
||||
supports_ppm=False,
|
||||
tx_capable=False
|
||||
)
|
||||
|
||||
|
||||
def _driver_to_sdr_type(driver: str) -> Optional[SDRType]:
|
||||
"""Map SoapySDR driver name to SDRType."""
|
||||
mapping = {
|
||||
'rtlsdr': SDRType.RTL_SDR,
|
||||
'lime': SDRType.LIME_SDR,
|
||||
'limesdr': SDRType.LIME_SDR,
|
||||
'hackrf': SDRType.HACKRF,
|
||||
# Future support
|
||||
# 'uhd': SDRType.USRP,
|
||||
# 'bladerf': SDRType.BLADE_RF,
|
||||
}
|
||||
return mapping.get(driver.lower())
|
||||
|
||||
|
||||
def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect RTL-SDR devices using rtl_test.
|
||||
|
||||
This uses the native rtl_test tool for best compatibility with
|
||||
existing RTL-SDR installations.
|
||||
"""
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
if not _check_tool('rtl_test'):
|
||||
logger.debug("rtl_test not found, skipping RTL-SDR detection")
|
||||
return devices
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['rtl_test', '-t'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
output = result.stderr + result.stdout
|
||||
|
||||
# Parse device info from rtl_test output
|
||||
# Format: "0: Realtek, RTL2838UHIDIR, SN: 00000001"
|
||||
device_pattern = r'(\d+):\s+(.+?)(?:,\s*SN:\s*(\S+))?$'
|
||||
|
||||
from .rtlsdr import RTLSDRCommandBuilder
|
||||
|
||||
for line in output.split('\n'):
|
||||
line = line.strip()
|
||||
match = re.match(device_pattern, line)
|
||||
if match:
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.RTL_SDR,
|
||||
index=int(match.group(1)),
|
||||
name=match.group(2).strip().rstrip(','),
|
||||
serial=match.group(3) or 'N/A',
|
||||
driver='rtlsdr',
|
||||
capabilities=RTLSDRCommandBuilder.CAPABILITIES
|
||||
))
|
||||
|
||||
# Fallback: if we found devices but couldn't parse details
|
||||
if not devices:
|
||||
found_match = re.search(r'Found (\d+) device', output)
|
||||
if found_match:
|
||||
count = int(found_match.group(1))
|
||||
for i in range(count):
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.RTL_SDR,
|
||||
index=i,
|
||||
name=f'RTL-SDR Device {i}',
|
||||
serial='Unknown',
|
||||
driver='rtlsdr',
|
||||
capabilities=RTLSDRCommandBuilder.CAPABILITIES
|
||||
))
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("rtl_test timed out")
|
||||
except Exception as e:
|
||||
logger.debug(f"RTL-SDR detection error: {e}")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def detect_soapy_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect SDR devices via SoapySDR.
|
||||
|
||||
This detects LimeSDR, HackRF, USRP, BladeRF, and other SoapySDR-compatible
|
||||
devices. RTL-SDR devices may also appear here but we prefer the native
|
||||
detection for those.
|
||||
"""
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
if not _check_tool('SoapySDRUtil'):
|
||||
logger.debug("SoapySDRUtil not found, skipping SoapySDR detection")
|
||||
return devices
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['SoapySDRUtil', '--find'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
# Parse SoapySDR output
|
||||
# Format varies but typically includes lines like:
|
||||
# " driver = lime"
|
||||
# " serial = 0009060B00123456"
|
||||
# " label = LimeSDR Mini [USB 3.0] 0009060B00123456"
|
||||
|
||||
current_device: dict = {}
|
||||
device_counts: dict[SDRType, int] = {}
|
||||
|
||||
for line in result.stdout.split('\n'):
|
||||
line = line.strip()
|
||||
|
||||
# Start of new device block
|
||||
if line.startswith('Found device'):
|
||||
if current_device.get('driver'):
|
||||
_add_soapy_device(devices, current_device, device_counts)
|
||||
current_device = {}
|
||||
continue
|
||||
|
||||
# Parse key = value pairs
|
||||
if ' = ' in line:
|
||||
key, value = line.split(' = ', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
current_device[key] = value
|
||||
|
||||
# Don't forget the last device
|
||||
if current_device.get('driver'):
|
||||
_add_soapy_device(devices, current_device, device_counts)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("SoapySDRUtil timed out")
|
||||
except Exception as e:
|
||||
logger.debug(f"SoapySDR detection error: {e}")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def _add_soapy_device(
|
||||
devices: list[SDRDevice],
|
||||
device_info: dict,
|
||||
device_counts: dict[SDRType, int]
|
||||
) -> None:
|
||||
"""Add a device from SoapySDR detection to the list."""
|
||||
driver = device_info.get('driver', '').lower()
|
||||
sdr_type = _driver_to_sdr_type(driver)
|
||||
|
||||
if not sdr_type:
|
||||
logger.debug(f"Unknown SoapySDR driver: {driver}")
|
||||
return
|
||||
|
||||
# Skip RTL-SDR devices from SoapySDR (we use native detection)
|
||||
if sdr_type == SDRType.RTL_SDR:
|
||||
return
|
||||
|
||||
# Track device index per type
|
||||
if sdr_type not in device_counts:
|
||||
device_counts[sdr_type] = 0
|
||||
|
||||
index = device_counts[sdr_type]
|
||||
device_counts[sdr_type] += 1
|
||||
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=sdr_type,
|
||||
index=index,
|
||||
name=device_info.get('label', device_info.get('driver', 'Unknown')),
|
||||
serial=device_info.get('serial', 'N/A'),
|
||||
driver=driver,
|
||||
capabilities=_get_capabilities_for_type(sdr_type)
|
||||
))
|
||||
|
||||
|
||||
def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect HackRF devices using native hackrf_info tool.
|
||||
|
||||
Fallback for when SoapySDR is not available.
|
||||
"""
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
if not _check_tool('hackrf_info'):
|
||||
return devices
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['hackrf_info'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
# Parse hackrf_info output
|
||||
# Look for "Serial number:" lines
|
||||
serial_pattern = r'Serial number:\s*(\S+)'
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
|
||||
serials_found = re.findall(serial_pattern, result.stdout)
|
||||
|
||||
for i, serial in enumerate(serials_found):
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.HACKRF,
|
||||
index=i,
|
||||
name=f'HackRF One',
|
||||
serial=serial,
|
||||
driver='hackrf',
|
||||
capabilities=HackRFCommandBuilder.CAPABILITIES
|
||||
))
|
||||
|
||||
# Fallback: check if any HackRF found without serial
|
||||
if not devices and 'Found HackRF' in result.stdout:
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.HACKRF,
|
||||
index=0,
|
||||
name='HackRF One',
|
||||
serial='Unknown',
|
||||
driver='hackrf',
|
||||
capabilities=HackRFCommandBuilder.CAPABILITIES
|
||||
))
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"HackRF detection error: {e}")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def detect_all_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect all connected SDR devices across all supported hardware types.
|
||||
|
||||
Returns a unified list of SDRDevice objects sorted by type and index.
|
||||
"""
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
# RTL-SDR via native tool (primary method)
|
||||
devices.extend(detect_rtlsdr_devices())
|
||||
|
||||
# SoapySDR devices (LimeSDR, HackRF, etc.)
|
||||
soapy_devices = detect_soapy_devices()
|
||||
devices.extend(soapy_devices)
|
||||
|
||||
# Native HackRF detection (fallback if SoapySDR didn't find it)
|
||||
hackrf_from_soapy = any(d.sdr_type == SDRType.HACKRF for d in soapy_devices)
|
||||
if not hackrf_from_soapy:
|
||||
devices.extend(detect_hackrf_devices())
|
||||
|
||||
# Sort by type name, then index
|
||||
devices.sort(key=lambda d: (d.sdr_type.value, d.index))
|
||||
|
||||
logger.info(f"Detected {len(devices)} SDR device(s)")
|
||||
for d in devices:
|
||||
logger.debug(f" {d.sdr_type.value}:{d.index} - {d.name} (serial: {d.serial})")
|
||||
|
||||
return devices
|
||||
148
utils/sdr/hackrf.py
Normal file
148
utils/sdr/hackrf.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
HackRF command builder implementation.
|
||||
|
||||
Uses SoapySDR-based tools for FM demodulation and signal capture.
|
||||
HackRF supports 1 MHz to 6 GHz frequency range.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
|
||||
class HackRFCommandBuilder(CommandBuilder):
|
||||
"""HackRF command builder using SoapySDR tools."""
|
||||
|
||||
CAPABILITIES = SDRCapabilities(
|
||||
sdr_type=SDRType.HACKRF,
|
||||
freq_min_mhz=1.0, # 1 MHz
|
||||
freq_max_mhz=6000.0, # 6 GHz
|
||||
gain_min=0.0,
|
||||
gain_max=62.0, # LNA (0-40) + VGA (0-62)
|
||||
sample_rates=[2000000, 4000000, 8000000, 10000000, 20000000],
|
||||
supports_bias_t=True,
|
||||
supports_ppm=False,
|
||||
tx_capable=True
|
||||
)
|
||||
|
||||
def _build_device_string(self, device: SDRDevice) -> str:
|
||||
"""Build SoapySDR device string for HackRF."""
|
||||
if device.serial and device.serial != 'N/A':
|
||||
return f'driver=hackrf,serial={device.serial}'
|
||||
return f'driver=hackrf'
|
||||
|
||||
def _split_gain(self, gain: float) -> tuple[int, int]:
|
||||
"""
|
||||
Split total gain into LNA and VGA components.
|
||||
|
||||
HackRF has two gain stages:
|
||||
- LNA: 0-40 dB (RF amplifier)
|
||||
- VGA: 0-62 dB (IF amplifier)
|
||||
|
||||
This function distributes the requested gain across both stages.
|
||||
"""
|
||||
if gain <= 40:
|
||||
# All to LNA first
|
||||
return int(gain), 0
|
||||
else:
|
||||
# Max out LNA, rest to VGA
|
||||
lna = 40
|
||||
vga = min(62, int(gain - 40))
|
||||
return lna, vga
|
||||
|
||||
def build_fm_demod_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float,
|
||||
sample_rate: int = 22050,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None,
|
||||
modulation: str = "fm",
|
||||
squelch: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build SoapySDR rx_fm command for FM demodulation.
|
||||
|
||||
For pager decoding and iridium capture with HackRF.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
cmd = [
|
||||
'rx_fm',
|
||||
'-d', device_str,
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-M', modulation,
|
||||
'-s', str(sample_rate),
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
lna, vga = self._split_gain(gain)
|
||||
cmd.extend(['-g', f'LNA={lna},VGA={vga}'])
|
||||
|
||||
if squelch is not None and squelch > 0:
|
||||
cmd.extend(['-l', str(squelch)])
|
||||
|
||||
# Output to stdout
|
||||
cmd.append('-')
|
||||
|
||||
return cmd
|
||||
|
||||
def build_adsb_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
gain: Optional[float] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build dump1090/readsb command with SoapySDR support for ADS-B decoding.
|
||||
|
||||
Uses readsb which has better SoapySDR support.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
cmd = [
|
||||
'readsb',
|
||||
'--net',
|
||||
'--device-type', 'soapysdr',
|
||||
'--device', device_str,
|
||||
'--quiet'
|
||||
]
|
||||
|
||||
if gain is not None:
|
||||
cmd.extend(['--gain', str(int(gain))])
|
||||
|
||||
return cmd
|
||||
|
||||
def build_ism_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float = 433.92,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build rtl_433 command with SoapySDR support for ISM band decoding.
|
||||
|
||||
rtl_433 has native SoapySDR support via -d flag.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
cmd = [
|
||||
'rtl_433',
|
||||
'-d', device_str,
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-F', 'json'
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
cmd.extend(['-g', str(int(gain))])
|
||||
|
||||
return cmd
|
||||
|
||||
def get_capabilities(self) -> SDRCapabilities:
|
||||
"""Return HackRF capabilities."""
|
||||
return self.CAPABILITIES
|
||||
|
||||
@classmethod
|
||||
def get_sdr_type(cls) -> SDRType:
|
||||
"""Return SDR type."""
|
||||
return SDRType.HACKRF
|
||||
136
utils/sdr/limesdr.py
Normal file
136
utils/sdr/limesdr.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
LimeSDR command builder implementation.
|
||||
|
||||
Uses SoapySDR-based tools for FM demodulation and signal capture.
|
||||
LimeSDR supports 100 kHz to 3.8 GHz frequency range.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
|
||||
class LimeSDRCommandBuilder(CommandBuilder):
|
||||
"""LimeSDR command builder using SoapySDR tools."""
|
||||
|
||||
CAPABILITIES = SDRCapabilities(
|
||||
sdr_type=SDRType.LIME_SDR,
|
||||
freq_min_mhz=0.1, # 100 kHz
|
||||
freq_max_mhz=3800.0, # 3.8 GHz
|
||||
gain_min=0.0,
|
||||
gain_max=73.0, # Combined LNA + TIA + PGA
|
||||
sample_rates=[1000000, 2000000, 4000000, 8000000, 10000000, 20000000],
|
||||
supports_bias_t=False,
|
||||
supports_ppm=False, # Uses TCXO, no PPM correction needed
|
||||
tx_capable=True
|
||||
)
|
||||
|
||||
def _build_device_string(self, device: SDRDevice) -> str:
|
||||
"""Build SoapySDR device string for LimeSDR."""
|
||||
if device.serial and device.serial != 'N/A':
|
||||
return f'driver=lime,serial={device.serial}'
|
||||
return f'driver=lime'
|
||||
|
||||
def build_fm_demod_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float,
|
||||
sample_rate: int = 22050,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None,
|
||||
modulation: str = "fm",
|
||||
squelch: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build SoapySDR rx_fm command for FM demodulation.
|
||||
|
||||
For pager decoding and iridium capture with LimeSDR.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
cmd = [
|
||||
'rx_fm',
|
||||
'-d', device_str,
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-M', modulation,
|
||||
'-s', str(sample_rate),
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
# LimeSDR gain is applied to LNAH element
|
||||
cmd.extend(['-g', f'LNAH={int(gain)}'])
|
||||
|
||||
if squelch is not None and squelch > 0:
|
||||
cmd.extend(['-l', str(squelch)])
|
||||
|
||||
# Output to stdout
|
||||
cmd.append('-')
|
||||
|
||||
return cmd
|
||||
|
||||
def build_adsb_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
gain: Optional[float] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build dump1090 command with SoapySDR support for ADS-B decoding.
|
||||
|
||||
Uses dump1090 compiled with SoapySDR support, or readsb as alternative.
|
||||
Note: Requires dump1090 with SoapySDR support or readsb.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
# Try readsb first (better SoapySDR support), fallback to dump1090
|
||||
cmd = [
|
||||
'readsb',
|
||||
'--net',
|
||||
'--device-type', 'soapysdr',
|
||||
'--device', device_str,
|
||||
'--quiet'
|
||||
]
|
||||
|
||||
if gain is not None:
|
||||
cmd.extend(['--gain', str(int(gain))])
|
||||
|
||||
return cmd
|
||||
|
||||
def build_ism_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float = 433.92,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build rtl_433 command with SoapySDR support for ISM band decoding.
|
||||
|
||||
rtl_433 has native SoapySDR support via -d flag.
|
||||
"""
|
||||
device_str = self._build_device_string(device)
|
||||
|
||||
cmd = [
|
||||
'rtl_433',
|
||||
'-d', device_str,
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-F', 'json'
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
cmd.extend(['-g', str(int(gain))])
|
||||
|
||||
# PPM not typically needed for LimeSDR (TCXO)
|
||||
# but include if specified
|
||||
if ppm is not None and ppm != 0:
|
||||
cmd.extend(['-p', str(ppm)])
|
||||
|
||||
return cmd
|
||||
|
||||
def get_capabilities(self) -> SDRCapabilities:
|
||||
"""Return LimeSDR capabilities."""
|
||||
return self.CAPABILITIES
|
||||
|
||||
@classmethod
|
||||
def get_sdr_type(cls) -> SDRType:
|
||||
"""Return SDR type."""
|
||||
return SDRType.LIME_SDR
|
||||
121
utils/sdr/rtlsdr.py
Normal file
121
utils/sdr/rtlsdr.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
RTL-SDR command builder implementation.
|
||||
|
||||
Uses native rtl_* tools (rtl_fm, rtl_433) and dump1090 for maximum compatibility
|
||||
with existing RTL-SDR installations. No SoapySDR dependency required.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
|
||||
class RTLSDRCommandBuilder(CommandBuilder):
|
||||
"""RTL-SDR command builder using native rtl_* tools."""
|
||||
|
||||
CAPABILITIES = SDRCapabilities(
|
||||
sdr_type=SDRType.RTL_SDR,
|
||||
freq_min_mhz=24.0,
|
||||
freq_max_mhz=1766.0,
|
||||
gain_min=0.0,
|
||||
gain_max=49.6,
|
||||
sample_rates=[250000, 1024000, 1800000, 2048000, 2400000],
|
||||
supports_bias_t=True,
|
||||
supports_ppm=True,
|
||||
tx_capable=False
|
||||
)
|
||||
|
||||
def build_fm_demod_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float,
|
||||
sample_rate: int = 22050,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None,
|
||||
modulation: str = "fm",
|
||||
squelch: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build rtl_fm command for FM demodulation.
|
||||
|
||||
Used for pager decoding and iridium capture.
|
||||
"""
|
||||
cmd = [
|
||||
'rtl_fm',
|
||||
'-d', str(device.index),
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-M', modulation,
|
||||
'-s', str(sample_rate),
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
cmd.extend(['-g', str(gain)])
|
||||
|
||||
if ppm is not None and ppm != 0:
|
||||
cmd.extend(['-p', str(ppm)])
|
||||
|
||||
if squelch is not None and squelch > 0:
|
||||
cmd.extend(['-l', str(squelch)])
|
||||
|
||||
# Output to stdout for piping
|
||||
cmd.append('-')
|
||||
|
||||
return cmd
|
||||
|
||||
def build_adsb_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
gain: Optional[float] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build dump1090 command for ADS-B decoding.
|
||||
|
||||
Uses dump1090 with network output for SBS data streaming.
|
||||
"""
|
||||
cmd = [
|
||||
'dump1090',
|
||||
'--net',
|
||||
'--device-index', str(device.index),
|
||||
'--quiet'
|
||||
]
|
||||
|
||||
if gain is not None:
|
||||
cmd.extend(['--gain', str(int(gain))])
|
||||
|
||||
return cmd
|
||||
|
||||
def build_ism_command(
|
||||
self,
|
||||
device: SDRDevice,
|
||||
frequency_mhz: float = 433.92,
|
||||
gain: Optional[float] = None,
|
||||
ppm: Optional[int] = None
|
||||
) -> list[str]:
|
||||
"""
|
||||
Build rtl_433 command for ISM band sensor decoding.
|
||||
|
||||
Outputs JSON for easy parsing.
|
||||
"""
|
||||
cmd = [
|
||||
'rtl_433',
|
||||
'-d', str(device.index),
|
||||
'-f', f'{frequency_mhz}M',
|
||||
'-F', 'json'
|
||||
]
|
||||
|
||||
if gain is not None and gain > 0:
|
||||
cmd.extend(['-g', str(int(gain))])
|
||||
|
||||
if ppm is not None and ppm != 0:
|
||||
cmd.extend(['-p', str(ppm)])
|
||||
|
||||
return cmd
|
||||
|
||||
def get_capabilities(self) -> SDRCapabilities:
|
||||
"""Return RTL-SDR capabilities."""
|
||||
return self.CAPABILITIES
|
||||
|
||||
@classmethod
|
||||
def get_sdr_type(cls) -> SDRType:
|
||||
"""Return SDR type."""
|
||||
return SDRType.RTL_SDR
|
||||
257
utils/sdr/validation.py
Normal file
257
utils/sdr/validation.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
Hardware-specific parameter validation for SDR devices.
|
||||
|
||||
Validates frequency, gain, sample rate, and other parameters against
|
||||
the capabilities of specific SDR hardware.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .base import SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
|
||||
class SDRValidationError(ValueError):
|
||||
"""Raised when SDR parameter validation fails."""
|
||||
pass
|
||||
|
||||
|
||||
def validate_frequency(
|
||||
freq_mhz: float,
|
||||
device: Optional[SDRDevice] = None,
|
||||
capabilities: Optional[SDRCapabilities] = None
|
||||
) -> float:
|
||||
"""
|
||||
Validate frequency against device capabilities.
|
||||
|
||||
Args:
|
||||
freq_mhz: Frequency in MHz
|
||||
device: SDR device (optional, takes precedence)
|
||||
capabilities: SDR capabilities (used if device not provided)
|
||||
|
||||
Returns:
|
||||
Validated frequency in MHz
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If frequency is out of range
|
||||
"""
|
||||
if device:
|
||||
caps = device.capabilities
|
||||
elif capabilities:
|
||||
caps = capabilities
|
||||
else:
|
||||
# Default RTL-SDR range for backwards compatibility
|
||||
caps = SDRCapabilities(
|
||||
sdr_type=SDRType.RTL_SDR,
|
||||
freq_min_mhz=24.0,
|
||||
freq_max_mhz=1766.0,
|
||||
gain_min=0.0,
|
||||
gain_max=50.0
|
||||
)
|
||||
|
||||
if not caps.freq_min_mhz <= freq_mhz <= caps.freq_max_mhz:
|
||||
raise SDRValidationError(
|
||||
f"Frequency {freq_mhz} MHz out of range for {caps.sdr_type.value}. "
|
||||
f"Valid range: {caps.freq_min_mhz}-{caps.freq_max_mhz} MHz"
|
||||
)
|
||||
|
||||
return freq_mhz
|
||||
|
||||
|
||||
def validate_gain(
|
||||
gain: float,
|
||||
device: Optional[SDRDevice] = None,
|
||||
capabilities: Optional[SDRCapabilities] = None
|
||||
) -> float:
|
||||
"""
|
||||
Validate gain against device capabilities.
|
||||
|
||||
Args:
|
||||
gain: Gain in dB
|
||||
device: SDR device (optional, takes precedence)
|
||||
capabilities: SDR capabilities (used if device not provided)
|
||||
|
||||
Returns:
|
||||
Validated gain in dB
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If gain is out of range
|
||||
"""
|
||||
if device:
|
||||
caps = device.capabilities
|
||||
elif capabilities:
|
||||
caps = capabilities
|
||||
else:
|
||||
# Default range for backwards compatibility
|
||||
caps = SDRCapabilities(
|
||||
sdr_type=SDRType.RTL_SDR,
|
||||
freq_min_mhz=24.0,
|
||||
freq_max_mhz=1766.0,
|
||||
gain_min=0.0,
|
||||
gain_max=50.0
|
||||
)
|
||||
|
||||
# Allow 0 for auto gain
|
||||
if gain == 0:
|
||||
return gain
|
||||
|
||||
if not caps.gain_min <= gain <= caps.gain_max:
|
||||
raise SDRValidationError(
|
||||
f"Gain {gain} dB out of range for {caps.sdr_type.value}. "
|
||||
f"Valid range: {caps.gain_min}-{caps.gain_max} dB"
|
||||
)
|
||||
|
||||
return gain
|
||||
|
||||
|
||||
def validate_sample_rate(
|
||||
rate: int,
|
||||
device: Optional[SDRDevice] = None,
|
||||
capabilities: Optional[SDRCapabilities] = None,
|
||||
snap_to_nearest: bool = True
|
||||
) -> int:
|
||||
"""
|
||||
Validate sample rate against device capabilities.
|
||||
|
||||
Args:
|
||||
rate: Sample rate in Hz
|
||||
device: SDR device (optional, takes precedence)
|
||||
capabilities: SDR capabilities (used if device not provided)
|
||||
snap_to_nearest: If True, return nearest valid rate instead of raising
|
||||
|
||||
Returns:
|
||||
Validated sample rate in Hz
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If rate is invalid and snap_to_nearest is False
|
||||
"""
|
||||
if device:
|
||||
caps = device.capabilities
|
||||
elif capabilities:
|
||||
caps = capabilities
|
||||
else:
|
||||
return rate # No validation without capabilities
|
||||
|
||||
if not caps.sample_rates:
|
||||
return rate # No restrictions
|
||||
|
||||
if rate in caps.sample_rates:
|
||||
return rate
|
||||
|
||||
if snap_to_nearest:
|
||||
# Find closest valid rate
|
||||
closest = min(caps.sample_rates, key=lambda x: abs(x - rate))
|
||||
return closest
|
||||
|
||||
raise SDRValidationError(
|
||||
f"Sample rate {rate} Hz not supported by {caps.sdr_type.value}. "
|
||||
f"Valid rates: {caps.sample_rates}"
|
||||
)
|
||||
|
||||
|
||||
def validate_ppm(
|
||||
ppm: int,
|
||||
device: Optional[SDRDevice] = None,
|
||||
capabilities: Optional[SDRCapabilities] = None
|
||||
) -> int:
|
||||
"""
|
||||
Validate PPM frequency correction.
|
||||
|
||||
Args:
|
||||
ppm: PPM correction value
|
||||
device: SDR device (optional, takes precedence)
|
||||
capabilities: SDR capabilities (used if device not provided)
|
||||
|
||||
Returns:
|
||||
Validated PPM value
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If PPM is out of range or not supported
|
||||
"""
|
||||
if device:
|
||||
caps = device.capabilities
|
||||
elif capabilities:
|
||||
caps = capabilities
|
||||
else:
|
||||
caps = None
|
||||
|
||||
# Check if device supports PPM
|
||||
if caps and not caps.supports_ppm:
|
||||
if ppm != 0:
|
||||
# Warn but don't fail - some hardware just ignores PPM
|
||||
pass
|
||||
return 0 # Return 0 to indicate no correction
|
||||
|
||||
# Standard PPM range
|
||||
if not -1000 <= ppm <= 1000:
|
||||
raise SDRValidationError(
|
||||
f"PPM correction {ppm} out of range. Valid range: -1000 to 1000"
|
||||
)
|
||||
|
||||
return ppm
|
||||
|
||||
|
||||
def validate_device_index(index: int) -> int:
|
||||
"""
|
||||
Validate device index.
|
||||
|
||||
Args:
|
||||
index: Device index (0-255)
|
||||
|
||||
Returns:
|
||||
Validated device index
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If index is out of range
|
||||
"""
|
||||
if not 0 <= index <= 255:
|
||||
raise SDRValidationError(
|
||||
f"Device index {index} out of range. Valid range: 0-255"
|
||||
)
|
||||
return index
|
||||
|
||||
|
||||
def validate_squelch(squelch: int) -> int:
|
||||
"""
|
||||
Validate squelch level.
|
||||
|
||||
Args:
|
||||
squelch: Squelch level (0-1000, 0 = off)
|
||||
|
||||
Returns:
|
||||
Validated squelch level
|
||||
|
||||
Raises:
|
||||
SDRValidationError: If squelch is out of range
|
||||
"""
|
||||
if not 0 <= squelch <= 1000:
|
||||
raise SDRValidationError(
|
||||
f"Squelch {squelch} out of range. Valid range: 0-1000"
|
||||
)
|
||||
return squelch
|
||||
|
||||
|
||||
def get_capabilities_for_type(sdr_type: SDRType) -> SDRCapabilities:
|
||||
"""
|
||||
Get default capabilities for an SDR type.
|
||||
|
||||
Args:
|
||||
sdr_type: The SDR type
|
||||
|
||||
Returns:
|
||||
SDRCapabilities for the specified type
|
||||
"""
|
||||
from .rtlsdr import RTLSDRCommandBuilder
|
||||
from .limesdr import LimeSDRCommandBuilder
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
|
||||
builders = {
|
||||
SDRType.RTL_SDR: RTLSDRCommandBuilder,
|
||||
SDRType.LIME_SDR: LimeSDRCommandBuilder,
|
||||
SDRType.HACKRF: HackRFCommandBuilder,
|
||||
}
|
||||
|
||||
builder_class = builders.get(sdr_type)
|
||||
if builder_class:
|
||||
return builder_class.CAPABILITIES
|
||||
|
||||
raise SDRValidationError(f"Unknown SDR type: {sdr_type}")
|
||||
Reference in New Issue
Block a user