diff --git a/routes/adsb.py b/routes/adsb.py index c718415..338a32a 100644 --- a/routes/adsb.py +++ b/routes/adsb.py @@ -893,6 +893,36 @@ def start_adsb(): 'message': full_msg }) + # dump1090 is still running but SBS port never came up — device may be + # held by a stale process from a previous mode. Kill it so the USB + # device is released and report a clear error to the frontend. + if not dump1090_ready: + logger.warning("dump1090 running but SBS port not available after %.1fs — killing", DUMP1090_START_WAIT) + try: + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 15) + app_module.adsb_process.wait(timeout=ADSB_TERMINATE_TIMEOUT) + except (subprocess.TimeoutExpired, ProcessLookupError, OSError): + try: + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 9) + except (ProcessLookupError, OSError): + pass + app_module.adsb_process = None + clear_dump1090_pid() + app_module.release_sdr_device(device_int, sdr_type_str) + adsb_active_device = None + adsb_active_sdr_type = None + return jsonify({ + 'status': 'error', + 'error_type': 'DEVICE_BUSY', + 'message': ( + 'SDR device did not become ready in time. ' + 'Another mode may still be releasing the device. ' + 'Please wait a moment and try again.' + ), + }) + adsb_using_service = True thread = threading.Thread(target=parse_sbs_stream, args=(f'localhost:{ADSB_SBS_PORT}',), daemon=True) thread.start() diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 4a4d3c8..a7fa318 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -2172,6 +2172,58 @@ sudo make install const [adsbSdrType, adsbDeviceIdx] = adsbSelectVal.includes(':') ? adsbSelectVal.split(':') : ['rtlsdr', adsbSelectVal]; const adsbDevice = parseInt(adsbDeviceIdx) || 0; + // Pre-flight: check if another mode is using this device and auto-stop it + if (!useAgent) { + try { + const devResp = await fetch('/devices/status'); + if (devResp.ok) { + const devices = await devResp.json(); + const target = devices.find(d => d.sdr_type === adsbSdrType && d.index === adsbDevice); + if (target && target.in_use && target.used_by && target.used_by !== 'adsb') { + const stopEndpoints = { + pager: '/stop', + sensor: '/stop_sensor', + morse: '/morse/stop', + adsb: '/adsb/stop', + acars: '/acars/stop', + vdl2: '/vdl2/stop', + ais: '/ais/stop', + dsc: '/dsc/stop', + weathersat: '/weather-sat/stop', + sstv: '/sstv/stop', + radiosonde: '/radiosonde/stop', + rtlamr: '/rtlamr/stop_rtlamr', + aprs: '/aprs/stop', + tscm: '/tscm/sweep/stop', + wefax: '/wefax/stop', + sstv_general: '/sstv-general/stop', + }; + const mode = target.used_by; + const stopUrl = stopEndpoints[mode]; + if (stopUrl) { + console.log(`Device ${adsbSdrType}:${adsbDevice} in use by ${mode}, stopping...`); + btn.textContent = `Stopping ${mode}...`; + btn.disabled = true; + try { + await fetch(stopUrl, { method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ source: 'adsb_conflict' }) + }); + } catch (e) { + console.warn(`Failed to stop ${mode}:`, e); + } + // Wait for USB device to be released + await new Promise(resolve => setTimeout(resolve, 1500)); + btn.textContent = 'Starting...'; + btn.disabled = false; + } + } + } + } catch (e) { + console.warn('Device conflict check failed:', e); + } + } + const requestBody = { device: adsbDevice, sdr_type: adsbSdrType,