diff --git a/routes/sstv_general.py b/routes/sstv_general.py index 0ddcbfb..63d4f8d 100644 --- a/routes/sstv_general.py +++ b/routes/sstv_general.py @@ -13,6 +13,7 @@ from pathlib import Path from flask import Blueprint, Response, jsonify, request, send_file +import app as app_module from utils.logging import get_logger from utils.sse import format_sse 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 _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 SSTV_FREQUENCIES = [ {'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', }), 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 decoder.set_callback(_progress_callback) success = decoder.start( @@ -159,6 +174,7 @@ def start_decoder(): ) if success: + _sstv_general_active_device = device_int return jsonify({ 'status': 'started', 'frequency': frequency, @@ -166,6 +182,7 @@ def start_decoder(): 'device': device_index, }) else: + app_module.release_sdr_device(device_int) return jsonify({ 'status': 'error', 'message': 'Failed to start decoder', @@ -175,8 +192,14 @@ def start_decoder(): @sstv_general_bp.route('/stop', methods=['POST']) def stop_decoder(): """Stop general SSTV decoder.""" + global _sstv_general_active_device decoder = get_general_sstv_decoder() 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'}) diff --git a/utils/sstv/sstv_decoder.py b/utils/sstv/sstv_decoder.py index 498d52e..20f3eba 100644 --- a/utils/sstv/sstv_decoder.py +++ b/utils/sstv/sstv_decoder.py @@ -475,6 +475,7 @@ class SSTVDecoder: mode_spec = get_mode(vis_code) if mode_spec: current_mode_name = mode_name + last_partial_pct = -1 image_decoder = SSTVImageDecoder( mode_spec, sample_rate=SAMPLE_RATE, diff --git a/utils/sstv/vis.py b/utils/sstv/vis.py index 1f014e4..50a83c2 100644 --- a/utils/sstv/vis.py +++ b/utils/sstv/vis.py @@ -298,16 +298,26 @@ class VISDetector: if self._tone_counter >= self._bit_windows: result = self._validate_and_decode() - # Do NOT call reset() here. self._buffer still holds samples - # that arrived after the STOP_BIT window — those are the very - # first samples of the image. Wiping the 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 - # self._buffer pointing at the image start. The caller must - # read remaining_buffer and then call reset() explicitly. - self._state = VISState.DETECTED - return result + if result is not None: + # Do NOT call reset() here. self._buffer still holds + # samples that arrived after the STOP_BIT window — those + # are the very first samples of the image. Wiping the + # 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 + # self._buffer pointing at the image start. The caller + # must read remaining_buffer and then call reset(). + self._state = VISState.DETECTED + 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: # Waiting for caller to call reset() after reading remaining_buffer.