mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -6,14 +6,15 @@ Detects RTL-SDR devices via rtl_test and other SDR hardware via SoapySDR.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from .base import SDRCapabilities, SDRDevice, SDRType
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from utils.dependencies import get_tool_path
|
||||
|
||||
from .base import SDRCapabilities, SDRDevice, SDRType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -43,12 +44,7 @@ def _hackrf_probe_blocked() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
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:
|
||||
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
|
||||
@@ -100,7 +96,7 @@ def _driver_to_sdr_type(driver: str) -> Optional[SDRType]:
|
||||
return mapping.get(driver.lower())
|
||||
|
||||
|
||||
def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect RTL-SDR devices using rtl_test.
|
||||
|
||||
@@ -109,9 +105,10 @@ def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
if not _check_tool('rtl_test'):
|
||||
logger.debug("rtl_test not found, skipping RTL-SDR detection")
|
||||
return devices
|
||||
rtl_test_path = get_tool_path('rtl_test')
|
||||
if not rtl_test_path:
|
||||
logger.debug("rtl_test not found, skipping RTL-SDR detection")
|
||||
return devices
|
||||
|
||||
try:
|
||||
import os
|
||||
@@ -122,11 +119,11 @@ def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
lib_paths = ['/usr/local/lib', '/opt/homebrew/lib']
|
||||
current_ld = env.get('DYLD_LIBRARY_PATH', '')
|
||||
env['DYLD_LIBRARY_PATH'] = ':'.join(lib_paths + [current_ld] if current_ld else lib_paths)
|
||||
result = subprocess.run(
|
||||
['rtl_test', '-t'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
result = subprocess.run(
|
||||
[rtl_test_path, '-t'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace',
|
||||
timeout=5,
|
||||
env=env
|
||||
@@ -176,13 +173,14 @@ def detect_rtlsdr_devices() -> list[SDRDevice]:
|
||||
return devices
|
||||
|
||||
|
||||
def _find_soapy_util() -> str | None:
|
||||
"""Find SoapySDR utility command (name varies by distribution)."""
|
||||
# Try different command names used across distributions
|
||||
for cmd in ['SoapySDRUtil', 'soapy_sdr_util', 'soapysdr-util']:
|
||||
if _check_tool(cmd):
|
||||
return cmd
|
||||
return None
|
||||
def _find_soapy_util() -> str | None:
|
||||
"""Find SoapySDR utility command (name varies by distribution)."""
|
||||
# Try different command names used across distributions
|
||||
for cmd in ['SoapySDRUtil', 'soapy_sdr_util', 'soapysdr-util']:
|
||||
tool_path = get_tool_path(cmd)
|
||||
if tool_path:
|
||||
return tool_path
|
||||
return None
|
||||
|
||||
|
||||
def _get_soapy_env() -> dict:
|
||||
@@ -324,7 +322,7 @@ def _add_soapy_device(
|
||||
))
|
||||
|
||||
|
||||
def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
"""
|
||||
Detect HackRF devices using native hackrf_info tool.
|
||||
|
||||
@@ -343,33 +341,46 @@ def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
|
||||
devices: list[SDRDevice] = []
|
||||
|
||||
if not _check_tool('hackrf_info'):
|
||||
_hackrf_cache = devices
|
||||
_hackrf_cache_ts = now
|
||||
return devices
|
||||
hackrf_info_path = get_tool_path('hackrf_info')
|
||||
if not hackrf_info_path:
|
||||
_hackrf_cache = devices
|
||||
_hackrf_cache_ts = now
|
||||
return devices
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['hackrf_info'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
result = subprocess.run(
|
||||
[hackrf_info_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
# Combine stdout + stderr: newer firmware may print to stderr,
|
||||
# and hackrf_info may exit non-zero when device is briefly busy
|
||||
# but still output valid info.
|
||||
output = result.stdout + result.stderr
|
||||
|
||||
# Parse hackrf_info output
|
||||
# Extract board name from "Board ID Number: X (Name)" and serial
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
|
||||
serial_pattern = r'Serial number:\s*(\S+)'
|
||||
board_pattern = r'Board ID Number:\s*\d+\s*\(([^)]+)\)'
|
||||
|
||||
serials_found = re.findall(serial_pattern, output)
|
||||
boards_found = re.findall(board_pattern, output)
|
||||
output = f"{result.stdout or ''}\n{result.stderr or ''}"
|
||||
|
||||
# Parse hackrf_info output
|
||||
# Extract board name from "Board ID Number: X (Name)" and serial
|
||||
from .hackrf import HackRFCommandBuilder
|
||||
|
||||
serial_pattern = re.compile(
|
||||
r'^\s*Serial\s+number:\s*(.+)$',
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
board_pattern = re.compile(
|
||||
r'Board\s+ID\s+Number:\s*\d+\s*\(([^)]+)\)',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
serials_found = []
|
||||
for raw in serial_pattern.findall(output):
|
||||
# Normalise legacy formats like "0x1234 5678" to plain hex.
|
||||
serial = re.sub(r'0x', '', raw, flags=re.IGNORECASE)
|
||||
serial = re.sub(r'[^0-9A-Fa-f]', '', serial)
|
||||
if serial:
|
||||
serials_found.append(serial)
|
||||
boards_found = board_pattern.findall(output)
|
||||
|
||||
for i, serial in enumerate(serials_found):
|
||||
board_name = boards_found[i] if i < len(boards_found) else 'HackRF'
|
||||
@@ -383,11 +394,11 @@ def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
))
|
||||
|
||||
# Fallback: check if any HackRF found without serial
|
||||
if not devices and 'Found HackRF' in output:
|
||||
board_match = re.search(board_pattern, output)
|
||||
board_name = board_match.group(1) if board_match else 'HackRF'
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.HACKRF,
|
||||
if not devices and re.search(r'Found\s+HackRF', output, re.IGNORECASE):
|
||||
board_match = board_pattern.search(output)
|
||||
board_name = board_match.group(1) if board_match else 'HackRF'
|
||||
devices.append(SDRDevice(
|
||||
sdr_type=SDRType.HACKRF,
|
||||
index=0,
|
||||
name=board_name,
|
||||
serial='Unknown',
|
||||
@@ -403,7 +414,7 @@ def detect_hackrf_devices() -> list[SDRDevice]:
|
||||
return devices
|
||||
|
||||
|
||||
def probe_rtlsdr_device(device_index: int) -> str | None:
|
||||
def probe_rtlsdr_device(device_index: int) -> str | None:
|
||||
"""Probe whether an RTL-SDR device is available at the USB level.
|
||||
|
||||
Runs a quick ``rtl_test`` invocation targeting a single device to
|
||||
@@ -417,10 +428,11 @@ def probe_rtlsdr_device(device_index: int) -> str | None:
|
||||
An error message string if the device cannot be opened,
|
||||
or ``None`` if the device is available.
|
||||
"""
|
||||
if not _check_tool('rtl_test'):
|
||||
# Can't probe without rtl_test — let the caller proceed and
|
||||
# surface errors from the actual decoder process instead.
|
||||
return None
|
||||
rtl_test_path = get_tool_path('rtl_test')
|
||||
if not rtl_test_path:
|
||||
# Can't probe without rtl_test — let the caller proceed and
|
||||
# surface errors from the actual decoder process instead.
|
||||
return None
|
||||
|
||||
try:
|
||||
import os
|
||||
@@ -437,11 +449,11 @@ def probe_rtlsdr_device(device_index: int) -> str | None:
|
||||
# Use Popen with early termination instead of run() with full timeout.
|
||||
# rtl_test prints device info to stderr quickly, then keeps running
|
||||
# its test loop. We kill it as soon as we see success or failure.
|
||||
proc = subprocess.Popen(
|
||||
['rtl_test', '-d', str(device_index), '-t'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
proc = subprocess.Popen(
|
||||
[rtl_test_path, '-d', str(device_index), '-t'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
240
utils/subghz.py
240
utils/subghz.py
@@ -21,11 +21,12 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import BinaryIO, Callable
|
||||
|
||||
import numpy as np
|
||||
|
||||
from utils.logging import get_logger
|
||||
from utils.process import register_process, safe_terminate, unregister_process
|
||||
from utils.constants import (
|
||||
import numpy as np
|
||||
|
||||
from utils.dependencies import get_tool_path
|
||||
from utils.logging import get_logger
|
||||
from utils.process import register_process, safe_terminate, unregister_process
|
||||
from utils.constants import (
|
||||
SUBGHZ_TX_ALLOWED_BANDS,
|
||||
SUBGHZ_FREQ_MIN_MHZ,
|
||||
SUBGHZ_FREQ_MAX_MHZ,
|
||||
@@ -187,19 +188,23 @@ class SubGhzManager:
|
||||
except Exception as e:
|
||||
logger.error(f"Error in SubGHz callback: {e}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Tool detection
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def check_hackrf(self) -> bool:
|
||||
if self._hackrf_available is None:
|
||||
self._hackrf_available = shutil.which('hackrf_transfer') is not None
|
||||
return self._hackrf_available
|
||||
|
||||
def check_hackrf_info(self) -> bool:
|
||||
if self._hackrf_info_available is None:
|
||||
self._hackrf_info_available = shutil.which('hackrf_info') is not None
|
||||
return self._hackrf_info_available
|
||||
# ------------------------------------------------------------------
|
||||
# Tool detection
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _resolve_tool(self, name: str) -> str | None:
|
||||
"""Resolve executable path via PATH first, then platform-aware fallbacks."""
|
||||
return shutil.which(name) or get_tool_path(name)
|
||||
|
||||
def check_hackrf(self) -> bool:
|
||||
if self._hackrf_available is None:
|
||||
self._hackrf_available = self._resolve_tool('hackrf_transfer') is not None
|
||||
return self._hackrf_available
|
||||
|
||||
def check_hackrf_info(self) -> bool:
|
||||
if self._hackrf_info_available is None:
|
||||
self._hackrf_info_available = self._resolve_tool('hackrf_info') is not None
|
||||
return self._hackrf_info_available
|
||||
|
||||
def check_hackrf_device(self) -> bool | None:
|
||||
"""Return True if a HackRF device is detected, False if not, or None if detection unavailable."""
|
||||
@@ -228,15 +233,15 @@ class SubGhzManager:
|
||||
return 'HackRF device not detected'
|
||||
return None
|
||||
|
||||
def check_rtl433(self) -> bool:
|
||||
if self._rtl433_available is None:
|
||||
self._rtl433_available = shutil.which('rtl_433') is not None
|
||||
return self._rtl433_available
|
||||
|
||||
def check_sweep(self) -> bool:
|
||||
if self._sweep_available is None:
|
||||
self._sweep_available = shutil.which('hackrf_sweep') is not None
|
||||
return self._sweep_available
|
||||
def check_rtl433(self) -> bool:
|
||||
if self._rtl433_available is None:
|
||||
self._rtl433_available = self._resolve_tool('rtl_433') is not None
|
||||
return self._rtl433_available
|
||||
|
||||
def check_sweep(self) -> bool:
|
||||
if self._sweep_available is None:
|
||||
self._sweep_available = self._resolve_tool('hackrf_sweep') is not None
|
||||
return self._sweep_available
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Status
|
||||
@@ -307,23 +312,24 @@ class SubGhzManager:
|
||||
# RECEIVE (IQ capture via hackrf_transfer -r)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start_receive(
|
||||
self,
|
||||
frequency_hz: int,
|
||||
sample_rate: int = 2000000,
|
||||
def start_receive(
|
||||
self,
|
||||
frequency_hz: int,
|
||||
sample_rate: int = 2000000,
|
||||
lna_gain: int = 32,
|
||||
vga_gain: int = 20,
|
||||
trigger_enabled: bool = False,
|
||||
trigger_pre_ms: int = 350,
|
||||
trigger_post_ms: int = 700,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
if not self.check_hackrf():
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
trigger_post_ms: int = 700,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
hackrf_transfer_path = self._resolve_tool('hackrf_transfer')
|
||||
if not hackrf_transfer_path:
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
|
||||
with self._lock:
|
||||
if self.active_mode != 'idle':
|
||||
@@ -339,11 +345,11 @@ class SubGhzManager:
|
||||
basename = f"{freq_mhz:.3f}MHz_{ts}"
|
||||
iq_file = self._captures_dir / f"{basename}.iq"
|
||||
|
||||
cmd = [
|
||||
'hackrf_transfer',
|
||||
'-r', str(iq_file),
|
||||
'-f', str(frequency_hz),
|
||||
'-s', str(sample_rate),
|
||||
cmd = [
|
||||
hackrf_transfer_path,
|
||||
'-r', str(iq_file),
|
||||
'-f', str(frequency_hz),
|
||||
'-s', str(sample_rate),
|
||||
'-l', str(lna_gain),
|
||||
'-g', str(vga_gain),
|
||||
]
|
||||
@@ -1272,23 +1278,25 @@ class SubGhzManager:
|
||||
# DECODE (hackrf_transfer piped to rtl_433)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start_decode(
|
||||
self,
|
||||
frequency_hz: int,
|
||||
sample_rate: int = 2_000_000,
|
||||
def start_decode(
|
||||
self,
|
||||
frequency_hz: int,
|
||||
sample_rate: int = 2_000_000,
|
||||
lna_gain: int = 32,
|
||||
vga_gain: int = 20,
|
||||
decode_profile: str = 'weather',
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
if not self.check_hackrf():
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
if not self.check_rtl433():
|
||||
return {'status': 'error', 'message': 'rtl_433 not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
decode_profile: str = 'weather',
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
hackrf_transfer_path = self._resolve_tool('hackrf_transfer')
|
||||
if not hackrf_transfer_path:
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
rtl433_path = self._resolve_tool('rtl_433')
|
||||
if not rtl433_path:
|
||||
return {'status': 'error', 'message': 'rtl_433 not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
|
||||
with self._lock:
|
||||
if self.active_mode != 'idle':
|
||||
@@ -1299,25 +1307,25 @@ class SubGhzManager:
|
||||
requested_sample_rate = int(sample_rate)
|
||||
stable_sample_rate = max(2_000_000, min(2_000_000, requested_sample_rate))
|
||||
|
||||
# Build hackrf_transfer command (producer: raw IQ to stdout)
|
||||
hackrf_cmd = [
|
||||
'hackrf_transfer',
|
||||
'-r', '-',
|
||||
'-f', str(frequency_hz),
|
||||
'-s', str(stable_sample_rate),
|
||||
# Build hackrf_transfer command (producer: raw IQ to stdout)
|
||||
hackrf_cmd = [
|
||||
hackrf_transfer_path,
|
||||
'-r', '-',
|
||||
'-f', str(frequency_hz),
|
||||
'-s', str(stable_sample_rate),
|
||||
'-l', str(max(SUBGHZ_LNA_GAIN_MIN, min(SUBGHZ_LNA_GAIN_MAX, lna_gain))),
|
||||
'-g', str(max(SUBGHZ_VGA_GAIN_MIN, min(SUBGHZ_VGA_GAIN_MAX, vga_gain))),
|
||||
]
|
||||
if device_serial:
|
||||
hackrf_cmd.extend(['-d', device_serial])
|
||||
|
||||
# Build rtl_433 command (consumer: reads IQ from stdin)
|
||||
# Feed signed 8-bit complex IQ directly from hackrf_transfer.
|
||||
rtl433_cmd = [
|
||||
'rtl_433',
|
||||
'-r', 'cs8:-',
|
||||
'-s', str(stable_sample_rate),
|
||||
'-f', str(frequency_hz),
|
||||
# Build rtl_433 command (consumer: reads IQ from stdin)
|
||||
# Feed signed 8-bit complex IQ directly from hackrf_transfer.
|
||||
rtl433_cmd = [
|
||||
rtl433_path,
|
||||
'-r', 'cs8:-',
|
||||
'-s', str(stable_sample_rate),
|
||||
'-f', str(frequency_hz),
|
||||
'-F', 'json',
|
||||
'-F', 'log',
|
||||
'-M', 'level',
|
||||
@@ -1936,21 +1944,22 @@ class SubGhzManager:
|
||||
except OSError as exc:
|
||||
logger.debug(f"Failed to remove TX temp file {path}: {exc}")
|
||||
|
||||
def transmit(
|
||||
self,
|
||||
capture_id: str,
|
||||
tx_gain: int = 20,
|
||||
def transmit(
|
||||
self,
|
||||
capture_id: str,
|
||||
tx_gain: int = 20,
|
||||
max_duration: int = 10,
|
||||
start_seconds: float | None = None,
|
||||
duration_seconds: float | None = None,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
if not self.check_hackrf():
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
duration_seconds: float | None = None,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
hackrf_transfer_path = self._resolve_tool('hackrf_transfer')
|
||||
if not hackrf_transfer_path:
|
||||
return {'status': 'error', 'message': 'hackrf_transfer not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
|
||||
# Pre-lock: capture lookup, validation, and segment I/O (can be large)
|
||||
capture = self._load_capture(capture_id)
|
||||
@@ -2046,14 +2055,14 @@ class SubGhzManager:
|
||||
|
||||
# Clear any orphaned temp segment from a previous TX attempt.
|
||||
self._cleanup_tx_temp_file()
|
||||
if segment_path_for_cleanup:
|
||||
self._tx_temp_file = segment_path_for_cleanup
|
||||
|
||||
cmd = [
|
||||
'hackrf_transfer',
|
||||
'-t', str(tx_path),
|
||||
'-f', str(capture.frequency_hz),
|
||||
'-s', str(capture.sample_rate),
|
||||
if segment_path_for_cleanup:
|
||||
self._tx_temp_file = segment_path_for_cleanup
|
||||
|
||||
cmd = [
|
||||
hackrf_transfer_path,
|
||||
'-t', str(tx_path),
|
||||
'-f', str(capture.frequency_hz),
|
||||
'-s', str(capture.sample_rate),
|
||||
'-x', str(tx_gain),
|
||||
]
|
||||
if device_serial:
|
||||
@@ -2183,19 +2192,20 @@ class SubGhzManager:
|
||||
# SWEEP (hackrf_sweep)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start_sweep(
|
||||
self,
|
||||
freq_start_mhz: float = 300.0,
|
||||
freq_end_mhz: float = 928.0,
|
||||
bin_width: int = 100000,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
if not self.check_sweep():
|
||||
return {'status': 'error', 'message': 'hackrf_sweep not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
def start_sweep(
|
||||
self,
|
||||
freq_start_mhz: float = 300.0,
|
||||
freq_end_mhz: float = 928.0,
|
||||
bin_width: int = 100000,
|
||||
device_serial: str | None = None,
|
||||
) -> dict:
|
||||
# Pre-lock: tool availability & device detection (blocking I/O)
|
||||
hackrf_sweep_path = self._resolve_tool('hackrf_sweep')
|
||||
if not hackrf_sweep_path:
|
||||
return {'status': 'error', 'message': 'hackrf_sweep not found'}
|
||||
device_err = self._require_hackrf_device()
|
||||
if device_err:
|
||||
return {'status': 'error', 'message': device_err}
|
||||
|
||||
# Wait for previous sweep thread to exit (blocking) before lock
|
||||
if self._sweep_thread and self._sweep_thread.is_alive():
|
||||
@@ -2204,14 +2214,14 @@ class SubGhzManager:
|
||||
return {'status': 'error', 'message': 'Previous sweep still shutting down'}
|
||||
|
||||
with self._lock:
|
||||
if self.active_mode != 'idle':
|
||||
return {'status': 'error', 'message': f'Already running: {self.active_mode}'}
|
||||
|
||||
cmd = [
|
||||
'hackrf_sweep',
|
||||
'-f', f'{int(freq_start_mhz)}:{int(freq_end_mhz)}',
|
||||
'-w', str(bin_width),
|
||||
]
|
||||
if self.active_mode != 'idle':
|
||||
return {'status': 'error', 'message': f'Already running: {self.active_mode}'}
|
||||
|
||||
cmd = [
|
||||
hackrf_sweep_path,
|
||||
'-f', f'{int(freq_start_mhz)}:{int(freq_end_mhz)}',
|
||||
'-w', str(bin_width),
|
||||
]
|
||||
if device_serial:
|
||||
cmd.extend(['-d', device_serial])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user