mirror of
https://github.com/smittix/intercept.git
synced 2026-07-12 11:38:12 -07:00
Stabilize monitor retune across waterfall click restarts
This commit is contained in:
+28
-18
@@ -1573,29 +1573,39 @@ def stream_audio() -> Response:
|
|||||||
if not audio_running:
|
if not audio_running:
|
||||||
return Response(b'', mimetype='audio/wav', status=204)
|
return Response(b'', mimetype='audio/wav', status=204)
|
||||||
|
|
||||||
def generate_shared():
|
def generate_shared():
|
||||||
global audio_running, audio_source
|
global audio_running, audio_source
|
||||||
try:
|
try:
|
||||||
from routes.waterfall_websocket import (
|
from routes.waterfall_websocket import (
|
||||||
get_shared_capture_status,
|
get_shared_capture_status,
|
||||||
read_shared_monitor_audio_chunk,
|
read_shared_monitor_audio_chunk,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Browser expects an immediate WAV header.
|
# Browser expects an immediate WAV header.
|
||||||
yield _wav_header(sample_rate=48000)
|
yield _wav_header(sample_rate=48000)
|
||||||
|
inactive_since: float | None = None
|
||||||
while audio_running and audio_source == 'waterfall':
|
|
||||||
chunk = read_shared_monitor_audio_chunk(timeout=1.0)
|
while audio_running and audio_source == 'waterfall':
|
||||||
if chunk:
|
chunk = read_shared_monitor_audio_chunk(timeout=1.0)
|
||||||
yield chunk
|
if chunk:
|
||||||
continue
|
inactive_since = None
|
||||||
shared = get_shared_capture_status()
|
yield chunk
|
||||||
if not shared.get('running') or not shared.get('monitor_enabled'):
|
continue
|
||||||
audio_running = False
|
shared = get_shared_capture_status()
|
||||||
audio_source = 'process'
|
if shared.get('running') and shared.get('monitor_enabled'):
|
||||||
break
|
inactive_since = None
|
||||||
|
continue
|
||||||
|
if inactive_since is None:
|
||||||
|
inactive_since = time.monotonic()
|
||||||
|
continue
|
||||||
|
if (time.monotonic() - inactive_since) < 4.0:
|
||||||
|
continue
|
||||||
|
if not shared.get('running') or not shared.get('monitor_enabled'):
|
||||||
|
audio_running = False
|
||||||
|
audio_source = 'process'
|
||||||
|
break
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
generate_shared(),
|
generate_shared(),
|
||||||
|
|||||||
@@ -413,6 +413,10 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
cmd = data.get('cmd')
|
cmd = data.get('cmd')
|
||||||
|
|
||||||
if cmd == 'start':
|
if cmd == 'start':
|
||||||
|
shared_before = get_shared_capture_status()
|
||||||
|
keep_monitor_enabled = bool(shared_before.get('monitor_enabled'))
|
||||||
|
keep_monitor_modulation = str(shared_before.get('monitor_modulation', 'wfm'))
|
||||||
|
keep_monitor_squelch = int(shared_before.get('monitor_squelch', 0) or 0)
|
||||||
# Stop any existing capture
|
# Stop any existing capture
|
||||||
was_restarting = iq_process is not None
|
was_restarting = iq_process is not None
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
@@ -441,6 +445,12 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
# Parse config
|
# Parse config
|
||||||
try:
|
try:
|
||||||
center_freq_mhz = _parse_center_freq_mhz(data)
|
center_freq_mhz = _parse_center_freq_mhz(data)
|
||||||
|
requested_vfo_mhz = float(
|
||||||
|
data.get(
|
||||||
|
'vfo_freq_mhz',
|
||||||
|
data.get('frequency_mhz', center_freq_mhz),
|
||||||
|
)
|
||||||
|
)
|
||||||
span_mhz = _parse_span_mhz(data)
|
span_mhz = _parse_span_mhz(data)
|
||||||
gain_raw = data.get('gain')
|
gain_raw = data.get('gain')
|
||||||
if gain_raw is None or str(gain_raw).lower() == 'auto':
|
if gain_raw is None or str(gain_raw).lower() == 'auto':
|
||||||
@@ -491,6 +501,9 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
effective_span_mhz = sample_rate / 1e6
|
effective_span_mhz = sample_rate / 1e6
|
||||||
start_freq = center_freq_mhz - effective_span_mhz / 2
|
start_freq = center_freq_mhz - effective_span_mhz / 2
|
||||||
end_freq = center_freq_mhz + effective_span_mhz / 2
|
end_freq = center_freq_mhz + effective_span_mhz / 2
|
||||||
|
target_vfo_mhz = requested_vfo_mhz
|
||||||
|
if not (start_freq <= target_vfo_mhz <= end_freq):
|
||||||
|
target_vfo_mhz = center_freq_mhz
|
||||||
|
|
||||||
# Claim the device (retry when restarting to allow
|
# Claim the device (retry when restarting to allow
|
||||||
# the kernel time to release the USB handle).
|
# the kernel time to release the USB handle).
|
||||||
@@ -591,10 +604,10 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
sample_rate=sample_rate,
|
sample_rate=sample_rate,
|
||||||
)
|
)
|
||||||
_set_shared_monitor(
|
_set_shared_monitor(
|
||||||
enabled=False,
|
enabled=keep_monitor_enabled,
|
||||||
frequency_mhz=center_freq_mhz,
|
frequency_mhz=target_vfo_mhz,
|
||||||
modulation='wfm',
|
modulation=keep_monitor_modulation,
|
||||||
squelch=0,
|
squelch=keep_monitor_squelch,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send started confirmation
|
# Send started confirmation
|
||||||
@@ -608,7 +621,7 @@ def init_waterfall_websocket(app: Flask):
|
|||||||
'effective_span_mhz': effective_span_mhz,
|
'effective_span_mhz': effective_span_mhz,
|
||||||
'db_min': db_min,
|
'db_min': db_min,
|
||||||
'db_max': db_max,
|
'db_max': db_max,
|
||||||
'vfo_freq_mhz': center_freq_mhz,
|
'vfo_freq_mhz': target_vfo_mhz,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
# Start reader thread — puts frames on queue, never calls ws.send()
|
# Start reader thread — puts frames on queue, never calls ws.send()
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const Waterfall = (function () {
|
|||||||
let _startingMonitor = false;
|
let _startingMonitor = false;
|
||||||
let _monitorSource = 'process';
|
let _monitorSource = 'process';
|
||||||
let _pendingSharedMonitorRearm = false;
|
let _pendingSharedMonitorRearm = false;
|
||||||
|
let _pendingCaptureVfoMhz = null;
|
||||||
let _audioConnectNonce = 0;
|
let _audioConnectNonce = 0;
|
||||||
let _audioAnalyser = null;
|
let _audioAnalyser = null;
|
||||||
let _audioContext = null;
|
let _audioContext = null;
|
||||||
@@ -1183,6 +1184,7 @@ const Waterfall = (function () {
|
|||||||
function _scanTuneTo(freqMhz) {
|
function _scanTuneTo(freqMhz) {
|
||||||
const clamped = _clamp(freqMhz, 0.001, 6000.0);
|
const clamped = _clamp(freqMhz, 0.001, 6000.0);
|
||||||
_monitorFreqMhz = clamped;
|
_monitorFreqMhz = clamped;
|
||||||
|
_pendingCaptureVfoMhz = clamped;
|
||||||
_updateFreqDisplay();
|
_updateFreqDisplay();
|
||||||
|
|
||||||
if (_monitoring && !_isSharedMonitorActive()) {
|
if (_monitoring && !_isSharedMonitorActive()) {
|
||||||
@@ -1873,6 +1875,7 @@ const Waterfall = (function () {
|
|||||||
if (input) input.value = clamped.toFixed(4);
|
if (input) input.value = clamped.toFixed(4);
|
||||||
|
|
||||||
_monitorFreqMhz = clamped;
|
_monitorFreqMhz = clamped;
|
||||||
|
_pendingCaptureVfoMhz = clamped;
|
||||||
const currentSpan = _endMhz - _startMhz;
|
const currentSpan = _endMhz - _startMhz;
|
||||||
const configuredSpan = _clamp(_currentSpan(), 0.05, 30.0);
|
const configuredSpan = _clamp(_currentSpan(), 0.05, 30.0);
|
||||||
const activeSpan = Number.isFinite(currentSpan) && currentSpan > 0 ? currentSpan : configuredSpan;
|
const activeSpan = Number.isFinite(currentSpan) && currentSpan > 0 ? currentSpan : configuredSpan;
|
||||||
@@ -1930,6 +1933,8 @@ const Waterfall = (function () {
|
|||||||
if (!msg || msg.status !== 'retune_required') return false;
|
if (!msg || msg.status !== 'retune_required') return false;
|
||||||
_setStatus(msg.message || 'Retuning SDR capture...');
|
_setStatus(msg.message || 'Retuning SDR capture...');
|
||||||
if (Number.isFinite(msg.vfo_freq_mhz)) {
|
if (Number.isFinite(msg.vfo_freq_mhz)) {
|
||||||
|
_monitorFreqMhz = Number(msg.vfo_freq_mhz);
|
||||||
|
_pendingCaptureVfoMhz = _monitorFreqMhz;
|
||||||
const input = document.getElementById('wfCenterFreq');
|
const input = document.getElementById('wfCenterFreq');
|
||||||
if (input) input.value = Number(msg.vfo_freq_mhz).toFixed(4);
|
if (input) input.value = Number(msg.vfo_freq_mhz).toFixed(4);
|
||||||
}
|
}
|
||||||
@@ -2210,7 +2215,6 @@ const Waterfall = (function () {
|
|||||||
const spanMhz = _clamp(_currentSpan(), 0.05, 30.0);
|
const spanMhz = _clamp(_currentSpan(), 0.05, 30.0);
|
||||||
_startMhz = centerMhz - spanMhz / 2;
|
_startMhz = centerMhz - spanMhz / 2;
|
||||||
_endMhz = centerMhz + spanMhz / 2;
|
_endMhz = centerMhz + spanMhz / 2;
|
||||||
_monitorFreqMhz = centerMhz;
|
|
||||||
_peakLine = null;
|
_peakLine = null;
|
||||||
_drawFreqAxis();
|
_drawFreqAxis();
|
||||||
|
|
||||||
@@ -2239,11 +2243,13 @@ const Waterfall = (function () {
|
|||||||
function _sendWsStartCmd() {
|
function _sendWsStartCmd() {
|
||||||
if (!_ws || _ws.readyState !== WebSocket.OPEN) return;
|
if (!_ws || _ws.readyState !== WebSocket.OPEN) return;
|
||||||
const cfg = _waterfallRequestConfig();
|
const cfg = _waterfallRequestConfig();
|
||||||
|
const targetVfoMhz = Number.isFinite(_monitorFreqMhz) ? _monitorFreqMhz : cfg.centerMhz;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
cmd: 'start',
|
cmd: 'start',
|
||||||
center_freq_mhz: cfg.centerMhz,
|
center_freq_mhz: cfg.centerMhz,
|
||||||
center_freq: cfg.centerMhz,
|
center_freq: cfg.centerMhz,
|
||||||
|
vfo_freq_mhz: targetVfoMhz,
|
||||||
span_mhz: cfg.spanMhz,
|
span_mhz: cfg.spanMhz,
|
||||||
gain: cfg.gain,
|
gain: cfg.gain,
|
||||||
sdr_type: cfg.device.sdrType,
|
sdr_type: cfg.device.sdrType,
|
||||||
@@ -2492,7 +2498,10 @@ const Waterfall = (function () {
|
|||||||
_scanAwaitingCapture = false;
|
_scanAwaitingCapture = false;
|
||||||
_scanStartPending = false;
|
_scanStartPending = false;
|
||||||
_scanRestartAttempts = 0;
|
_scanRestartAttempts = 0;
|
||||||
if (Number.isFinite(msg.vfo_freq_mhz)) {
|
if (Number.isFinite(_pendingCaptureVfoMhz)) {
|
||||||
|
_monitorFreqMhz = _pendingCaptureVfoMhz;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
|
} else if (Number.isFinite(msg.vfo_freq_mhz)) {
|
||||||
_monitorFreqMhz = Number(msg.vfo_freq_mhz);
|
_monitorFreqMhz = Number(msg.vfo_freq_mhz);
|
||||||
}
|
}
|
||||||
if (Number.isFinite(msg.start_freq) && Number.isFinite(msg.end_freq)) {
|
if (Number.isFinite(msg.start_freq) && Number.isFinite(msg.end_freq)) {
|
||||||
@@ -2502,15 +2511,20 @@ const Waterfall = (function () {
|
|||||||
}
|
}
|
||||||
_setStatus(`Streaming ${_startMhz.toFixed(4)} - ${_endMhz.toFixed(4)} MHz`);
|
_setStatus(`Streaming ${_startMhz.toFixed(4)} - ${_endMhz.toFixed(4)} MHz`);
|
||||||
_setVisualStatus('RUNNING');
|
_setVisualStatus('RUNNING');
|
||||||
if (_pendingSharedMonitorRearm) {
|
if (_monitoring) {
|
||||||
|
_pendingSharedMonitorRearm = false;
|
||||||
|
// After any capture restart, always retune monitor
|
||||||
|
// audio to the current VFO frequency.
|
||||||
|
_queueMonitorRetune(_monitorSource === 'waterfall' ? 120 : 80);
|
||||||
|
} else if (_pendingSharedMonitorRearm) {
|
||||||
_pendingSharedMonitorRearm = false;
|
_pendingSharedMonitorRearm = false;
|
||||||
if (_monitoring && _monitorSource === 'waterfall') {
|
|
||||||
_queueMonitorRetune(120);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (msg.status === 'tuned') {
|
} else if (msg.status === 'tuned') {
|
||||||
if (_onRetuneRequired(msg)) return;
|
if (_onRetuneRequired(msg)) return;
|
||||||
if (Number.isFinite(msg.vfo_freq_mhz)) {
|
if (Number.isFinite(_pendingCaptureVfoMhz)) {
|
||||||
|
_monitorFreqMhz = _pendingCaptureVfoMhz;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
|
} else if (Number.isFinite(msg.vfo_freq_mhz)) {
|
||||||
_monitorFreqMhz = Number(msg.vfo_freq_mhz);
|
_monitorFreqMhz = Number(msg.vfo_freq_mhz);
|
||||||
}
|
}
|
||||||
_updateFreqDisplay();
|
_updateFreqDisplay();
|
||||||
@@ -2520,6 +2534,7 @@ const Waterfall = (function () {
|
|||||||
return;
|
return;
|
||||||
} else if (msg.status === 'stopped') {
|
} else if (msg.status === 'stopped') {
|
||||||
_running = false;
|
_running = false;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
_scanAwaitingCapture = false;
|
_scanAwaitingCapture = false;
|
||||||
_scanStartPending = false;
|
_scanStartPending = false;
|
||||||
_scanRestartAttempts = 0;
|
_scanRestartAttempts = 0;
|
||||||
@@ -2531,6 +2546,7 @@ const Waterfall = (function () {
|
|||||||
_setVisualStatus('STOPPED');
|
_setVisualStatus('STOPPED');
|
||||||
} else if (msg.status === 'error') {
|
} else if (msg.status === 'error') {
|
||||||
_running = false;
|
_running = false;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
_scanStartPending = false;
|
_scanStartPending = false;
|
||||||
_pendingSharedMonitorRearm = false;
|
_pendingSharedMonitorRearm = false;
|
||||||
// If the monitor was using the shared IQ stream that
|
// If the monitor was using the shared IQ stream that
|
||||||
@@ -2915,6 +2931,7 @@ const Waterfall = (function () {
|
|||||||
_monitoring = false;
|
_monitoring = false;
|
||||||
_monitorSource = 'process';
|
_monitorSource = 'process';
|
||||||
_pendingSharedMonitorRearm = false;
|
_pendingSharedMonitorRearm = false;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
_syncMonitorButtons();
|
_syncMonitorButtons();
|
||||||
_setMonitorState('No audio monitor');
|
_setMonitorState('No audio monitor');
|
||||||
|
|
||||||
@@ -3107,6 +3124,7 @@ const Waterfall = (function () {
|
|||||||
_clearWsFallbackTimer();
|
_clearWsFallbackTimer();
|
||||||
_wsOpened = false;
|
_wsOpened = false;
|
||||||
_pendingSharedMonitorRearm = false;
|
_pendingSharedMonitorRearm = false;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
// Reset in-flight monitor start flag so the button is not left
|
// Reset in-flight monitor start flag so the button is not left
|
||||||
// disabled after a waterfall stop/restart cycle.
|
// disabled after a waterfall stop/restart cycle.
|
||||||
if (_startingMonitor) {
|
if (_startingMonitor) {
|
||||||
@@ -3386,6 +3404,7 @@ const Waterfall = (function () {
|
|||||||
_setUnlockVisible(false);
|
_setUnlockVisible(false);
|
||||||
_audioUnlockRequired = false;
|
_audioUnlockRequired = false;
|
||||||
_pendingSharedMonitorRearm = false;
|
_pendingSharedMonitorRearm = false;
|
||||||
|
_pendingCaptureVfoMhz = null;
|
||||||
_sseStartConfigKey = '';
|
_sseStartConfigKey = '';
|
||||||
_sseStartPromise = null;
|
_sseStartPromise = null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user