mirror of
https://github.com/smittix/intercept.git
synced 2026-07-26 09:48:09 -07:00
Forward rtl_fm stderr to Morse frontend diagnostic log
rtl_fm prints device info, tuning, and errors to stderr but the morse route only logged these server-side. Now stderr lines are forwarded to the morse queue as info events, displayed in a compact diagnostic log below the scope canvas. After 10s with no audio data, the scope text escalates to prompt the user to check the SDR log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -158,12 +158,17 @@ def start_morse() -> Response:
|
|||||||
morse_active_device = None
|
morse_active_device = None
|
||||||
return jsonify({'status': 'error', 'message': msg}), 500
|
return jsonify({'status': 'error', 'message': msg}), 500
|
||||||
|
|
||||||
# Monitor rtl_fm stderr
|
# Forward rtl_fm stderr to queue so frontend can display diagnostics
|
||||||
def monitor_stderr():
|
def monitor_stderr():
|
||||||
for line in rtl_process.stderr:
|
for line in rtl_process.stderr:
|
||||||
err_text = line.decode('utf-8', errors='replace').strip()
|
err_text = line.decode('utf-8', errors='replace').strip()
|
||||||
if err_text:
|
if err_text:
|
||||||
logger.debug(f"[rtl_fm/morse] {err_text}")
|
logger.debug(f"[rtl_fm/morse] {err_text}")
|
||||||
|
with contextlib.suppress(queue.Full):
|
||||||
|
app_module.morse_queue.put_nowait({
|
||||||
|
'type': 'info',
|
||||||
|
'text': f'[rtl_fm] {err_text}',
|
||||||
|
})
|
||||||
|
|
||||||
stderr_thread = threading.Thread(target=monitor_stderr)
|
stderr_thread = threading.Thread(target=monitor_stderr)
|
||||||
stderr_thread.daemon = True
|
stderr_thread.daemon = True
|
||||||
@@ -190,6 +195,11 @@ def start_morse() -> Response:
|
|||||||
app_module.morse_process._decoder_thread = decoder_thread
|
app_module.morse_process._decoder_thread = decoder_thread
|
||||||
|
|
||||||
app_module.morse_queue.put({'type': 'status', 'status': 'started'})
|
app_module.morse_queue.put({'type': 'status', 'status': 'started'})
|
||||||
|
with contextlib.suppress(queue.Full):
|
||||||
|
app_module.morse_queue.put_nowait({
|
||||||
|
'type': 'info',
|
||||||
|
'text': f'[cmd] {full_cmd}',
|
||||||
|
})
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'started',
|
'status': 'started',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ var MorseMode = (function () {
|
|||||||
var scopeThreshold = 0;
|
var scopeThreshold = 0;
|
||||||
var scopeToneOn = false;
|
var scopeToneOn = false;
|
||||||
var scopeWaiting = false;
|
var scopeWaiting = false;
|
||||||
|
var waitingStart = 0; // timestamp when waiting began
|
||||||
|
|
||||||
// ---- Initialization ----
|
// ---- Initialization ----
|
||||||
|
|
||||||
@@ -152,9 +153,13 @@ var MorseMode = (function () {
|
|||||||
// Update scope data
|
// Update scope data
|
||||||
var amps = msg.amplitudes || [];
|
var amps = msg.amplitudes || [];
|
||||||
if (msg.waiting && amps.length === 0 && scopeHistory.length === 0) {
|
if (msg.waiting && amps.length === 0 && scopeHistory.length === 0) {
|
||||||
scopeWaiting = true;
|
if (!scopeWaiting) {
|
||||||
|
scopeWaiting = true;
|
||||||
|
waitingStart = Date.now();
|
||||||
|
}
|
||||||
} else if (amps.length > 0) {
|
} else if (amps.length > 0) {
|
||||||
scopeWaiting = false;
|
scopeWaiting = false;
|
||||||
|
waitingStart = 0;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < amps.length; i++) {
|
for (var i = 0; i < amps.length; i++) {
|
||||||
scopeHistory.push(amps[i]);
|
scopeHistory.push(amps[i]);
|
||||||
@@ -178,6 +183,9 @@ var MorseMode = (function () {
|
|||||||
disconnectSSE();
|
disconnectSSE();
|
||||||
stopScope();
|
stopScope();
|
||||||
}
|
}
|
||||||
|
} else if (type === 'info') {
|
||||||
|
appendDiagLine(msg.text);
|
||||||
|
|
||||||
} else if (type === 'error') {
|
} else if (type === 'error') {
|
||||||
console.error('Morse error:', msg.text);
|
console.error('Morse error:', msg.text);
|
||||||
}
|
}
|
||||||
@@ -263,10 +271,14 @@ var MorseMode = (function () {
|
|||||||
|
|
||||||
if (scopeHistory.length === 0) {
|
if (scopeHistory.length === 0) {
|
||||||
if (scopeWaiting) {
|
if (scopeWaiting) {
|
||||||
scopeCtx.fillStyle = '#556677';
|
var elapsed = waitingStart ? (Date.now() - waitingStart) / 1000 : 0;
|
||||||
|
var waitText = elapsed > 10
|
||||||
|
? 'No audio data \u2014 check SDR log below'
|
||||||
|
: 'Awaiting SDR data\u2026';
|
||||||
|
scopeCtx.fillStyle = elapsed > 10 ? '#887744' : '#556677';
|
||||||
scopeCtx.font = '12px monospace';
|
scopeCtx.font = '12px monospace';
|
||||||
scopeCtx.textAlign = 'center';
|
scopeCtx.textAlign = 'center';
|
||||||
scopeCtx.fillText('Awaiting SDR data\u2026', w / 2, h / 2);
|
scopeCtx.fillText(waitText, w / 2, h / 2);
|
||||||
scopeCtx.textAlign = 'start';
|
scopeCtx.textAlign = 'start';
|
||||||
}
|
}
|
||||||
scopeAnim = requestAnimationFrame(draw);
|
scopeAnim = requestAnimationFrame(draw);
|
||||||
@@ -371,6 +383,30 @@ var MorseMode = (function () {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Diagnostic log ----
|
||||||
|
|
||||||
|
function appendDiagLine(text) {
|
||||||
|
var log = document.getElementById('morseDiagLog');
|
||||||
|
if (!log) return;
|
||||||
|
log.style.display = 'block';
|
||||||
|
var line = document.createElement('div');
|
||||||
|
line.textContent = text;
|
||||||
|
log.appendChild(line);
|
||||||
|
// Limit to 20 entries
|
||||||
|
while (log.children.length > 20) {
|
||||||
|
log.removeChild(log.firstChild);
|
||||||
|
}
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDiagLog() {
|
||||||
|
var log = document.getElementById('morseDiagLog');
|
||||||
|
if (log) {
|
||||||
|
log.innerHTML = '';
|
||||||
|
log.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- UI ----
|
// ---- UI ----
|
||||||
|
|
||||||
function updateUI(running) {
|
function updateUI(running) {
|
||||||
@@ -398,6 +434,14 @@ var MorseMode = (function () {
|
|||||||
var scopeStatus = document.getElementById('morseScopeStatusLabel');
|
var scopeStatus = document.getElementById('morseScopeStatusLabel');
|
||||||
if (scopeStatus) scopeStatus.textContent = running ? 'ACTIVE' : 'IDLE';
|
if (scopeStatus) scopeStatus.textContent = running ? 'ACTIVE' : 'IDLE';
|
||||||
if (scopeStatus) scopeStatus.style.color = running ? '#0f0' : '#444';
|
if (scopeStatus) scopeStatus.style.color = running ? '#0f0' : '#444';
|
||||||
|
|
||||||
|
// Diagnostic log: clear on start, hide on stop
|
||||||
|
if (running) {
|
||||||
|
clearDiagLog();
|
||||||
|
} else {
|
||||||
|
var diagLog = document.getElementById('morseDiagLog');
|
||||||
|
if (diagLog) diagLog.style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFreq(mhz) {
|
function setFreq(mhz) {
|
||||||
|
|||||||
@@ -3085,6 +3085,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="morseDiagLog" style="display: none; margin-bottom: 8px; max-height: 60px; overflow-y: auto;
|
||||||
|
background: #080812; border: 1px solid #1a1a2e; border-radius: 4px; padding: 4px 8px;
|
||||||
|
font-family: var(--font-mono); font-size: 10px; color: #556677; line-height: 1.6;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Morse Decoded Output -->
|
<!-- Morse Decoded Output -->
|
||||||
<div id="morseOutputPanel" style="display: none; margin-bottom: 12px;">
|
<div id="morseOutputPanel" style="display: none; margin-bottom: 12px;">
|
||||||
<div style="background: #0a0a0a; border: 1px solid #1a2e1a; border-radius: 6px; padding: 8px 10px;">
|
<div style="background: #0a0a0a; border: 1px solid #1a2e1a; border-radius: 6px; padding: 8px 10px;">
|
||||||
@@ -4172,6 +4177,8 @@
|
|||||||
const morseOutputPanel = document.getElementById('morseOutputPanel');
|
const morseOutputPanel = document.getElementById('morseOutputPanel');
|
||||||
if (morseScopePanel && mode !== 'morse') morseScopePanel.style.display = 'none';
|
if (morseScopePanel && mode !== 'morse') morseScopePanel.style.display = 'none';
|
||||||
if (morseOutputPanel && mode !== 'morse') morseOutputPanel.style.display = 'none';
|
if (morseOutputPanel && mode !== 'morse') morseOutputPanel.style.display = 'none';
|
||||||
|
const morseDiagLog = document.getElementById('morseDiagLog');
|
||||||
|
if (morseDiagLog && mode !== 'morse') morseDiagLog.style.display = 'none';
|
||||||
|
|
||||||
// Update output panel title based on mode
|
// Update output panel title based on mode
|
||||||
const outputTitle = document.getElementById('outputTitle');
|
const outputTitle = document.getElementById('outputTitle');
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ def morse_decoder_thread(
|
|||||||
CHUNK = 4096 # bytes per read (2048 samples at 16-bit mono)
|
CHUNK = 4096 # bytes per read (2048 samples at 16-bit mono)
|
||||||
SCOPE_INTERVAL = 0.1 # scope updates at ~10 Hz
|
SCOPE_INTERVAL = 0.1 # scope updates at ~10 Hz
|
||||||
last_scope = time.monotonic()
|
last_scope = time.monotonic()
|
||||||
|
waiting_since: float | None = None
|
||||||
|
|
||||||
decoder = MorseDecoder(
|
decoder = MorseDecoder(
|
||||||
sample_rate=sample_rate,
|
sample_rate=sample_rate,
|
||||||
@@ -293,6 +294,8 @@ def morse_decoder_thread(
|
|||||||
if not ready:
|
if not ready:
|
||||||
# No data from SDR — emit diagnostic heartbeat
|
# No data from SDR — emit diagnostic heartbeat
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
|
if waiting_since is None:
|
||||||
|
waiting_since = now
|
||||||
if now - last_scope >= SCOPE_INTERVAL:
|
if now - last_scope >= SCOPE_INTERVAL:
|
||||||
last_scope = now
|
last_scope = now
|
||||||
with contextlib.suppress(queue.Full):
|
with contextlib.suppress(queue.Full):
|
||||||
@@ -302,12 +305,14 @@ def morse_decoder_thread(
|
|||||||
'threshold': 0,
|
'threshold': 0,
|
||||||
'tone_on': False,
|
'tone_on': False,
|
||||||
'waiting': True,
|
'waiting': True,
|
||||||
|
'waiting_seconds': round(now - waiting_since, 1),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = os.read(fd, CHUNK)
|
data = os.read(fd, CHUNK)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
|
waiting_since = None
|
||||||
|
|
||||||
events = decoder.process_block(data)
|
events = decoder.process_block(data)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user