Stabilize satellite target switching

This commit is contained in:
James Smith
2026-03-19 17:44:02 +00:00
parent 511cecb311
commit 8caec74c5c
2 changed files with 44 additions and 13 deletions

View File

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

View File

@@ -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;