fix: release SDR device when switching modes across pages

When navigating from another mode (e.g. pager) to the ADS-B dashboard,
the old process could still hold the USB device. Two fixes:

1. routes/adsb.py: If dump1090 starts but SBS port never comes up,
   kill the process and return a DEVICE_BUSY error instead of silently
   claiming success with no data.

2. templates/adsb_dashboard.html: Pre-flight conflict check in
   toggleTracking() queries /devices/status and auto-stops any
   conflicting mode before starting ADS-B, with a 1.5s USB release
   delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-03-02 22:21:30 +00:00
parent eea44f9a6b
commit 4b64862eb4
2 changed files with 82 additions and 0 deletions

View File

@@ -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()

View File

@@ -2172,6 +2172,58 @@ sudo make install</code>
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,