mirror of
https://github.com/smittix/intercept.git
synced 2026-07-11 02:58:12 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1faa390ea7 |
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
All notable changes to iNTERCEPT will be documented in this file.
|
All notable changes to iNTERCEPT will be documented in this file.
|
||||||
|
|
||||||
|
## [2.31.0] - 2026-07-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **BladeRF support** — bladeRF 2.0 micro (47 MHz – 6 GHz) and bladeRF x40/x115 (300 MHz – 3.8 GHz) detected and usable via SoapyBladeRF. TX capable. Supports FM demod, ADS-B, ISM, AIS, and I/Q capture.
|
||||||
|
- **HydraSDR RFOne support** — Detected and usable via SoapyHydraSDR (24 MHz – 1800 MHz, RX only, 10 MHz instantaneous bandwidth). Supports FM demod, ADS-B, ISM, AIS, and I/Q capture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.30.0] - 2026-07-07
|
## [2.30.0] - 2026-07-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -7,10 +7,18 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Application version
|
# Application version
|
||||||
VERSION = "2.30.0"
|
VERSION = "2.31.0"
|
||||||
|
|
||||||
# Changelog - latest release notes (shown on welcome screen)
|
# Changelog - latest release notes (shown on welcome screen)
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
{
|
||||||
|
"version": "2.31.0",
|
||||||
|
"date": "July 2026",
|
||||||
|
"highlights": [
|
||||||
|
"Feat: BladeRF support — bladeRF 2.0 micro and x40/x115 detected and usable via SoapyBladeRF (47 MHz – 6 GHz, TX capable)",
|
||||||
|
"Feat: HydraSDR RFOne support — detected and usable via SoapyHydraSDR (24 MHz – 1800 MHz, RX only)",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2.30.0",
|
"version": "2.30.0",
|
||||||
"date": "July 2026",
|
"date": "July 2026",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "intercept"
|
name = "intercept"
|
||||||
version = "2.30.0"
|
version = "2.31.0"
|
||||||
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
|
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ from typing import Optional
|
|||||||
|
|
||||||
from .airspy import AirspyCommandBuilder
|
from .airspy import AirspyCommandBuilder
|
||||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
from .bladerf import BladeRFCommandBuilder
|
||||||
from .detection import detect_all_devices, invalidate_device_cache, probe_rtlsdr_device
|
from .detection import detect_all_devices, invalidate_device_cache, probe_rtlsdr_device
|
||||||
from .hackrf import HackRFCommandBuilder
|
from .hackrf import HackRFCommandBuilder
|
||||||
|
from .hydrasdr import HydraSDRCommandBuilder
|
||||||
from .limesdr import LimeSDRCommandBuilder
|
from .limesdr import LimeSDRCommandBuilder
|
||||||
from .rtlsdr import RTLSDRCommandBuilder
|
from .rtlsdr import RTLSDRCommandBuilder
|
||||||
from .sdrplay import SDRPlayCommandBuilder
|
from .sdrplay import SDRPlayCommandBuilder
|
||||||
@@ -55,6 +57,8 @@ class SDRFactory:
|
|||||||
SDRType.AIRSPY: AirspyCommandBuilder,
|
SDRType.AIRSPY: AirspyCommandBuilder,
|
||||||
SDRType.SDRPLAY: SDRPlayCommandBuilder,
|
SDRType.SDRPLAY: SDRPlayCommandBuilder,
|
||||||
SDRType.USRP: USRPCommandBuilder,
|
SDRType.USRP: USRPCommandBuilder,
|
||||||
|
SDRType.BLADE_RF: BladeRFCommandBuilder,
|
||||||
|
SDRType.HYDRA_SDR: HydraSDRCommandBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -214,6 +218,8 @@ __all__ = [
|
|||||||
"AirspyCommandBuilder",
|
"AirspyCommandBuilder",
|
||||||
"SDRPlayCommandBuilder",
|
"SDRPlayCommandBuilder",
|
||||||
"USRPCommandBuilder",
|
"USRPCommandBuilder",
|
||||||
|
"BladeRFCommandBuilder",
|
||||||
|
"HydraSDRCommandBuilder",
|
||||||
# Validation
|
# Validation
|
||||||
"SDRValidationError",
|
"SDRValidationError",
|
||||||
"validate_frequency",
|
"validate_frequency",
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ class SDRType(Enum):
|
|||||||
AIRSPY = "airspy"
|
AIRSPY = "airspy"
|
||||||
SDRPLAY = "sdrplay"
|
SDRPLAY = "sdrplay"
|
||||||
USRP = "usrp"
|
USRP = "usrp"
|
||||||
# Future support
|
BLADE_RF = "bladerf"
|
||||||
# BLADE_RF = "bladerf"
|
HYDRA_SDR = "hydrasdr"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
"""
|
||||||
|
BladeRF command builder implementation.
|
||||||
|
|
||||||
|
Uses SoapySDR (via SoapyBladeRF) for all signal processing tasks.
|
||||||
|
Targets bladeRF 2.0 micro (47 MHz – 6 GHz) but is compatible with
|
||||||
|
bladeRF x40/x115 (300 MHz – 3.8 GHz) — the SoapySDR layer handles
|
||||||
|
per-device frequency clamping transparently.
|
||||||
|
|
||||||
|
Requires: libbladeRF, SoapySDR, SoapyBladeRF module installed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
|
|
||||||
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
|
||||||
|
|
||||||
|
class BladeRFCommandBuilder(CommandBuilder):
|
||||||
|
"""BladeRF command builder using SoapySDR / SoapyBladeRF."""
|
||||||
|
|
||||||
|
CAPABILITIES = SDRCapabilities(
|
||||||
|
sdr_type=SDRType.BLADE_RF,
|
||||||
|
freq_min_mhz=47.0, # bladeRF 2.0 micro lower bound
|
||||||
|
freq_max_mhz=6000.0,
|
||||||
|
gain_min=0.0,
|
||||||
|
gain_max=66.0, # LNA (0/3/6) + RXVGA1 (5-30) + RXVGA2 (0-30)
|
||||||
|
sample_rates=[1000000, 2000000, 4000000, 8000000, 10000000, 20000000, 40000000],
|
||||||
|
supports_bias_t=False,
|
||||||
|
supports_ppm=False,
|
||||||
|
tx_capable=True,
|
||||||
|
supports_iq_capture=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_device_string(self, device: SDRDevice) -> str:
|
||||||
|
"""Build SoapySDR device string for BladeRF."""
|
||||||
|
if device.serial and device.serial not in ("N/A", "Unknown"):
|
||||||
|
return f"driver=bladerf,serial={device.serial}"
|
||||||
|
return "driver=bladerf"
|
||||||
|
|
||||||
|
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]:
|
||||||
|
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.BLADE_RF
|
||||||
@@ -49,7 +49,9 @@ def _get_capabilities_for_type(sdr_type: SDRType) -> SDRCapabilities:
|
|||||||
"""Get default capabilities for an SDR type."""
|
"""Get default capabilities for an SDR type."""
|
||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from .airspy import AirspyCommandBuilder
|
from .airspy import AirspyCommandBuilder
|
||||||
|
from .bladerf import BladeRFCommandBuilder
|
||||||
from .hackrf import HackRFCommandBuilder
|
from .hackrf import HackRFCommandBuilder
|
||||||
|
from .hydrasdr import HydraSDRCommandBuilder
|
||||||
from .limesdr import LimeSDRCommandBuilder
|
from .limesdr import LimeSDRCommandBuilder
|
||||||
from .rtlsdr import RTLSDRCommandBuilder
|
from .rtlsdr import RTLSDRCommandBuilder
|
||||||
from .sdrplay import SDRPlayCommandBuilder
|
from .sdrplay import SDRPlayCommandBuilder
|
||||||
@@ -62,6 +64,8 @@ def _get_capabilities_for_type(sdr_type: SDRType) -> SDRCapabilities:
|
|||||||
SDRType.AIRSPY: AirspyCommandBuilder,
|
SDRType.AIRSPY: AirspyCommandBuilder,
|
||||||
SDRType.SDRPLAY: SDRPlayCommandBuilder,
|
SDRType.SDRPLAY: SDRPlayCommandBuilder,
|
||||||
SDRType.USRP: USRPCommandBuilder,
|
SDRType.USRP: USRPCommandBuilder,
|
||||||
|
SDRType.BLADE_RF: BladeRFCommandBuilder,
|
||||||
|
SDRType.HYDRA_SDR: HydraSDRCommandBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
builder_class = builders.get(sdr_type)
|
builder_class = builders.get(sdr_type)
|
||||||
@@ -93,8 +97,8 @@ def _driver_to_sdr_type(driver: str) -> SDRType | None:
|
|||||||
"airspyhf": SDRType.AIRSPY, # Airspy HF+ uses same builder
|
"airspyhf": SDRType.AIRSPY, # Airspy HF+ uses same builder
|
||||||
"sdrplay": SDRType.SDRPLAY,
|
"sdrplay": SDRType.SDRPLAY,
|
||||||
"uhd": SDRType.USRP,
|
"uhd": SDRType.USRP,
|
||||||
# Future support
|
"bladerf": SDRType.BLADE_RF,
|
||||||
# 'bladerf': SDRType.BLADE_RF,
|
"hydrasdr": SDRType.HYDRA_SDR,
|
||||||
}
|
}
|
||||||
return mapping.get(driver.lower())
|
return mapping.get(driver.lower())
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
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 0–21 (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 (24–1800 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
|
||||||
Reference in New Issue
Block a user