GPS im Admin und offline verbesserungen
This commit is contained in:
@@ -12,17 +12,26 @@
|
||||
<body class="page-admin">
|
||||
|
||||
<header>
|
||||
<svg class="hdr-icon" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="7" y="11" width="18" height="10" rx="1" fill="#0088FF" opacity=".85"/>
|
||||
<rect x="11" y="7" width="10" height="4" rx="1" fill="#0088FF" opacity=".5"/>
|
||||
<circle cx="9" cy="23" r="3.5" fill="none" stroke="#0088FF" stroke-width="1.5"/>
|
||||
<circle cx="23" cy="23" r="3.5" fill="none" stroke="#0088FF" stroke-width="1.5"/>
|
||||
<circle cx="9" cy="23" r="1.3" fill="#0088FF"/>
|
||||
<circle cx="23" cy="23" r="1.3" fill="#0088FF"/>
|
||||
<rect x="22" y="8" width="4" height="3" rx=".5" fill="#FF6B00" opacity=".9"/>
|
||||
<line x1="16" y1="7" x2="16" y2="3" stroke="#0088FF" stroke-width="1.5"/>
|
||||
<circle cx="16" cy="2.5" r="1" fill="#0088FF"/>
|
||||
<rect x="3" y="14" width="4" height="2" rx="1" fill="#0088FF" opacity=".6"/>
|
||||
<svg class="hdr-icon" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<ellipse cx="14" cy="51" rx="6" ry="7" fill="#FFB21F"/>
|
||||
<ellipse cx="28" cy="50" rx="6" ry="8" fill="#FFB21F"/>
|
||||
<ellipse cx="43" cy="50" rx="6" ry="8" fill="#FFB21F"/>
|
||||
<ellipse cx="55" cy="49" rx="5" ry="7" fill="#FFB21F"/>
|
||||
<path d="M11 36L23 30H50L58 32L47 37H14L11 36Z" fill="#1F43C9"/>
|
||||
<rect x="24" y="14" width="7" height="23" rx="3.5" fill="#1B120F"/>
|
||||
<path d="M18 38L25 36L41 36L51 40L51 46L42 49L38 59L31 59L29 50L19 47L18 38Z" fill="#1B120F"/>
|
||||
<rect x="21" y="38" width="5" height="10" rx="2.5" fill="#1B120F"/>
|
||||
<rect x="43" y="39" width="5" height="11" rx="2.5" fill="#1B120F"/>
|
||||
<path d="M15 41L9 44L8 49" stroke="#1B120F" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M49 41L57 43L57 47" stroke="#1B120F" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M14 16C14 12 17 9 22 9H32C38 9 41 12 41 16C41 22 36 26 28 26H20C16 26 14 23 14 16Z" fill="#FFB21F"/>
|
||||
<circle cx="21" cy="15.5" r="3.2" fill="white"/>
|
||||
<circle cx="30.5" cy="14.5" r="3.6" fill="white"/>
|
||||
<circle cx="21.5" cy="17" r="1.8" fill="#000"/>
|
||||
<circle cx="30.8" cy="16" r="2.1" fill="#000"/>
|
||||
<path d="M25 18C25.8 19.2 27.2 19.2 28 18V20.2C28 21.8 26.8 22.7 25.5 22.7C24.2 22.7 23 21.8 23 20.2V18C23.6 18.8 24.4 19.1 25 18Z" fill="#1B120F"/>
|
||||
<path d="M12 12C13.5 9.2 17.8 8.2 21 10" stroke="#1B120F" stroke-width="2.8" stroke-linecap="round"/>
|
||||
<path d="M29 7.5C31.8 6.2 35.6 7.1 37.6 9.7" stroke="#1B120F" stroke-width="2.8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span class="hdr-title">CUNO <em>ADMIN</em></span>
|
||||
<div class="hdr-div"></div>
|
||||
@@ -30,6 +39,8 @@
|
||||
<div class="hdr-gap"></div>
|
||||
<span id="admin_status" class="hdr-status">Lade...</span>
|
||||
<div class="hdr-div"></div>
|
||||
<div class="mission-ok"><div class="pulse-dot"></div><span id="page-connection-label">Prüfe…</span></div>
|
||||
<div class="hdr-div"></div>
|
||||
<a class="hdr-back" href="./index.html">← Ground Control</a>
|
||||
<div class="hdr-div"></div>
|
||||
<div class="hdr-clk">
|
||||
@@ -37,12 +48,17 @@
|
||||
<div class="hdr-clk-val" id="utc-clock">--:--:--</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="connection-banner" class="connection-banner" hidden>
|
||||
<strong id="connection-banner-title">Verbindung wird geprüft</strong>
|
||||
<span id="connection-banner-detail">Warte auf ROS und Admin-API.</span>
|
||||
</div>
|
||||
|
||||
<div class="admin-shell">
|
||||
<div class="admin-section-tabs" role="tablist" aria-label="Admin-Bereiche">
|
||||
<button id="admin_tab_system" class="admin-section-tab is_active" role="tab" aria-selected="true" onclick="switchAdminSection('system')">Admin</button>
|
||||
<button id="admin_tab_drive" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('drive')">Geschwindigkeit / Weg</button>
|
||||
<button id="admin_tab_map" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('map')">Karte</button>
|
||||
<button id="admin_tab_gps" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('gps')">GPS</button>
|
||||
<button id="admin_tab_motor" class="admin-section-tab" role="tab" aria-selected="false" onclick="switchAdminSection('motor')">Motorentest</button>
|
||||
</div>
|
||||
|
||||
@@ -357,6 +373,64 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="admin_panel_gps" class="admin-section-panel" role="tabpanel">
|
||||
<div class="admin-section-grid admin-section-grid_gps">
|
||||
<div class="admin-col admin-col_gps_main">
|
||||
<div class="panel gps-panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">GNSS-Satelliten</span>
|
||||
<span class="p-tag">Himmelskarte</span>
|
||||
</div>
|
||||
<div class="copy-text gps-copy">
|
||||
Diese Ansicht zeigt die vom Empfänger gemeldeten Satelliten im Himmelsplot. Außen ist der Horizont, die Mitte ist der Zenit.
|
||||
</div>
|
||||
<div class="gps-sky-stage">
|
||||
<canvas id="gps_sky_canvas" class="gps-sky-canvas" width="560" height="560"></canvas>
|
||||
<div id="gps_satellite_tooltip" class="gps-satellite-tooltip" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-col admin-col_gps_side">
|
||||
<div class="panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">Fix-Diagnose</span>
|
||||
<span class="p-tag">GNSS-Diagnose</span>
|
||||
</div>
|
||||
<div class="sr"><span class="sr-led led-ok"></span><span class="sr-key">Status</span><span id="gps_diag_status" class="sr-val">Offline</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Fix-Qualität</span><span id="gps_diag_fix_quality" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Frame</span><span id="gps_diag_frame" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Breite</span><span id="gps_diag_lat" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Länge</span><span id="gps_diag_lon" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Höhe</span><span id="gps_diag_alt" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Geschwindigkeit</span><span id="gps_diag_speed" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Kurs</span><span id="gps_diag_track" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">UTC-Datum</span><span id="gps_diag_utc_date" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">UTC-Zeit</span><span id="gps_diag_utc_time" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Fix-Alter</span><span id="gps_diag_fix_age" class="sr-val">--</span></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="p-hdr">
|
||||
<div class="p-dot"></div>
|
||||
<span class="p-lbl">Genauigkeit</span>
|
||||
<span class="p-tag">Qualität</span>
|
||||
</div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Positionsgenauigkeit</span><span id="gps_diag_pdop" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Horizontale Genauigkeit</span><span id="gps_diag_hdop" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Vertikale Genauigkeit</span><span id="gps_diag_vdop" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Für Fix genutzt</span><span id="gps_diag_sat_used" class="sr-val">--</span></div>
|
||||
<div class="sr"><span class="sr-led led-blue"></span><span class="sr-key">Im Himmelsplot</span><span id="gps_diag_sat_visible" class="sr-val">--</span></div>
|
||||
<div class="copy-text gps-copy gps-copy_compact">
|
||||
Für eine gute Fix-Qualität sind niedrige DOP-Werte und hohe, gleichmäßig verteilte Satelliten sinnvoll. Der Himmelsplot zeigt nur Satelliten, die der Empfänger per GSV mit Richtung und Höhe meldet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="admin_panel_motor" class="admin-section-panel motor-admin-tab" role="tabpanel">
|
||||
<div class="admin-section-grid">
|
||||
<div class="admin-col">
|
||||
@@ -479,6 +553,15 @@
|
||||
var motorCommandsTopic = null;
|
||||
var gpsFixTopic = null;
|
||||
var gpsStatusTopic = null;
|
||||
var gpsDiagnosticsTopic = null;
|
||||
var pageConnection = {
|
||||
apiOk: false,
|
||||
apiLastAt: 0,
|
||||
rosOk: false,
|
||||
gpsLastAt: 0,
|
||||
lastHealthyAt: 0,
|
||||
state: 'offline'
|
||||
};
|
||||
|
||||
var isMotorTestRunning = false;
|
||||
var isContainerActionRunning = false;
|
||||
@@ -560,6 +643,29 @@
|
||||
hasCenteredOnFix: false,
|
||||
suppressMapClickUntil: 0
|
||||
};
|
||||
var gpsDiagnosticsState = {
|
||||
connected: false,
|
||||
frameId: 'gps',
|
||||
valid: false,
|
||||
fixQuality: 0,
|
||||
satellitesUsed: 0,
|
||||
satellitesVisible: 0,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
altitudeM: null,
|
||||
hdop: null,
|
||||
pdop: null,
|
||||
vdop: null,
|
||||
speedMps: null,
|
||||
speedKmh: null,
|
||||
trackDeg: null,
|
||||
utcDate: null,
|
||||
utcTime: null,
|
||||
lastFixAgeS: null,
|
||||
satellites: [],
|
||||
selectedSatellitePrn: null,
|
||||
plottedSatellites: []
|
||||
};
|
||||
|
||||
function submitPassword() {
|
||||
var input = document.getElementById('auth_input');
|
||||
@@ -578,6 +684,129 @@
|
||||
if (el) el.textContent = text;
|
||||
}
|
||||
|
||||
function clearAdminApiDisplay() {
|
||||
setText('info_ips', '--');
|
||||
setText('info_wifi', '--');
|
||||
setText('info_temp', '--');
|
||||
setText('info_uptime', '--');
|
||||
setText('info_disk', '--');
|
||||
setUndervoltageState('info_undervoltage', '--', 'unknown');
|
||||
setServiceState('service_camera', '-');
|
||||
setServiceState('service_admin_api', '-');
|
||||
setServiceState('service_exomy', '-');
|
||||
var bar = document.getElementById('bar-temp-adm');
|
||||
if (bar) bar.style.width = '0%';
|
||||
}
|
||||
|
||||
function clearAdminRosDisplay() {
|
||||
roverMapState.hasFix = false;
|
||||
roverMapState.roverLat = null;
|
||||
roverMapState.roverLon = null;
|
||||
roverMapState.roverAlt = null;
|
||||
roverMapState.accuracyMeters = null;
|
||||
roverMapState.lastFixAt = null;
|
||||
roverMapState.gpsStatusText = 'Offline';
|
||||
roverMapState.satellites = '--';
|
||||
updateMapTelemetryPanel();
|
||||
|
||||
setDriveEstimatorState('offline', 'state_inactive');
|
||||
setText('drive_estimator_speed', '--');
|
||||
setText('drive_estimator_distance', '--');
|
||||
setText('drive_estimator_pulse', '--');
|
||||
setText('drive_estimator_rpm', '--');
|
||||
setText('drive_estimator_mode', '--');
|
||||
setText('drive_estimator_direction', '--');
|
||||
gpsDiagnosticsState.connected = false;
|
||||
gpsDiagnosticsState.valid = false;
|
||||
gpsDiagnosticsState.fixQuality = 0;
|
||||
gpsDiagnosticsState.satellitesUsed = 0;
|
||||
gpsDiagnosticsState.satellitesVisible = 0;
|
||||
gpsDiagnosticsState.latitude = null;
|
||||
gpsDiagnosticsState.longitude = null;
|
||||
gpsDiagnosticsState.altitudeM = null;
|
||||
gpsDiagnosticsState.hdop = null;
|
||||
gpsDiagnosticsState.pdop = null;
|
||||
gpsDiagnosticsState.vdop = null;
|
||||
gpsDiagnosticsState.speedMps = null;
|
||||
gpsDiagnosticsState.speedKmh = null;
|
||||
gpsDiagnosticsState.trackDeg = null;
|
||||
gpsDiagnosticsState.utcDate = null;
|
||||
gpsDiagnosticsState.utcTime = null;
|
||||
gpsDiagnosticsState.lastFixAgeS = null;
|
||||
gpsDiagnosticsState.satellites = [];
|
||||
updateGpsDiagnosticsPanel();
|
||||
}
|
||||
|
||||
function formatConnectionTime(timestamp) {
|
||||
if (!timestamp) return '--:--:--';
|
||||
return new Date(timestamp).toLocaleTimeString('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function updatePageConnectionState() {
|
||||
var now = Date.now();
|
||||
var apiFresh = pageConnection.apiOk && (now - pageConnection.apiLastAt) < 20000;
|
||||
var rosFresh = !!pageConnection.rosOk;
|
||||
var gpsFresh = pageConnection.gpsLastAt && (now - pageConnection.gpsLastAt) < 15000;
|
||||
var state = 'offline';
|
||||
var title = 'Offline';
|
||||
var details = [];
|
||||
|
||||
if (apiFresh && rosFresh) {
|
||||
state = gpsFresh || !pageConnection.gpsLastAt ? 'online' : 'degraded';
|
||||
title = state === 'online' ? 'Online' : 'Eingeschränkt';
|
||||
} else if (apiFresh || rosFresh) {
|
||||
state = 'degraded';
|
||||
title = 'Eingeschränkt';
|
||||
}
|
||||
|
||||
if (!apiFresh) details.push('Admin-API nicht erreichbar');
|
||||
if (!rosFresh) details.push('ROSBridge getrennt');
|
||||
if (rosFresh && pageConnection.gpsLastAt && !gpsFresh) details.push('GPS-Daten veraltet');
|
||||
if (!details.length) details.push('Alle Live-Dienste antworten');
|
||||
if (state !== 'online' && pageConnection.lastHealthyAt) {
|
||||
details.push('letzter Kontakt ' + formatConnectionTime(pageConnection.lastHealthyAt));
|
||||
}
|
||||
|
||||
pageConnection.state = state;
|
||||
document.body.classList.toggle('page-connection-online', state === 'online');
|
||||
document.body.classList.toggle('page-connection-degraded', state === 'degraded');
|
||||
document.body.classList.toggle('page-connection-offline', state === 'offline');
|
||||
setText('page-connection-label', title);
|
||||
setText('connection-banner-title', title);
|
||||
setText('connection-banner-detail', details.join(' | '));
|
||||
|
||||
if (!apiFresh) clearAdminApiDisplay();
|
||||
if (!rosFresh) clearAdminRosDisplay();
|
||||
|
||||
var banner = document.getElementById('connection-banner');
|
||||
if (banner) banner.hidden = state === 'online';
|
||||
}
|
||||
|
||||
function markAdminApiConnection(ok) {
|
||||
pageConnection.apiOk = !!ok;
|
||||
if (ok) {
|
||||
pageConnection.apiLastAt = Date.now();
|
||||
pageConnection.lastHealthyAt = pageConnection.apiLastAt;
|
||||
}
|
||||
updatePageConnectionState();
|
||||
}
|
||||
|
||||
function markAdminRosConnection(ok) {
|
||||
pageConnection.rosOk = !!ok;
|
||||
if (ok) pageConnection.lastHealthyAt = Date.now();
|
||||
updatePageConnectionState();
|
||||
}
|
||||
|
||||
function markAdminGpsFresh() {
|
||||
pageConnection.gpsLastAt = Date.now();
|
||||
if (pageConnection.rosOk) pageConnection.lastHealthyAt = pageConnection.gpsLastAt;
|
||||
updatePageConnectionState();
|
||||
}
|
||||
|
||||
function setMotorTestStatus(text) {
|
||||
setAdminStatus(text);
|
||||
}
|
||||
@@ -587,26 +816,32 @@
|
||||
var systemTab = document.getElementById('admin_tab_system');
|
||||
var driveTab = document.getElementById('admin_tab_drive');
|
||||
var mapTab = document.getElementById('admin_tab_map');
|
||||
var gpsTab = document.getElementById('admin_tab_gps');
|
||||
var motorTab = document.getElementById('admin_tab_motor');
|
||||
var systemPanel = document.getElementById('admin_panel_system');
|
||||
var drivePanel = document.getElementById('admin_panel_drive');
|
||||
var mapPanel = document.getElementById('admin_panel_map');
|
||||
var gpsPanel = document.getElementById('admin_panel_gps');
|
||||
var motorPanel = document.getElementById('admin_panel_motor');
|
||||
var isSystem = sectionName === 'system';
|
||||
var isDrive = sectionName === 'drive';
|
||||
var isMap = sectionName === 'map';
|
||||
var isGps = sectionName === 'gps';
|
||||
var isMotor = sectionName === 'motor';
|
||||
systemTab.classList.toggle('is_active', isSystem);
|
||||
driveTab.classList.toggle('is_active', isDrive);
|
||||
mapTab.classList.toggle('is_active', isMap);
|
||||
gpsTab.classList.toggle('is_active', isGps);
|
||||
motorTab.classList.toggle('is_active', isMotor);
|
||||
systemTab.setAttribute('aria-selected', isSystem ? 'true' : 'false');
|
||||
driveTab.setAttribute('aria-selected', isDrive ? 'true' : 'false');
|
||||
mapTab.setAttribute('aria-selected', isMap ? 'true' : 'false');
|
||||
gpsTab.setAttribute('aria-selected', isGps ? 'true' : 'false');
|
||||
motorTab.setAttribute('aria-selected', isMotor ? 'true' : 'false');
|
||||
systemPanel.classList.toggle('is_active', isSystem);
|
||||
drivePanel.classList.toggle('is_active', isDrive);
|
||||
mapPanel.classList.toggle('is_active', isMap);
|
||||
gpsPanel.classList.toggle('is_active', isGps);
|
||||
motorPanel.classList.toggle('is_active', isMotor);
|
||||
window.location.hash = isSystem ? '' : ('#' + sectionName);
|
||||
if (isMap) {
|
||||
@@ -615,6 +850,9 @@
|
||||
if (roverMapState.map) roverMapState.map.invalidateSize();
|
||||
}, 80);
|
||||
}
|
||||
if (isGps) {
|
||||
window.setTimeout(drawGpsSkyPlot, 80);
|
||||
}
|
||||
if (isMotor) fetchMotorTestStatus();
|
||||
}
|
||||
|
||||
@@ -665,14 +903,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
function formatCoordinate(value) {
|
||||
function formatCoordinate(value, digits) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 5);
|
||||
return formatNumber(value, typeof digits === 'number' ? digits : 5);
|
||||
}
|
||||
|
||||
function formatAltitude(value) {
|
||||
function formatAltitude(value, digits) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 1) + ' m';
|
||||
return formatNumber(value, typeof digits === 'number' ? digits : 1) + ' m';
|
||||
}
|
||||
|
||||
function formatAccuracy(value) {
|
||||
@@ -680,6 +918,26 @@
|
||||
return formatNumber(value, 1) + ' m';
|
||||
}
|
||||
|
||||
function formatDop(value) {
|
||||
if (typeof value !== 'number' || isNaN(value) || value <= 0) return '--';
|
||||
return formatNumber(value, 2);
|
||||
}
|
||||
|
||||
function formatDegrees(value) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 1) + '°';
|
||||
}
|
||||
|
||||
function formatSpeedKmh(value) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 1) + ' km/h';
|
||||
}
|
||||
|
||||
function formatFixAge(value) {
|
||||
if (typeof value !== 'number' || isNaN(value)) return '--';
|
||||
return formatNumber(value, 1) + ' s';
|
||||
}
|
||||
|
||||
function formatFixTime(date) {
|
||||
if (!date) return '--';
|
||||
return date.toLocaleTimeString('de-DE', {
|
||||
@@ -689,6 +947,241 @@
|
||||
});
|
||||
}
|
||||
|
||||
function fixQualityLabel(value) {
|
||||
var labels = {
|
||||
0: 'Kein Fix',
|
||||
1: 'GPS Fix',
|
||||
2: 'DGPS Fix',
|
||||
4: 'RTK Fixed',
|
||||
5: 'RTK Float'
|
||||
};
|
||||
return labels[value] || String(value || 0);
|
||||
}
|
||||
|
||||
function describeDopQuality(value) {
|
||||
if (typeof value !== 'number' || isNaN(value) || value <= 0) {
|
||||
return { text: '--', stateClass: 'state_unknown' };
|
||||
}
|
||||
if (value < 1) {
|
||||
return { text: formatNumber(value, 2) + ' sehr gut', stateClass: 'state_active' };
|
||||
}
|
||||
if (value < 2) {
|
||||
return { text: formatNumber(value, 2) + ' gut', stateClass: 'state_active' };
|
||||
}
|
||||
if (value < 5) {
|
||||
return { text: formatNumber(value, 2) + ' brauchbar', stateClass: 'state_unknown' };
|
||||
}
|
||||
if (value < 10) {
|
||||
return { text: formatNumber(value, 2) + ' kritisch', stateClass: 'state_inactive' };
|
||||
}
|
||||
return { text: formatNumber(value, 2) + ' schlecht', stateClass: 'state_inactive' };
|
||||
}
|
||||
|
||||
function setGpsQualityState(id, value) {
|
||||
var node = document.getElementById(id);
|
||||
if (!node) return;
|
||||
var quality = describeDopQuality(value);
|
||||
node.textContent = quality.text;
|
||||
node.classList.remove('state_active', 'state_inactive', 'state_unknown');
|
||||
node.classList.add(quality.stateClass);
|
||||
}
|
||||
|
||||
function updateGpsDiagnosticsPanel() {
|
||||
setText('gps_diag_status', gpsDiagnosticsState.connected ? (gpsDiagnosticsState.valid ? 'Fix aktiv' : 'Empfänger online') : 'Offline');
|
||||
setText('gps_diag_fix_quality', fixQualityLabel(gpsDiagnosticsState.fixQuality));
|
||||
setText('gps_diag_frame', gpsDiagnosticsState.frameId || '--');
|
||||
setText('gps_diag_lat', formatCoordinate(gpsDiagnosticsState.latitude, 8));
|
||||
setText('gps_diag_lon', formatCoordinate(gpsDiagnosticsState.longitude, 8));
|
||||
setText('gps_diag_alt', formatAltitude(gpsDiagnosticsState.altitudeM, 4));
|
||||
setText('gps_diag_speed', formatSpeedKmh(gpsDiagnosticsState.speedKmh));
|
||||
setText('gps_diag_track', formatDegrees(gpsDiagnosticsState.trackDeg));
|
||||
setText('gps_diag_utc_date', gpsDiagnosticsState.utcDate || '--');
|
||||
setText('gps_diag_utc_time', gpsDiagnosticsState.utcTime || '--');
|
||||
setText('gps_diag_fix_age', formatFixAge(gpsDiagnosticsState.lastFixAgeS));
|
||||
setGpsQualityState('gps_diag_pdop', gpsDiagnosticsState.pdop);
|
||||
setGpsQualityState('gps_diag_hdop', gpsDiagnosticsState.hdop);
|
||||
setGpsQualityState('gps_diag_vdop', gpsDiagnosticsState.vdop);
|
||||
setText('gps_diag_sat_used', gpsDiagnosticsState.satellitesUsed);
|
||||
setText('gps_diag_sat_visible', gpsDiagnosticsState.satellitesVisible);
|
||||
drawGpsSkyPlot();
|
||||
}
|
||||
|
||||
function getSelectedSatellite() {
|
||||
if (!gpsDiagnosticsState.satellites.length) return null;
|
||||
var selected = gpsDiagnosticsState.satellites.find(function(item) {
|
||||
return item.prn === gpsDiagnosticsState.selectedSatellitePrn;
|
||||
});
|
||||
if (selected) return selected;
|
||||
gpsDiagnosticsState.selectedSatellitePrn = gpsDiagnosticsState.satellites[0].prn || null;
|
||||
return gpsDiagnosticsState.satellites[0];
|
||||
}
|
||||
|
||||
function hideGpsSatelliteTooltip() {
|
||||
var node = document.getElementById('gps_satellite_tooltip');
|
||||
if (!node) return;
|
||||
node.hidden = true;
|
||||
}
|
||||
|
||||
function renderGpsSatelliteTooltip(leftPx, topPx) {
|
||||
var node = document.getElementById('gps_satellite_tooltip');
|
||||
if (!node) return;
|
||||
var selected = getSelectedSatellite();
|
||||
if (!selected) {
|
||||
node.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
node.innerHTML =
|
||||
'<div class="gps-satellite-tooltip-title">' + (selected.prn || '--') + '</div>' +
|
||||
'<div class="gps-satellite-tooltip-row"><span>System</span><strong>' + (selected.constellation || '--') + '</strong></div>' +
|
||||
'<div class="gps-satellite-tooltip-row"><span>Richtung</span><strong>' + (typeof selected.azimuth === 'number' ? selected.azimuth + '°' : '--') + '</strong></div>' +
|
||||
'<div class="gps-satellite-tooltip-row"><span>Höhe</span><strong>' + (typeof selected.elevation === 'number' ? selected.elevation + '°' : '--') + '</strong></div>' +
|
||||
'<div class="gps-satellite-tooltip-row"><span>Signal</span><strong>' + (typeof selected.snr === 'number' ? selected.snr + ' dB' : '--') + '</strong></div>' +
|
||||
'<div class="gps-satellite-tooltip-row"><span>Qualität</span><strong>' + (typeof selected.snr === 'number' ? (selected.snr >= 35 ? 'sehr gut' : (selected.snr >= 20 ? 'gut' : 'schwach')) : 'unbekannt') + '</strong></div>';
|
||||
node.style.left = leftPx + 'px';
|
||||
node.style.top = topPx + 'px';
|
||||
node.hidden = false;
|
||||
}
|
||||
|
||||
function drawGpsSkyPlot() {
|
||||
var canvas = document.getElementById('gps_sky_canvas');
|
||||
if (!canvas || !canvas.getContext) return;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
var width = canvas.width;
|
||||
var height = canvas.height;
|
||||
var cx = width / 2;
|
||||
var cy = height / 2;
|
||||
var radius = Math.min(width, height) * 0.42;
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = '#07111c';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.strokeStyle = 'rgba(0,255,145,.18)';
|
||||
ctx.lineWidth = 1;
|
||||
[radius, radius * 0.66, radius * 0.33].forEach(function(ring) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, ring, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cy - radius);
|
||||
ctx.lineTo(cx, cy + radius);
|
||||
ctx.moveTo(cx - radius, cy);
|
||||
ctx.lineTo(cx + radius, cy);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = 'rgba(190,255,225,.84)';
|
||||
ctx.font = '16px Consolas';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('N', cx, cy - radius - 12);
|
||||
ctx.fillText('S', cx, cy + radius + 24);
|
||||
ctx.fillText('W', cx - radius - 18, cy + 6);
|
||||
ctx.fillText('E', cx + radius + 18, cy + 6);
|
||||
|
||||
ctx.fillStyle = 'rgba(150,255,210,.5)';
|
||||
ctx.font = '12px Consolas';
|
||||
ctx.fillText('30°', cx, cy - radius * 0.33 + 4);
|
||||
ctx.fillText('60°', cx, cy - radius * 0.66 + 4);
|
||||
ctx.fillText('90°', cx, cy + 4);
|
||||
|
||||
gpsDiagnosticsState.plottedSatellites = [];
|
||||
gpsDiagnosticsState.satellites.forEach(function(item) {
|
||||
if (typeof item.azimuth !== 'number' || typeof item.elevation !== 'number') return;
|
||||
var satRadius = radius * (1 - Math.max(0, Math.min(90, item.elevation)) / 90);
|
||||
var angle = (item.azimuth - 90) * Math.PI / 180.0;
|
||||
var x = cx + Math.cos(angle) * satRadius;
|
||||
var y = cy + Math.sin(angle) * satRadius;
|
||||
var snr = typeof item.snr === 'number' ? item.snr : 0;
|
||||
var isSelected = item.prn === gpsDiagnosticsState.selectedSatellitePrn;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = snr >= 35 ? '#00ff91' : (snr >= 20 ? '#ffb300' : '#ff6b6b');
|
||||
ctx.arc(x, y, isSelected ? 13 : 10, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = isSelected ? '#ffffff' : 'rgba(5,17,29,.9)';
|
||||
ctx.lineWidth = isSelected ? 3 : 2;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#041017';
|
||||
ctx.font = 'bold 10px Consolas';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(String(item.prn || '?'), x, y + 3);
|
||||
|
||||
gpsDiagnosticsState.plottedSatellites.push({
|
||||
prn: item.prn,
|
||||
x: x,
|
||||
y: y,
|
||||
radius: isSelected ? 13 : 10
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onGpsSkyCanvasClick(event) {
|
||||
var canvas = document.getElementById('gps_sky_canvas');
|
||||
if (!canvas) return;
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var scaleX = canvas.width / rect.width;
|
||||
var scaleY = canvas.height / rect.height;
|
||||
var x = (event.clientX - rect.left) * scaleX;
|
||||
var y = (event.clientY - rect.top) * scaleY;
|
||||
var selectedPrn = null;
|
||||
|
||||
gpsDiagnosticsState.plottedSatellites.forEach(function(item) {
|
||||
var dx = x - item.x;
|
||||
var dy = y - item.y;
|
||||
if ((dx * dx + dy * dy) <= (item.radius + 6) * (item.radius + 6)) {
|
||||
selectedPrn = item.prn;
|
||||
}
|
||||
});
|
||||
|
||||
if (!selectedPrn) {
|
||||
hideGpsSatelliteTooltip();
|
||||
return;
|
||||
}
|
||||
gpsDiagnosticsState.selectedSatellitePrn = selectedPrn;
|
||||
drawGpsSkyPlot();
|
||||
var tooltipLeft = Math.min(rect.width - 18, (event.clientX - rect.left) + 18);
|
||||
var tooltipTop = Math.max(18, (event.clientY - rect.top) - 18);
|
||||
renderGpsSatelliteTooltip(tooltipLeft, tooltipTop);
|
||||
}
|
||||
|
||||
function handleGpsDiagnostics(message) {
|
||||
if (!message || !message.data) return;
|
||||
try {
|
||||
var data = JSON.parse(message.data);
|
||||
gpsDiagnosticsState.connected = !!data.connected;
|
||||
gpsDiagnosticsState.frameId = data.frame_id || 'gps';
|
||||
gpsDiagnosticsState.valid = !!data.valid;
|
||||
gpsDiagnosticsState.fixQuality = Number(data.fix_quality || 0);
|
||||
gpsDiagnosticsState.satellitesUsed = Number(data.satellites_used || 0);
|
||||
gpsDiagnosticsState.satellitesVisible = Number(data.satellites_visible || 0);
|
||||
gpsDiagnosticsState.latitude = typeof data.latitude === 'number' ? data.latitude : null;
|
||||
gpsDiagnosticsState.longitude = typeof data.longitude === 'number' ? data.longitude : null;
|
||||
gpsDiagnosticsState.altitudeM = typeof data.altitude_m === 'number' ? data.altitude_m : null;
|
||||
gpsDiagnosticsState.hdop = typeof data.hdop === 'number' ? data.hdop : null;
|
||||
gpsDiagnosticsState.pdop = typeof data.pdop === 'number' ? data.pdop : null;
|
||||
gpsDiagnosticsState.vdop = typeof data.vdop === 'number' ? data.vdop : null;
|
||||
gpsDiagnosticsState.speedMps = typeof data.speed_mps === 'number' ? data.speed_mps : null;
|
||||
gpsDiagnosticsState.speedKmh = typeof data.speed_kmh === 'number' ? data.speed_kmh : null;
|
||||
gpsDiagnosticsState.trackDeg = typeof data.track_deg === 'number' ? data.track_deg : null;
|
||||
gpsDiagnosticsState.utcDate = data.utc_date || null;
|
||||
gpsDiagnosticsState.utcTime = data.utc_time || null;
|
||||
gpsDiagnosticsState.lastFixAgeS = typeof data.last_fix_age_s === 'number' ? data.last_fix_age_s : null;
|
||||
gpsDiagnosticsState.satellites = Array.isArray(data.satellites) ? data.satellites : [];
|
||||
if (gpsDiagnosticsState.selectedSatellitePrn) {
|
||||
var stillExists = gpsDiagnosticsState.satellites.some(function(item) {
|
||||
return item.prn === gpsDiagnosticsState.selectedSatellitePrn;
|
||||
});
|
||||
if (!stillExists) gpsDiagnosticsState.selectedSatellitePrn = null;
|
||||
}
|
||||
updateGpsDiagnosticsPanel();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
function updateMapTelemetryPanel() {
|
||||
setText('map_gps_status', roverMapState.gpsStatusText || 'Offline');
|
||||
setText('map_gps_satellites', roverMapState.satellites);
|
||||
@@ -1113,6 +1606,7 @@
|
||||
}
|
||||
|
||||
function updateGpsStatusSummary(text) {
|
||||
markAdminGpsFresh();
|
||||
roverMapState.gpsStatusText = text || 'Offline';
|
||||
var satMatch = String(roverMapState.gpsStatusText).match(/(\d+)\s*Sat/i);
|
||||
roverMapState.satellites = satMatch ? satMatch[1] : '--';
|
||||
@@ -1121,6 +1615,7 @@
|
||||
|
||||
function handleGpsFix(message) {
|
||||
if (!message || !message.status) return;
|
||||
markAdminGpsFresh();
|
||||
|
||||
roverMapState.hasFix = Number(message.status.status) >= 0;
|
||||
if (!roverMapState.hasFix) {
|
||||
@@ -1450,6 +1945,15 @@
|
||||
}
|
||||
|
||||
function renderDriveEstimator() {
|
||||
if (!pageConnection.rosOk) {
|
||||
setText('drive_estimator_speed', '--');
|
||||
setText('drive_estimator_distance', '--');
|
||||
setText('drive_estimator_pulse', '--');
|
||||
setText('drive_estimator_rpm', '--');
|
||||
setText('drive_estimator_mode', '--');
|
||||
setText('drive_estimator_direction', '--');
|
||||
return;
|
||||
}
|
||||
updateDriveEstimatorModel();
|
||||
setText('drive_estimator_speed', formatSpeedCms(driveEstimator.speedMps, 1));
|
||||
setText('drive_estimator_distance', formatNumber(driveEstimator.distanceM, 2));
|
||||
@@ -1572,9 +2076,11 @@
|
||||
});
|
||||
|
||||
ros.on('connection', function() {
|
||||
markAdminRosConnection(true);
|
||||
setDriveEstimatorState('verbunden', 'state_active');
|
||||
});
|
||||
ros.on('close', function() {
|
||||
markAdminRosConnection(false);
|
||||
setDriveEstimatorState('getrennt', 'state_inactive');
|
||||
ros = null;
|
||||
roverCommandTopic = null;
|
||||
@@ -1582,6 +2088,7 @@
|
||||
window.setTimeout(connectDriveEstimatorRos, 3000);
|
||||
});
|
||||
ros.on('error', function() {
|
||||
markAdminRosConnection(false);
|
||||
setDriveEstimatorState('Fehler', 'state_unknown');
|
||||
});
|
||||
|
||||
@@ -1621,6 +2128,13 @@
|
||||
gpsStatusTopic.subscribe(function(message) {
|
||||
updateGpsStatusSummary(message.data);
|
||||
});
|
||||
|
||||
gpsDiagnosticsTopic = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: '/gps/diagnostics_json',
|
||||
messageType: 'std_msgs/String'
|
||||
});
|
||||
gpsDiagnosticsTopic.subscribe(handleGpsDiagnostics);
|
||||
}
|
||||
|
||||
function temperatureToWidth(value) {
|
||||
@@ -1649,8 +2163,10 @@
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/status');
|
||||
if (!response.ok) throw new Error('status ' + response.status);
|
||||
markAdminApiConnection(true);
|
||||
renderStatus(await response.json());
|
||||
} catch (e) {
|
||||
markAdminApiConnection(false);
|
||||
setAdminStatus('API nicht erreichbar');
|
||||
}
|
||||
}
|
||||
@@ -2116,18 +2632,23 @@
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var input = document.getElementById('auth_input');
|
||||
var gpsCanvas = document.getElementById('gps_sky_canvas');
|
||||
input.focus();
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') submitPassword();
|
||||
});
|
||||
if (gpsCanvas) gpsCanvas.addEventListener('click', onGpsSkyCanvasClick);
|
||||
if (window.location.hash === '#motor') {
|
||||
switchAdminSection('motor');
|
||||
} else if (window.location.hash === '#drive') {
|
||||
switchAdminSection('drive');
|
||||
} else if (window.location.hash === '#gps') {
|
||||
switchAdminSection('gps');
|
||||
} else if (window.location.hash === '#map') {
|
||||
switchAdminSection('map');
|
||||
}
|
||||
updateClock();
|
||||
updatePageConnectionState();
|
||||
window.setInterval(updateClock, 1000);
|
||||
fetchStatus();
|
||||
fetchDelay();
|
||||
@@ -2140,11 +2661,13 @@
|
||||
renderWaypointList();
|
||||
applyWaypointPlanningState();
|
||||
renderDriveEstimator();
|
||||
updateGpsDiagnosticsPanel();
|
||||
window.setInterval(fetchStatus, 10000);
|
||||
window.setInterval(fetchDelay, 5000);
|
||||
window.setInterval(fetchSpeedLimit, 5000);
|
||||
window.setInterval(fetchMotorTestStatus, 4000);
|
||||
window.setInterval(renderDriveEstimator, 250);
|
||||
window.setInterval(updatePageConnectionState, 1000);
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function(event) {
|
||||
|
||||
Reference in New Issue
Block a user