feat: add GPS status endpoint and dashboard indicator
- add /api/gps/status for thread/fix-age visibility - surface GPS status in Pi health and live GPS cards - add tests for app_gps status snapshots Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -209,6 +209,59 @@ function reverseGeocode(lat, lon) {
|
||||
.catch(() => { clearTimeout(t); _geocodePending = false; el.textContent = fallback; });
|
||||
}
|
||||
|
||||
function formatGpsAge(seconds) {
|
||||
if (seconds == null) return 'No fix yet.';
|
||||
if (seconds < 10) return 'Last fix just now.';
|
||||
if (seconds < 60) return `Last fix ${Math.round(seconds)}s ago.`;
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.round(seconds % 60);
|
||||
return secs ? `Last fix ${mins}m ${secs}s ago.` : `Last fix ${mins}m ago.`;
|
||||
}
|
||||
|
||||
function renderGpsStatus(status) {
|
||||
const liveBadge = document.getElementById('gps-live-badge');
|
||||
const fixDetail = document.getElementById('gps-fix-detail');
|
||||
const healthStatus = document.getElementById('pi-gps-status');
|
||||
const healthDetail = document.getElementById('pi-gps-detail');
|
||||
if (!liveBadge || !fixDetail || !healthStatus || !healthDetail) return;
|
||||
|
||||
let label = 'Unavailable';
|
||||
let badgeClass = 'text-bg-secondary';
|
||||
let detail = 'GPS thread has not started.';
|
||||
|
||||
if (status.thread_alive) {
|
||||
if (status.has_fix) {
|
||||
const stale = status.fix_age_seconds != null && status.fix_age_seconds > 30;
|
||||
label = stale ? 'Stale fix' : 'Live fix';
|
||||
badgeClass = stale ? 'text-bg-warning' : 'text-bg-success';
|
||||
detail = formatGpsAge(status.fix_age_seconds);
|
||||
} else {
|
||||
label = 'No fix';
|
||||
badgeClass = 'text-bg-warning';
|
||||
detail = 'GNSS thread is running, but no fix is available yet.';
|
||||
}
|
||||
} else {
|
||||
label = 'Offline';
|
||||
badgeClass = 'text-bg-danger';
|
||||
}
|
||||
|
||||
liveBadge.className = `badge ${badgeClass}`;
|
||||
liveBadge.textContent = label;
|
||||
fixDetail.textContent = detail;
|
||||
healthStatus.textContent = label;
|
||||
healthStatus.className = `h6 fw-bold mb-0 ${label === 'Live fix' ? 'text-success' : label === 'Offline' ? 'text-danger' : 'text-warning'}`;
|
||||
healthDetail.textContent = detail;
|
||||
}
|
||||
|
||||
async function refreshGpsStatus() {
|
||||
const response = await fetch('/api/gps/status');
|
||||
const status = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(status.error || 'Could not load GPS status.');
|
||||
}
|
||||
renderGpsStatus(status);
|
||||
}
|
||||
|
||||
socket.on('gps_location', function(data) {
|
||||
const LocationX = document.getElementById('gpsLocationX');
|
||||
const LocationY = document.getElementById('gpsLocationY');
|
||||
@@ -218,6 +271,7 @@ socket.on('gps_location', function(data) {
|
||||
const lat = parseFloat(data[0]);
|
||||
const lon = parseFloat(data[1]);
|
||||
if (lat != null && lon != null) {
|
||||
renderGpsStatus({thread_alive: true, has_fix: true, fix_age_seconds: 0});
|
||||
if (!map.hasLayer(mapTiles)) {
|
||||
mapTiles.addTo(map);
|
||||
}
|
||||
@@ -334,3 +388,19 @@ window.setInterval(function () {
|
||||
message.hidden = false;
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
refreshGpsStatus().catch(function (error) {
|
||||
renderGpsStatus({thread_alive: false, has_fix: false, fix_age_seconds: null});
|
||||
const fixDetail = document.getElementById('gps-fix-detail');
|
||||
const healthDetail = document.getElementById('pi-gps-detail');
|
||||
if (fixDetail) fixDetail.textContent = error.message;
|
||||
if (healthDetail) healthDetail.textContent = error.message;
|
||||
});
|
||||
window.setInterval(function () {
|
||||
refreshGpsStatus().catch(function (error) {
|
||||
const fixDetail = document.getElementById('gps-fix-detail');
|
||||
const healthDetail = document.getElementById('pi-gps-detail');
|
||||
if (fixDetail) fixDetail.textContent = error.message;
|
||||
if (healthDetail) healthDetail.textContent = error.message;
|
||||
});
|
||||
}, 15000);
|
||||
|
||||
Reference in New Issue
Block a user