mirror of
https://github.com/smittix/intercept.git
synced 2026-07-17 05:48:11 -07:00
Fix WeFax error detection and surface errors in strip UI
rtl_fm subprocess failures (missing tool, no SDR hardware) were silent — add tool-path check and post-spawn health check in _start_pipeline(), show errors prominently in the strip status bar (red text + red dot), and include error detail in scheduler skip events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -192,11 +192,15 @@ var WeFax = (function () {
|
|||||||
setStripFreq(freqKhz);
|
setStripFreq(freqKhz);
|
||||||
connectSSE();
|
connectSSE();
|
||||||
} else {
|
} else {
|
||||||
setStatus('Error: ' + (data.message || 'unknown'));
|
var errMsg = data.message || 'unknown error';
|
||||||
|
setStatus('Error: ' + errMsg);
|
||||||
|
showStripError(errMsg);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
setStatus('Error: ' + err.message);
|
var errMsg = err.message || 'Network error';
|
||||||
|
setStatus('Error: ' + errMsg);
|
||||||
|
showStripError(errMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +327,7 @@ var WeFax = (function () {
|
|||||||
if (data.status === 'error') {
|
if (data.status === 'error') {
|
||||||
state.running = false;
|
state.running = false;
|
||||||
updateButtons(false);
|
updateButtons(false);
|
||||||
|
showStripError(data.message || 'Decode error');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.status === 'stopped') {
|
if (data.status === 'stopped') {
|
||||||
@@ -680,6 +685,16 @@ var WeFax = (function () {
|
|||||||
if (el) el.textContent = String(khz);
|
if (el) el.textContent = String(khz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showStripError(msg) {
|
||||||
|
var statusEl = document.getElementById('wefaxStripStatus');
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.textContent = 'Error: ' + msg;
|
||||||
|
statusEl.style.color = '#ff4444';
|
||||||
|
}
|
||||||
|
var dot = document.getElementById('wefaxStripDot');
|
||||||
|
if (dot) dot.className = 'wefax-strip-dot error';
|
||||||
|
}
|
||||||
|
|
||||||
function flashStartError() {
|
function flashStartError() {
|
||||||
setStatus('Select a station and frequency first');
|
setStatus('Select a station and frequency first');
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -26,6 +26,7 @@ from typing import Callable
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from utils.dependencies import get_tool_path
|
||||||
from utils.logging import get_logger
|
from utils.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger('intercept.wefax')
|
logger = get_logger('intercept.wefax')
|
||||||
@@ -235,11 +236,17 @@ class WeFaxDecoder:
|
|||||||
self._direct_sampling = True
|
self._direct_sampling = True
|
||||||
|
|
||||||
self._output_dir.mkdir(parents=True, exist_ok=True)
|
self._output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._last_error: str = ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
return self._running
|
return self._running
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_error(self) -> str:
|
||||||
|
"""Last error message from a failed start() attempt."""
|
||||||
|
return self._last_error
|
||||||
|
|
||||||
def set_callback(self, callback: Callable[[dict], None]) -> None:
|
def set_callback(self, callback: Callable[[dict], None]) -> None:
|
||||||
"""Set callback for progress updates (fed to SSE queue)."""
|
"""Set callback for progress updates (fed to SSE queue)."""
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
@@ -283,6 +290,7 @@ class WeFaxDecoder:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._running = True
|
self._running = True
|
||||||
|
self._last_error = ''
|
||||||
self._start_pipeline()
|
self._start_pipeline()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -298,6 +306,7 @@ class WeFaxDecoder:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._running = False
|
self._running = False
|
||||||
|
self._last_error = str(e)
|
||||||
logger.error(f"Failed to start WeFax decoder: {e}")
|
logger.error(f"Failed to start WeFax decoder: {e}")
|
||||||
self._emit_progress(WeFaxProgress(
|
self._emit_progress(WeFaxProgress(
|
||||||
status='error',
|
status='error',
|
||||||
@@ -307,10 +316,14 @@ class WeFaxDecoder:
|
|||||||
|
|
||||||
def _start_pipeline(self) -> None:
|
def _start_pipeline(self) -> None:
|
||||||
"""Start rtl_fm subprocess in USB mode for WeFax."""
|
"""Start rtl_fm subprocess in USB mode for WeFax."""
|
||||||
|
rtl_fm_path = get_tool_path('rtl_fm')
|
||||||
|
if not rtl_fm_path:
|
||||||
|
raise RuntimeError('rtl_fm not found')
|
||||||
|
|
||||||
freq_hz = int(self._frequency_khz * 1000)
|
freq_hz = int(self._frequency_khz * 1000)
|
||||||
|
|
||||||
rtl_cmd = [
|
rtl_cmd = [
|
||||||
'rtl_fm',
|
rtl_fm_path,
|
||||||
'-d', str(self._device_index),
|
'-d', str(self._device_index),
|
||||||
'-f', str(freq_hz),
|
'-f', str(freq_hz),
|
||||||
'-M', 'usb',
|
'-M', 'usb',
|
||||||
@@ -332,6 +345,18 @@ class WeFaxDecoder:
|
|||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Post-spawn health check — catch immediate failures
|
||||||
|
time.sleep(0.3)
|
||||||
|
if self._rtl_process.poll() is not None:
|
||||||
|
stderr_detail = ''
|
||||||
|
if self._rtl_process.stderr:
|
||||||
|
stderr_detail = self._rtl_process.stderr.read().decode(
|
||||||
|
errors='replace').strip()
|
||||||
|
rc = self._rtl_process.returncode
|
||||||
|
self._rtl_process = None
|
||||||
|
detail = stderr_detail.split('\n')[-1] if stderr_detail else f'exit code {rc}'
|
||||||
|
raise RuntimeError(f'rtl_fm failed: {detail}')
|
||||||
|
|
||||||
self._decode_thread = threading.Thread(
|
self._decode_thread = threading.Thread(
|
||||||
target=self._decode_audio_stream, daemon=True)
|
target=self._decode_audio_stream, daemon=True)
|
||||||
self._decode_thread.start()
|
self._decode_thread.start()
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ class WeFaxScheduler:
|
|||||||
'type': 'schedule_capture_skipped',
|
'type': 'schedule_capture_skipped',
|
||||||
'broadcast': sb.to_dict(),
|
'broadcast': sb.to_dict(),
|
||||||
'reason': 'start_failed',
|
'reason': 'start_failed',
|
||||||
|
'detail': decoder.last_error or 'unknown error',
|
||||||
})
|
})
|
||||||
|
|
||||||
def _stop_capture(
|
def _stop_capture(
|
||||||
|
|||||||
Reference in New Issue
Block a user