Files
intercept/utils/sdr/validation.py
James Smith 5ed9674e1f 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>
2026-01-02 14:24:57 +00:00

258 lines
6.6 KiB
Python

"""
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}")