Files
intercept/utils/sdr/hydrasdr.py
T
James Smith 1faa390ea7 feat: BladeRF and HydraSDR RFOne support (closes #121, closes #203)
- BladeRF 2.0 micro / x40 / x115 detected via SoapyBladeRF (47 MHz–6 GHz,
  TX capable, gain 0–66 dB)
- HydraSDR RFOne detected via SoapyHydraSDR (24 MHz–1800 MHz, RX only,
  linear gain 0–21)
- Both follow the established SoapySDR command-builder pattern and support
  FM demod, ADS-B, ISM, AIS, and I/Q capture
- Bump version to 2.31.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 12:08:03 +01:00

158 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
HydraSDR RFOne command builder implementation.
Uses SoapySDR (via SoapyHydraSDR plugin) for all signal processing tasks.
The RFOne is an RX-only receiver covering 24 MHz to 1800 MHz with a
10 MHz instantaneous bandwidth.
Gain is a linear scale 021 (not dB); the SoapyHydraSDR plugin maps this
to the hardware's RF gain stages.
Requires: rfone_host library, SoapySDR, SoapyHydraSDR module installed.
Official: https://github.com/hydrasdr/SoapyHydraSDR
"""
from __future__ import annotations
from utils.dependencies import get_tool_path
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
class HydraSDRCommandBuilder(CommandBuilder):
"""HydraSDR RFOne command builder using SoapySDR / SoapyHydraSDR."""
CAPABILITIES = SDRCapabilities(
sdr_type=SDRType.HYDRA_SDR,
freq_min_mhz=24.0,
freq_max_mhz=1800.0,
gain_min=0.0,
gain_max=21.0, # linear scale; recommended starting point: 12
sample_rates=[2500000, 5000000, 10000000],
supports_bias_t=False,
supports_ppm=False,
tx_capable=False,
supports_iq_capture=True,
)
def _build_device_string(self, device: SDRDevice) -> str:
"""Build SoapySDR device string for HydraSDR RFOne."""
if device.serial and device.serial not in ("N/A", "Unknown"):
return f"driver=hydrasdr,serial={device.serial}"
return "driver=hydrasdr"
def build_fm_demod_command(
self,
device: SDRDevice,
frequency_mhz: float,
sample_rate: int = 22050,
gain: float | None = None,
ppm: int | None = None,
modulation: str = "fm",
squelch: int | None = None,
bias_t: bool = False,
) -> list[str]:
device_str = self._build_device_string(device)
rx_fm_path = get_tool_path("rx_fm") or "rx_fm"
cmd = [
rx_fm_path,
"-d",
device_str,
"-f",
f"{frequency_mhz}M",
"-M",
modulation,
"-s",
str(sample_rate),
]
if gain is not None and gain > 0:
cmd.extend(["-g", str(int(gain))])
if squelch is not None and squelch > 0:
cmd.extend(["-l", str(squelch)])
cmd.append("-")
return cmd
def build_adsb_command(self, device: SDRDevice, gain: float | None = None, bias_t: bool = False) -> list[str]:
# 1090 MHz is within the RFOne's range (241800 MHz)
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: float | None = None,
ppm: int | None = None,
bias_t: bool = False,
) -> list[str]:
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 build_ais_command(
self,
device: SDRDevice,
gain: float | None = None,
bias_t: bool = False,
tcp_port: int = 10110,
udp_host: str | None = None,
udp_port: int | None = None,
) -> list[str]:
device_str = self._build_device_string(device)
cmd = [
"AIS-catcher",
"-d",
f"soapysdr -d {device_str}",
"-S",
str(tcp_port),
"-o",
"5",
"-q",
]
if gain is not None and gain > 0:
cmd.extend(["-gr", "tuner", str(int(gain))])
if udp_host and udp_port:
cmd.extend(["-u", udp_host, str(udp_port)])
return cmd
def build_iq_capture_command(
self,
device: SDRDevice,
frequency_mhz: float,
sample_rate: int = 2048000,
gain: float | None = None,
ppm: int | None = None,
bias_t: bool = False,
output_format: str = "cu8",
) -> list[str]:
device_str = self._build_device_string(device)
freq_hz = int(frequency_mhz * 1e6)
rx_sdr_path = get_tool_path("rx_sdr") or "rx_sdr"
cmd = [
rx_sdr_path,
"-d",
device_str,
"-f",
str(freq_hz),
"-s",
str(sample_rate),
"-F",
"CU8",
]
if gain is not None and gain > 0:
cmd.extend(["-g", str(int(gain))])
cmd.append("-")
return cmd
def get_capabilities(self) -> SDRCapabilities:
return self.CAPABILITIES
@classmethod
def get_sdr_type(cls) -> SDRType:
return SDRType.HYDRA_SDR