morse: fix startup race and stuck noise floor in Goertzel decoder

Filter decoder-thread 'stopped' status events that race with the route
lifecycle, causing the frontend to drop back to idle on first start.
Pull noise floor upward using adjacent-frequency Goertzel reference when
warmup calibration runs before AGC converges, preventing permanent
tone-on with zero character decodes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-26 21:05:25 +00:00
parent 2e1b9b27be
commit 33a360b483
2 changed files with 23 additions and 1 deletions
+18 -1
View File
@@ -32,6 +32,23 @@ from utils.validation import (
morse_bp = Blueprint('morse', __name__) morse_bp = Blueprint('morse', __name__)
class _FilteredQueue:
"""Suppress decoder-thread 'stopped' events that race with route lifecycle."""
def __init__(self, inner: queue.Queue) -> None:
self._inner = inner
def put_nowait(self, item: Any) -> None:
if isinstance(item, dict) and item.get('type') == 'status' and item.get('status') == 'stopped':
return
self._inner.put_nowait(item)
def put(self, item: Any, **kwargs: Any) -> None:
if isinstance(item, dict) and item.get('type') == 'status' and item.get('status') == 'stopped':
return
self._inner.put(item, **kwargs)
# Track which device is being used # Track which device is being used
morse_active_device: int | None = None morse_active_device: int | None = None
@@ -495,7 +512,7 @@ def start_morse() -> Response:
target=morse_decoder_thread, target=morse_decoder_thread,
kwargs={ kwargs={
'rtl_stdout': rtl_process.stdout, 'rtl_stdout': rtl_process.stdout,
'output_queue': app_module.morse_queue, 'output_queue': _FilteredQueue(app_module.morse_queue),
'stop_event': stop_event, 'stop_event': stop_event,
'sample_rate': sample_rate, 'sample_rate': sample_rate,
'tone_freq': tone_freq, 'tone_freq': tone_freq,
+5
View File
@@ -433,6 +433,11 @@ class MorseDecoder:
self._signal_peak = max(self._signal_peak, self._noise_floor * 1.05) self._signal_peak = max(self._signal_peak, self._noise_floor * 1.05)
# Prevent noise floor from staying stuck below actual ambient noise
# (occurs when warmup calibration runs before AGC converges)
if noise_ref > self._noise_floor * 1.5:
self._noise_floor += settle_alpha * 0.5 * (noise_ref - self._noise_floor)
if self.threshold_mode == 'manual': if self.threshold_mode == 'manual':
self._threshold = max(0.0, self.manual_threshold) self._threshold = max(0.0, self.manual_threshold)
else: else: