Reduce repeated ADS-B device probes

This commit is contained in:
James Smith
2026-03-19 07:55:19 +00:00
parent 334146b799
commit cb0fb4f3be
2 changed files with 100 additions and 111 deletions

View File

@@ -767,23 +767,14 @@ def check_adsb_tools():
has_readsb = shutil.which('readsb') is not None has_readsb = shutil.which('readsb') is not None
has_rtl_adsb = shutil.which('rtl_adsb') is not None has_rtl_adsb = shutil.which('rtl_adsb') is not None
# Check what SDR hardware is detected
devices = SDRFactory.detect_devices()
has_rtlsdr = any(d.sdr_type == SDRType.RTL_SDR for d in devices)
has_soapy_sdr = any(d.sdr_type in (SDRType.HACKRF, SDRType.LIME_SDR, SDRType.AIRSPY) for d in devices)
soapy_types = [d.sdr_type.value for d in devices if d.sdr_type in (SDRType.HACKRF, SDRType.LIME_SDR, SDRType.AIRSPY)]
# Determine if readsb is needed but missing
needs_readsb = has_soapy_sdr and not has_readsb
return jsonify({ return jsonify({
'dump1090': has_dump1090, 'dump1090': has_dump1090,
'readsb': has_readsb, 'readsb': has_readsb,
'rtl_adsb': has_rtl_adsb, 'rtl_adsb': has_rtl_adsb,
'has_rtlsdr': has_rtlsdr, 'has_rtlsdr': None,
'has_soapy_sdr': has_soapy_sdr, 'has_soapy_sdr': None,
'soapy_types': soapy_types, 'soapy_types': [],
'needs_readsb': needs_readsb 'needs_readsb': False
}) })

View File

@@ -439,6 +439,7 @@
let panelSelectionFallbackTimer = null; let panelSelectionFallbackTimer = null;
let panelSelectionStageTimer = null; let panelSelectionStageTimer = null;
let mapCrosshairRequestId = 0; let mapCrosshairRequestId = 0;
let detectedDevicesPromise = null;
// Watchlist - persisted to localStorage // Watchlist - persisted to localStorage
let watchlist = JSON.parse(localStorage.getItem('adsb_watchlist') || '[]'); let watchlist = JSON.parse(localStorage.getItem('adsb_watchlist') || '[]');
@@ -1733,11 +1734,12 @@ ACARS: ${r.statistics.acarsMessages} messages`;
loadAdsbBiasTSetting(); loadAdsbBiasTSetting();
initMap(); initMap();
initDeviceSelectors(); initDeviceSelectors()
.then((devices) => checkAdsbTools(devices))
.catch(() => checkAdsbTools([]));
updateClock(); updateClock();
setInterval(updateClock, 1000); setInterval(updateClock, 1000);
setInterval(cleanupOldAircraft, 10000); setInterval(cleanupOldAircraft, 10000);
checkAdsbTools();
checkAircraftDatabase(); checkAircraftDatabase();
checkDvbDriverConflict(); checkDvbDriverConflict();
@@ -1751,62 +1753,89 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// Track which device is being used for ADS-B tracking // Track which device is being used for ADS-B tracking
let adsbActiveDevice = null; let adsbActiveDevice = null;
function initDeviceSelectors() { function fetchJsonWithTimeout(url, options = {}, timeoutMs = 4000) {
// Populate both ADS-B and airband device selectors const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
fetch('/devices') const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
.then(r => r.json()) return fetch(url, {
.then(devices => { ...options,
const adsbSelect = document.getElementById('adsbDeviceSelect'); ...(controller ? { signal: controller.signal } : {})
const airbandSelect = document.getElementById('airbandDeviceSelect'); }).finally(() => {
if (timeoutId) clearTimeout(timeoutId);
});
}
// Clear loading state function populateCompositeDeviceSelect(select, devices, emptyLabel = 'No SDR detected') {
adsbSelect.innerHTML = ''; if (!select) return;
airbandSelect.innerHTML = ''; select.innerHTML = '';
if (!devices || devices.length === 0) {
select.innerHTML = `<option value="rtlsdr:0">${emptyLabel}</option>`;
return;
}
if (devices.length === 0) {
adsbSelect.innerHTML = '<option value="rtlsdr:0">No SDR found</option>';
airbandSelect.innerHTML = '<option value="rtlsdr:0">No SDR found</option>';
airbandSelect.disabled = true;
} else {
devices.forEach((dev, i) => { devices.forEach((dev, i) => {
const idx = dev.index !== undefined ? dev.index : i; const idx = dev.index !== undefined ? dev.index : i;
const sdrType = dev.sdr_type || 'rtlsdr'; const sdrType = dev.sdr_type || 'rtlsdr';
const compositeVal = `${sdrType}:${idx}`; const option = document.createElement('option');
const displayName = `SDR ${idx}: ${dev.name}`; option.value = `${sdrType}:${idx}`;
option.dataset.sdrType = sdrType;
// Add to ADS-B selector option.dataset.index = idx;
const adsbOpt = document.createElement('option'); option.textContent = `SDR ${idx}: ${dev.name || dev.type || 'SDR'}`;
adsbOpt.value = compositeVal; select.appendChild(option);
adsbOpt.dataset.sdrType = sdrType;
adsbOpt.dataset.index = idx;
adsbOpt.textContent = displayName;
adsbSelect.appendChild(adsbOpt);
// Add to Airband selector
const airbandOpt = document.createElement('option');
airbandOpt.value = compositeVal;
airbandOpt.dataset.sdrType = sdrType;
airbandOpt.dataset.index = idx;
airbandOpt.textContent = displayName;
airbandSelect.appendChild(airbandOpt);
}); });
}
// Default: ADS-B uses first device, Airband uses second (if available) function getDetectedDevices(force = false) {
if (!force && detectedDevicesPromise) {
return detectedDevicesPromise;
}
detectedDevicesPromise = fetchJsonWithTimeout('/devices', {}, 4000)
.then((r) => r.ok ? r.json() : [])
.catch((err) => {
console.warn('[ADS-B] Device detection failed:', err?.message || err);
return [];
});
return detectedDevicesPromise;
}
function initDeviceSelectors() {
return getDetectedDevices().then((devices) => {
const adsbSelect = document.getElementById('adsbDeviceSelect');
const airbandSelect = document.getElementById('airbandDeviceSelect');
const acarsSelect = document.getElementById('acarsDeviceSelect');
const vdl2Select = document.getElementById('vdl2DeviceSelect');
populateCompositeDeviceSelect(adsbSelect, devices, 'No SDR found');
populateCompositeDeviceSelect(airbandSelect, devices, 'No SDR found');
populateCompositeDeviceSelect(acarsSelect, devices);
populateCompositeDeviceSelect(vdl2Select, devices);
if (!devices || devices.length === 0) {
if (airbandSelect) airbandSelect.disabled = true;
return devices;
}
if (airbandSelect) {
airbandSelect.disabled = false;
}
if (adsbSelect) {
adsbSelect.value = adsbSelect.options[0]?.value || 'rtlsdr:0'; adsbSelect.value = adsbSelect.options[0]?.value || 'rtlsdr:0';
if (devices.length > 1) { }
if (airbandSelect && devices.length > 1) {
airbandSelect.value = airbandSelect.options[1]?.value || airbandSelect.options[0]?.value || 'rtlsdr:0'; airbandSelect.value = airbandSelect.options[1]?.value || airbandSelect.options[0]?.value || 'rtlsdr:0';
} }
// Show warning if only one device
if (devices.length === 1) { if (devices.length === 1) {
document.getElementById('airbandStatus').textContent = '1 SDR only'; document.getElementById('airbandStatus').textContent = '1 SDR only';
document.getElementById('airbandStatus').style.color = 'var(--accent-orange)'; document.getElementById('airbandStatus').style.color = 'var(--accent-orange)';
} }
}
}) return devices;
.catch(() => { }).catch(() => {
document.getElementById('adsbDeviceSelect').innerHTML = '<option value="rtlsdr:0">Error</option>'; document.getElementById('adsbDeviceSelect').innerHTML = '<option value="rtlsdr:0">Error</option>';
document.getElementById('airbandDeviceSelect').innerHTML = '<option value="rtlsdr:0">Error</option>'; document.getElementById('airbandDeviceSelect').innerHTML = '<option value="rtlsdr:0">Error</option>';
return [];
}); });
} }
@@ -1911,12 +1940,15 @@ ACARS: ${r.statistics.acarsMessages} messages`;
if (warning) warning.remove(); if (warning) warning.remove();
} }
function checkAdsbTools() { function checkAdsbTools(devices = []) {
fetch('/adsb/tools') fetchJsonWithTimeout('/adsb/tools', {}, 3000)
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (data.needs_readsb) { const soapyTypes = (devices || [])
showReadsbWarning(data.soapy_types); .filter((d) => ['hackrf', 'limesdr', 'airspy'].includes((d.sdr_type || '').toLowerCase()))
.map((d) => d.sdr_type);
if (!data.readsb && soapyTypes.length > 0) {
showReadsbWarning(soapyTypes);
} }
}) })
.catch(() => {}); .catch(() => {});
@@ -4297,25 +4329,8 @@ sudo make install</code>
// Populate ACARS device selector // Populate ACARS device selector
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
fetch('/devices') getDetectedDevices().then((devices) => {
.then(r => r.json()) populateCompositeDeviceSelect(document.getElementById('acarsDeviceSelect'), devices);
.then(devices => {
const select = document.getElementById('acarsDeviceSelect');
select.innerHTML = '';
if (devices.length === 0) {
select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>';
} else {
devices.forEach((d, i) => {
const opt = document.createElement('option');
const sdrType = d.sdr_type || 'rtlsdr';
const idx = d.index !== undefined ? d.index : i;
opt.value = `${sdrType}:${idx}`;
opt.dataset.sdrType = sdrType;
opt.dataset.index = idx;
opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`;
select.appendChild(opt);
});
}
}); });
}); });
@@ -4846,25 +4861,8 @@ sudo make install</code>
// Populate VDL2 device selector and check running status // Populate VDL2 device selector and check running status
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
fetch('/devices') getDetectedDevices().then((devices) => {
.then(r => r.json()) populateCompositeDeviceSelect(document.getElementById('vdl2DeviceSelect'), devices);
.then(devices => {
const select = document.getElementById('vdl2DeviceSelect');
select.innerHTML = '';
if (devices.length === 0) {
select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>';
} else {
devices.forEach((d, i) => {
const opt = document.createElement('option');
const sdrType = d.sdr_type || 'rtlsdr';
const idx = d.index !== undefined ? d.index : i;
opt.value = `${sdrType}:${idx}`;
opt.dataset.sdrType = sdrType;
opt.dataset.index = idx;
opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`;
select.appendChild(opt);
});
}
}); });
// Check if VDL2 is already running (e.g. after page reload) // Check if VDL2 is already running (e.g. after page reload)