diff --git a/routes/satellite.py b/routes/satellite.py index 2629a61..e3c09ad 100644 --- a/routes/satellite.py +++ b/routes/satellite.py @@ -566,8 +566,9 @@ def get_satellite_position(): except ValueError as e: return api_error(str(e), 400) - sat_input = data.get('satellites', []) - include_track = bool(data.get('includeTrack', True)) + sat_input = data.get('satellites', []) + include_track = bool(data.get('includeTrack', True)) + prefer_realtime_api = bool(data.get('preferRealtimeApi', False)) observer = wgs84.latlon(lat, lon) ts = None @@ -579,8 +580,9 @@ def get_satellite_position(): for sat in sat_input: sat_name, norad_id, tle_data = _resolve_satellite_request(sat, tracked_by_norad, tracked_by_name) - # Special handling for ISS - prefer real-time API, but fall back to TLE if offline. - if norad_id == 25544 or sat_name == 'ISS': + # Optional special handling for ISS. The dashboard does not enable this + # because external API latency can make live updates stall. + if prefer_realtime_api and (norad_id == 25544 or sat_name == 'ISS'): iss_data = _fetch_iss_realtime(lat, lon) if iss_data: # Add orbit track if requested (using TLE for track prediction) diff --git a/templates/satellite_dashboard.html b/templates/satellite_dashboard.html index b526007..4c50666 100644 --- a/templates/satellite_dashboard.html +++ b/templates/satellite_dashboard.html @@ -775,6 +775,8 @@ let agents = []; let _txRequestId = 0; let _telemetryPollTimer = null; + let _telemetryAbortController = null; + let _activeTelemetryRequestKey = null; let _passRequestId = 0; let _passAbortController = null; let _passTimeoutId = null; @@ -792,7 +794,8 @@ const DASHBOARD_FETCH_TIMEOUT_MS = 30000; const PASS_FETCH_TIMEOUT_MS = 90000; const SAT_DRAWER_FETCH_TIMEOUT_MS = 15000; - const TELEMETRY_POLL_INTERVAL_MS = 3000; + const TELEMETRY_POLL_INTERVAL_MS = 5000; + const TELEMETRY_FETCH_TIMEOUT_MS = 8000; const BUILTIN_TX_FALLBACK = { 25544: [ { description: 'APRS digipeater', downlink_low: 145.825, downlink_high: 145.825, uplink_low: null, uplink_high: null, mode: 'FM AX.25', baud: 1200, status: 'active', type: 'beacon', service: 'Packet' }, @@ -913,13 +916,10 @@ } function getPassPredictionTargets() { - const targets = Object.keys(satellites || {}) - .map(id => parseInt(id, 10)) - .filter(id => Number.isFinite(id)); - if (Number.isFinite(selectedSatellite) && !targets.includes(selectedSatellite)) { - targets.unshift(selectedSatellite); + if (Number.isFinite(selectedSatellite)) { + return [selectedSatellite]; } - return Array.from(new Set(targets)); + return [25544]; } function getActivePassRequestKey(targetIds = getPassPredictionTargets()) { @@ -1170,6 +1170,11 @@ selectedPass = null; passes = []; latestLivePosition = null; + if (_telemetryAbortController) { + _telemetryAbortController.abort(); + _telemetryAbortController = null; + } + _activeTelemetryRequestKey = null; if (groundMap) { if (trackLine) { groundMap.removeLayer(trackLine); trackLine = null; } @@ -1299,10 +1304,17 @@ const lon = parseFloat(document.getElementById('obsLon')?.value); if (!Number.isFinite(lat) || !Number.isFinite(lon) || !selectedSatellite) return; const requestedSatellite = selectedSatellite; + const requestKey = `telemetry:${requestedSatellite}:${lat.toFixed(3)}:${lon.toFixed(3)}`; + + if (_telemetryAbortController && _activeTelemetryRequestKey === requestKey) { + return; + } try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), DASHBOARD_FETCH_TIMEOUT_MS); + _telemetryAbortController = controller; + _activeTelemetryRequestKey = requestKey; + const timeout = setTimeout(() => controller.abort(), TELEMETRY_FETCH_TIMEOUT_MS); const response = await fetch('/satellite/position', { method: 'POST', credentials: 'same-origin', @@ -1316,6 +1328,12 @@ }) }); clearTimeout(timeout); + if (_telemetryAbortController === controller) { + _telemetryAbortController = null; + } + if (_activeTelemetryRequestKey === requestKey) { + _activeTelemetryRequestKey = null; + } if (!response.ok) return; const contentType = response.headers.get('Content-Type') || ''; if (!contentType.includes('application/json')) return; @@ -1326,7 +1344,14 @@ cacheLivePosition(requestedSatellite, pos); if (requestedSatellite !== selectedSatellite) return; handleLivePositions(data.positions); - } catch (_) {} + } catch (_) { + if (_telemetryAbortController?.signal?.aborted) { + _telemetryAbortController = null; + } + if (_activeTelemetryRequestKey === requestKey) { + _activeTelemetryRequestKey = null; + } + } } function startTelemetryPolling() { @@ -1593,6 +1618,10 @@ // Refresh satellite list when the window regains focus (e.g. after enabling sats in the sidebar) window.addEventListener('focus', loadDashboardSatellites); window.addEventListener('pagehide', () => { + if (_telemetryAbortController) { + _telemetryAbortController.abort(); + _telemetryAbortController = null; + } if (_passAbortController) { _passAbortController.abort('pagehide'); _passAbortController = null;