From 6384e3957667a005b968e0c3ae478d9199a5c603 Mon Sep 17 00:00:00 2001 From: Smittix Date: Tue, 24 Feb 2026 23:32:08 +0000 Subject: [PATCH] Fix GPS globe startup and satellite polling errors --- routes/gps.py | 31 ++++++++------- static/js/modes/gps.js | 83 +++++++++++++++++++++++++++++++-------- static/js/modes/websdr.js | 29 +++++++++++++- tests/test_gps_routes.py | 15 +++++++ 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/routes/gps.py b/routes/gps.py index 22213cd..aab2b9e 100644 --- a/routes/gps.py +++ b/routes/gps.py @@ -207,21 +207,22 @@ def get_position(): }) -@gps_bp.route('/satellites') -def get_satellites(): - """Get current satellite sky view data.""" - reader = get_gps_reader() - - if not reader or not reader.is_running: - return jsonify({ - 'status': 'error', - 'message': 'GPS client not running' - }), 400 - - sky = reader.sky - if sky: - return jsonify({ - 'status': 'ok', +@gps_bp.route('/satellites') +def get_satellites(): + """Get current satellite sky view data.""" + reader = get_gps_reader() + + if not reader or not reader.is_running: + return jsonify({ + 'status': 'waiting', + 'running': False, + 'message': 'GPS client not running' + }) + + sky = reader.sky + if sky: + return jsonify({ + 'status': 'ok', 'sky': sky.to_dict() }) else: diff --git a/static/js/modes/gps.js b/static/js/modes/gps.js index 2b5100f..f7f7d6e 100644 --- a/static/js/modes/gps.js +++ b/static/js/modes/gps.js @@ -74,12 +74,32 @@ const GPS = (function() { if (skyRendererInitPromise) return skyRendererInitPromise; skyRendererInitAttempted = true; + let fallbackRenderer = null; + const fallbackCanvas = document.getElementById('gpsSkyCanvas'); + const fallbackOverlay = document.getElementById('gpsSkyOverlay'); + + // Show an immediate fallback while the globe library loads. + setSkyCanvasFallbackMode(true); + if (fallbackCanvas) { + try { + fallbackRenderer = createWebGlSkyRenderer(fallbackCanvas, fallbackOverlay); + skyRenderer = fallbackRenderer; + } catch (err) { + fallbackRenderer = null; + skyRenderer = null; + console.warn('GPS sky WebGL renderer failed, falling back to 2D', err); + } + } + skyRendererInitPromise = (async function() { const globeContainer = document.getElementById('gpsSkyGlobe'); if (globeContainer) { try { const globeRenderer = await createGlobeSkyRenderer(globeContainer); if (globeRenderer) { + if (fallbackRenderer && fallbackRenderer !== globeRenderer && typeof fallbackRenderer.destroy === 'function') { + fallbackRenderer.destroy(); + } setSkyCanvasFallbackMode(false); skyRenderer = globeRenderer; return skyRenderer; @@ -90,19 +110,17 @@ const GPS = (function() { } setSkyCanvasFallbackMode(true); - - const canvas = document.getElementById('gpsSkyCanvas'); - if (!canvas) return null; - - const overlay = document.getElementById('gpsSkyOverlay'); - try { - skyRenderer = createWebGlSkyRenderer(canvas, overlay); - return skyRenderer; - } catch (err) { - skyRenderer = null; - console.warn('GPS sky WebGL renderer failed, falling back to 2D', err); - return null; + if (!fallbackRenderer && fallbackCanvas) { + try { + fallbackRenderer = createWebGlSkyRenderer(fallbackCanvas, fallbackOverlay); + } catch (err) { + fallbackRenderer = null; + console.warn('GPS sky WebGL renderer failed, falling back to 2D', err); + } } + + skyRenderer = fallbackRenderer; + return skyRenderer; })(); return skyRendererInitPromise; @@ -158,10 +176,35 @@ const GPS = (function() { } function loadGpsGlobeScript(src) { + const state = getSharedGlobeScriptState(); + if (!state.promises[src]) { + state.promises[src] = loadSharedGlobeScript(src); + } + return state.promises[src].catch((error) => { + delete state.promises[src]; + throw error; + }); + } + + function getSharedGlobeScriptState() { + const key = '__interceptGlobeScriptState'; + if (!window[key]) { + window[key] = { + promises: Object.create(null), + }; + } + return window[key]; + } + + function loadSharedGlobeScript(src) { return new Promise((resolve, reject) => { - const existing = document.querySelector( - `script[data-websdr-src="${src}"], script[data-gps-globe-src="${src}"], script[src="${src}"]` - ); + const selector = [ + `script[data-intercept-globe-src="${src}"]`, + `script[data-websdr-src="${src}"]`, + `script[data-gps-globe-src="${src}"]`, + `script[src="${src}"]`, + ].join(', '); + const existing = document.querySelector(selector); if (existing) { if (existing.dataset.loaded === 'true') { @@ -181,6 +224,7 @@ const GPS = (function() { script.src = src; script.async = true; script.crossOrigin = 'anonymous'; + script.dataset.interceptGlobeSrc = src; script.dataset.gpsGlobeSrc = src; script.onload = () => { script.dataset.loaded = 'true'; @@ -521,7 +565,14 @@ const GPS = (function() { fetch('/gps/status') .then(r => r.json()) .then(data => { - if (!connected || !data || data.running !== true) return; + if (!connected || !data) return; + if (data.running !== true) { + connected = false; + stopSkyPolling(); + stopStatusPolling(); + updateConnectionUI(false, false, 'error', data.message || 'GPS disconnected'); + return; + } if (data.position) { lastPosition = data.position; diff --git a/static/js/modes/websdr.js b/static/js/modes/websdr.js index dc3a5f7..b2a60fe 100644 --- a/static/js/modes/websdr.js +++ b/static/js/modes/websdr.js @@ -185,8 +185,34 @@ async function ensureWebsdrGlobeLibrary() { } function loadWebsdrScript(src) { + const state = getSharedGlobeScriptState(); + if (!state.promises[src]) { + state.promises[src] = loadSharedGlobeScript(src); + } + return state.promises[src].catch((error) => { + delete state.promises[src]; + throw error; + }); +} + +function getSharedGlobeScriptState() { + const key = '__interceptGlobeScriptState'; + if (!window[key]) { + window[key] = { + promises: Object.create(null), + }; + } + return window[key]; +} + +function loadSharedGlobeScript(src) { return new Promise((resolve, reject) => { - const selector = `script[data-websdr-src="${src}"], script[data-gps-globe-src="${src}"], script[src="${src}"]`; + const selector = [ + `script[data-intercept-globe-src="${src}"]`, + `script[data-websdr-src="${src}"]`, + `script[data-gps-globe-src="${src}"]`, + `script[src="${src}"]`, + ].join(', '); const existing = document.querySelector(selector); if (existing) { @@ -207,6 +233,7 @@ function loadWebsdrScript(src) { script.src = src; script.async = true; script.crossOrigin = 'anonymous'; + script.dataset.interceptGlobeSrc = src; script.dataset.websdrSrc = src; script.onload = () => { script.dataset.loaded = 'true'; diff --git a/tests/test_gps_routes.py b/tests/test_gps_routes.py index 496e7a0..4d1a68a 100644 --- a/tests/test_gps_routes.py +++ b/tests/test_gps_routes.py @@ -61,3 +61,18 @@ def test_auto_connect_attaches_callbacks_when_reader_already_running(client, mon assert payload['status'] == 'connected' assert reader.position_callbacks == [gps_routes._position_callback] assert reader.sky_callbacks == [gps_routes._sky_callback] + + +def test_satellites_returns_waiting_when_reader_not_running(client, monkeypatch): + """Satellite endpoint should return a non-error waiting state when reader is down.""" + monkeypatch.setattr(gps_routes, 'get_gps_reader', lambda: None) + + with client.session_transaction() as sess: + sess['logged_in'] = True + + response = client.get('/gps/satellites') + payload = response.get_json() + + assert response.status_code == 200 + assert payload['status'] == 'waiting' + assert payload['running'] is False