mirror of
https://github.com/smittix/intercept.git
synced 2026-07-18 06:18:11 -07:00
Improve waterfall error handling and SDR tool path resolution
- Add pre-flight check for I/Q capture binary before spawning process
- Capture stderr from I/Q process for better error diagnostics
- Sync effective span value back to UI when backend adjusts it
- Use get_tool_path('rx_sdr') in Airspy, HackRF, LimeSDR, and SDRPlay
command builders to support custom install locations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import queue
|
import queue
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
@@ -546,6 +547,16 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
}))
|
}))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Pre-flight: check the capture binary exists
|
||||||
|
if not shutil.which(iq_cmd[0]):
|
||||||
|
app_module.release_sdr_device(device_index)
|
||||||
|
claimed_device = None
|
||||||
|
ws.send(json.dumps({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'Required tool "{iq_cmd[0]}" not found. Install SoapySDR tools (rx_sdr).',
|
||||||
|
}))
|
||||||
|
continue
|
||||||
|
|
||||||
# Spawn I/Q capture process (retry to handle USB release lag)
|
# Spawn I/Q capture process (retry to handle USB release lag)
|
||||||
max_attempts = 3 if was_restarting else 1
|
max_attempts = 3 if was_restarting else 1
|
||||||
try:
|
try:
|
||||||
@@ -558,7 +569,7 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
iq_process = subprocess.Popen(
|
iq_process = subprocess.Popen(
|
||||||
iq_cmd,
|
iq_cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.PIPE,
|
||||||
bufsize=0,
|
bufsize=0,
|
||||||
)
|
)
|
||||||
register_process(iq_process)
|
register_process(iq_process)
|
||||||
@@ -566,17 +577,23 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
# Brief check that process started
|
# Brief check that process started
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
if iq_process.poll() is not None:
|
if iq_process.poll() is not None:
|
||||||
|
stderr_out = ''
|
||||||
|
if iq_process.stderr:
|
||||||
|
with suppress(Exception):
|
||||||
|
stderr_out = iq_process.stderr.read().decode('utf-8', errors='replace').strip()
|
||||||
unregister_process(iq_process)
|
unregister_process(iq_process)
|
||||||
iq_process = None
|
iq_process = None
|
||||||
if attempt < max_attempts - 1:
|
if attempt < max_attempts - 1:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"I/Q process exited immediately, "
|
f"I/Q process exited immediately, "
|
||||||
f"retrying ({attempt + 1}/{max_attempts})..."
|
f"retrying ({attempt + 1}/{max_attempts})..."
|
||||||
|
+ (f" stderr: {stderr_out}" if stderr_out else "")
|
||||||
)
|
)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
continue
|
continue
|
||||||
|
detail = f": {stderr_out}" if stderr_out else ""
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"I/Q capture process exited immediately"
|
f"I/Q capture process exited immediately{detail}"
|
||||||
)
|
)
|
||||||
break # Process started successfully
|
break # Process started successfully
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -2515,6 +2515,10 @@ const Waterfall = (function () {
|
|||||||
_endMhz = msg.end_freq;
|
_endMhz = msg.end_freq;
|
||||||
_drawFreqAxis();
|
_drawFreqAxis();
|
||||||
}
|
}
|
||||||
|
if (Number.isFinite(msg.effective_span_mhz)) {
|
||||||
|
const spanEl = document.getElementById('wfSpanMhz');
|
||||||
|
if (spanEl) spanEl.value = msg.effective_span_mhz;
|
||||||
|
}
|
||||||
_setStatus(`Streaming ${_startMhz.toFixed(4)} - ${_endMhz.toFixed(4)} MHz`);
|
_setStatus(`Streaming ${_startMhz.toFixed(4)} - ${_endMhz.toFixed(4)} MHz`);
|
||||||
_setVisualStatus('RUNNING');
|
_setVisualStatus('RUNNING');
|
||||||
if (_monitoring) {
|
if (_monitoring) {
|
||||||
|
|||||||
+4
-1
@@ -10,6 +10,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
|
|
||||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
|
||||||
|
|
||||||
@@ -203,8 +205,9 @@ class AirspyCommandBuilder(CommandBuilder):
|
|||||||
device_str = self._build_device_string(device)
|
device_str = self._build_device_string(device)
|
||||||
freq_hz = int(frequency_mhz * 1e6)
|
freq_hz = int(frequency_mhz * 1e6)
|
||||||
|
|
||||||
|
rx_sdr_path = get_tool_path('rx_sdr') or 'rx_sdr'
|
||||||
cmd = [
|
cmd = [
|
||||||
'rx_sdr',
|
rx_sdr_path,
|
||||||
'-d', device_str,
|
'-d', device_str,
|
||||||
'-f', str(freq_hz),
|
'-f', str(freq_hz),
|
||||||
'-s', str(sample_rate),
|
'-s', str(sample_rate),
|
||||||
|
|||||||
+4
-1
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
|
|
||||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
|
||||||
|
|
||||||
@@ -203,8 +205,9 @@ class HackRFCommandBuilder(CommandBuilder):
|
|||||||
device_str = self._build_device_string(device)
|
device_str = self._build_device_string(device)
|
||||||
freq_hz = int(frequency_mhz * 1e6)
|
freq_hz = int(frequency_mhz * 1e6)
|
||||||
|
|
||||||
|
rx_sdr_path = get_tool_path('rx_sdr') or 'rx_sdr'
|
||||||
cmd = [
|
cmd = [
|
||||||
'rx_sdr',
|
rx_sdr_path,
|
||||||
'-d', device_str,
|
'-d', device_str,
|
||||||
'-f', str(freq_hz),
|
'-f', str(freq_hz),
|
||||||
'-s', str(sample_rate),
|
'-s', str(sample_rate),
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
|
|
||||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
|
||||||
|
|
||||||
@@ -181,8 +183,9 @@ class LimeSDRCommandBuilder(CommandBuilder):
|
|||||||
device_str = self._build_device_string(device)
|
device_str = self._build_device_string(device)
|
||||||
freq_hz = int(frequency_mhz * 1e6)
|
freq_hz = int(frequency_mhz * 1e6)
|
||||||
|
|
||||||
|
rx_sdr_path = get_tool_path('rx_sdr') or 'rx_sdr'
|
||||||
cmd = [
|
cmd = [
|
||||||
'rx_sdr',
|
rx_sdr_path,
|
||||||
'-d', device_str,
|
'-d', device_str,
|
||||||
'-f', str(freq_hz),
|
'-f', str(freq_hz),
|
||||||
'-s', str(sample_rate),
|
'-s', str(sample_rate),
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
|
|
||||||
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType
|
||||||
|
|
||||||
|
|
||||||
@@ -181,8 +183,9 @@ class SDRPlayCommandBuilder(CommandBuilder):
|
|||||||
device_str = self._build_device_string(device)
|
device_str = self._build_device_string(device)
|
||||||
freq_hz = int(frequency_mhz * 1e6)
|
freq_hz = int(frequency_mhz * 1e6)
|
||||||
|
|
||||||
|
rx_sdr_path = get_tool_path('rx_sdr') or 'rx_sdr'
|
||||||
cmd = [
|
cmd = [
|
||||||
'rx_sdr',
|
rx_sdr_path,
|
||||||
'-d', device_str,
|
'-d', device_str,
|
||||||
'-f', str(freq_hz),
|
'-f', str(freq_hz),
|
||||||
'-s', str(sample_rate),
|
'-s', str(sample_rate),
|
||||||
|
|||||||
Reference in New Issue
Block a user