mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 22:59:59 -07:00
fix: SSTV VIS detector stuck in DETECTED state on validation failure
The previous fix (f29ae3d) introduced a regression: when VIS parity
check failed or the VIS code was unrecognized, the detector entered
DETECTED state permanently and never resumed scanning. Now it resets
to IDLE on validation failure and only enters DETECTED on success.
Also resets partial image progress counter between consecutive decodes
and adds SDR device claiming to general SSTV route to prevent conflicts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from flask import Blueprint, Response, jsonify, request, send_file
|
from flask import Blueprint, Response, jsonify, request, send_file
|
||||||
|
|
||||||
|
import app as app_module
|
||||||
from utils.logging import get_logger
|
from utils.logging import get_logger
|
||||||
from utils.sse import format_sse
|
from utils.sse import format_sse
|
||||||
from utils.event_pipeline import process_event
|
from utils.event_pipeline import process_event
|
||||||
@@ -27,6 +28,9 @@ sstv_general_bp = Blueprint('sstv_general', __name__, url_prefix='/sstv-general'
|
|||||||
# Queue for SSE progress streaming
|
# Queue for SSE progress streaming
|
||||||
_sstv_general_queue: queue.Queue = queue.Queue(maxsize=100)
|
_sstv_general_queue: queue.Queue = queue.Queue(maxsize=100)
|
||||||
|
|
||||||
|
# Track which device is being used
|
||||||
|
_sstv_general_active_device: int | None = None
|
||||||
|
|
||||||
# Predefined SSTV frequencies
|
# Predefined SSTV frequencies
|
||||||
SSTV_FREQUENCIES = [
|
SSTV_FREQUENCIES = [
|
||||||
{'band': '80 m', 'frequency': 3.845, 'modulation': 'lsb', 'notes': 'Common US SSTV calling frequency', 'type': 'Terrestrial HF'},
|
{'band': '80 m', 'frequency': 3.845, 'modulation': 'lsb', 'notes': 'Common US SSTV calling frequency', 'type': 'Terrestrial HF'},
|
||||||
@@ -150,6 +154,17 @@ def start_decoder():
|
|||||||
'message': 'Modulation must be fm, usb, or lsb',
|
'message': 'Modulation must be fm, usb, or lsb',
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
|
# Claim SDR device
|
||||||
|
global _sstv_general_active_device
|
||||||
|
device_int = int(device_index)
|
||||||
|
error = app_module.claim_sdr_device(device_int, 'sstv_general')
|
||||||
|
if error:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'error_type': 'DEVICE_BUSY',
|
||||||
|
'message': error,
|
||||||
|
}), 409
|
||||||
|
|
||||||
# Set callback and start
|
# Set callback and start
|
||||||
decoder.set_callback(_progress_callback)
|
decoder.set_callback(_progress_callback)
|
||||||
success = decoder.start(
|
success = decoder.start(
|
||||||
@@ -159,6 +174,7 @@ def start_decoder():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
_sstv_general_active_device = device_int
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'started',
|
'status': 'started',
|
||||||
'frequency': frequency,
|
'frequency': frequency,
|
||||||
@@ -166,6 +182,7 @@ def start_decoder():
|
|||||||
'device': device_index,
|
'device': device_index,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
|
app_module.release_sdr_device(device_int)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Failed to start decoder',
|
'message': 'Failed to start decoder',
|
||||||
@@ -175,8 +192,14 @@ def start_decoder():
|
|||||||
@sstv_general_bp.route('/stop', methods=['POST'])
|
@sstv_general_bp.route('/stop', methods=['POST'])
|
||||||
def stop_decoder():
|
def stop_decoder():
|
||||||
"""Stop general SSTV decoder."""
|
"""Stop general SSTV decoder."""
|
||||||
|
global _sstv_general_active_device
|
||||||
decoder = get_general_sstv_decoder()
|
decoder = get_general_sstv_decoder()
|
||||||
decoder.stop()
|
decoder.stop()
|
||||||
|
|
||||||
|
if _sstv_general_active_device is not None:
|
||||||
|
app_module.release_sdr_device(_sstv_general_active_device)
|
||||||
|
_sstv_general_active_device = None
|
||||||
|
|
||||||
return jsonify({'status': 'stopped'})
|
return jsonify({'status': 'stopped'})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -475,6 +475,7 @@ class SSTVDecoder:
|
|||||||
mode_spec = get_mode(vis_code)
|
mode_spec = get_mode(vis_code)
|
||||||
if mode_spec:
|
if mode_spec:
|
||||||
current_mode_name = mode_name
|
current_mode_name = mode_name
|
||||||
|
last_partial_pct = -1
|
||||||
image_decoder = SSTVImageDecoder(
|
image_decoder = SSTVImageDecoder(
|
||||||
mode_spec,
|
mode_spec,
|
||||||
sample_rate=SAMPLE_RATE,
|
sample_rate=SAMPLE_RATE,
|
||||||
|
|||||||
@@ -298,16 +298,26 @@ class VISDetector:
|
|||||||
|
|
||||||
if self._tone_counter >= self._bit_windows:
|
if self._tone_counter >= self._bit_windows:
|
||||||
result = self._validate_and_decode()
|
result = self._validate_and_decode()
|
||||||
# Do NOT call reset() here. self._buffer still holds samples
|
if result is not None:
|
||||||
# that arrived after the STOP_BIT window — those are the very
|
# Do NOT call reset() here. self._buffer still holds
|
||||||
# first samples of the image. Wiping the buffer here causes all
|
# samples that arrived after the STOP_BIT window — those
|
||||||
# of them to be lost, making the image decoder start mid-stream
|
# are the very first samples of the image. Wiping the
|
||||||
# and producing garbled/diagonal output.
|
# buffer here causes all of them to be lost, making the
|
||||||
|
# image decoder start mid-stream and producing
|
||||||
|
# garbled/diagonal output.
|
||||||
# feed() will advance past the current window, leaving
|
# feed() will advance past the current window, leaving
|
||||||
# self._buffer pointing at the image start. The caller must
|
# self._buffer pointing at the image start. The caller
|
||||||
# read remaining_buffer and then call reset() explicitly.
|
# must read remaining_buffer and then call reset().
|
||||||
self._state = VISState.DETECTED
|
self._state = VISState.DETECTED
|
||||||
return result
|
return result
|
||||||
|
else:
|
||||||
|
# Parity failure or unknown VIS code — reset and
|
||||||
|
# continue scanning for the next VIS header.
|
||||||
|
self._tone_counter = 0
|
||||||
|
self._data_bits = []
|
||||||
|
self._parity_bit = 0
|
||||||
|
self._bit_accumulator = []
|
||||||
|
self._state = VISState.IDLE
|
||||||
|
|
||||||
elif self._state == VISState.DETECTED:
|
elif self._state == VISState.DETECTED:
|
||||||
# Waiting for caller to call reset() after reading remaining_buffer.
|
# Waiting for caller to call reset() after reading remaining_buffer.
|
||||||
|
|||||||
Reference in New Issue
Block a user