diff --git a/CHANGELOG.md b/CHANGELOG.md index ede1359..b502190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to iNTERCEPT will be documented in this file. +## [2.26.10] - 2026-03-14 + +### Fixed +- **APRS stop timeout and inverted SDR device status** — The APRS stop endpoint terminated two processes sequentially (up to 4s) while the frontend timed out at 2.2s, causing console errors and the SDR status panel to show stale state (active after stop, idle during use). Now releases the SDR device immediately and terminates processes in a background thread so the response returns instantly. (#194) + +--- + ## [2.26.9] - 2026-03-14 ### Fixed diff --git a/config.py b/config.py index c748bac..d34bf98 100644 --- a/config.py +++ b/config.py @@ -7,15 +7,15 @@ import os import sys # Application version -VERSION = "2.26.9" +VERSION = "2.26.10" # Changelog - latest release notes (shown on welcome screen) CHANGELOG = [ { - "version": "2.26.9", + "version": "2.26.10", "date": "March 2026", "highlights": [ - "ADS-B bias-t fallback via rtl_biast for Blog V4 when dump1090 lacks native support", + "Fix APRS stop timeout and inverted SDR device status", ] }, { diff --git a/pyproject.toml b/pyproject.toml index 1ff2bd1..c41db61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "intercept" -version = "2.26.9" +version = "2.26.10" description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth" readme = "README.md" requires-python = ">=3.9" diff --git a/routes/aprs.py b/routes/aprs.py index d523386..0d1d8d0 100644 --- a/routes/aprs.py +++ b/routes/aprs.py @@ -1924,7 +1924,13 @@ def start_aprs() -> Response: @aprs_bp.route('/stop', methods=['POST']) def stop_aprs() -> Response: - """Stop APRS decoder.""" + """Stop APRS decoder. + + Releases the SDR device immediately so the status panel updates + without waiting for process termination. Process cleanup runs in a + background thread to avoid blocking the HTTP response (which caused + frontend timeout errors when two processes each took up to 2s to die). + """ global aprs_active_device, aprs_active_sdr_type with app_module.aprs_lock: @@ -1939,6 +1945,28 @@ def stop_aprs() -> Response: if not processes_to_stop: return api_error('APRS decoder not running', 400) + # Release SDR device immediately so status panel reflects the + # change without waiting for process termination. + if aprs_active_device is not None: + app_module.release_sdr_device(aprs_active_device, aprs_active_sdr_type or 'rtlsdr') + aprs_active_device = None + aprs_active_sdr_type = None + + # Capture refs to clear before releasing the lock + master_fd = getattr(app_module, 'aprs_master_fd', None) + app_module.aprs_process = None + if hasattr(app_module, 'aprs_rtl_process'): + app_module.aprs_rtl_process = None + app_module.aprs_master_fd = None + + # Terminate processes in background so the response returns fast. + # Each proc.wait() can block up to PROCESS_TERMINATE_TIMEOUT (2s), + # which previously caused the frontend 2200ms fetch to abort. + def _cleanup(): + # Close PTY master fd first — this unblocks the stream thread + if master_fd is not None: + with contextlib.suppress(OSError): + os.close(master_fd) for proc in processes_to_stop: try: proc.terminate() @@ -1948,21 +1976,7 @@ def stop_aprs() -> Response: except Exception as e: logger.error(f"Error stopping APRS process: {e}") - # Close PTY master fd - if hasattr(app_module, 'aprs_master_fd') and app_module.aprs_master_fd is not None: - with contextlib.suppress(OSError): - os.close(app_module.aprs_master_fd) - app_module.aprs_master_fd = None - - app_module.aprs_process = None - if hasattr(app_module, 'aprs_rtl_process'): - app_module.aprs_rtl_process = None - - # Release SDR device - if aprs_active_device is not None: - app_module.release_sdr_device(aprs_active_device, aprs_active_sdr_type or 'rtlsdr') - aprs_active_device = None - aprs_active_sdr_type = None + threading.Thread(target=_cleanup, daemon=True).start() return jsonify({'status': 'stopped'})