Alles verbessert
This commit is contained in:
@@ -138,7 +138,7 @@
|
||||
</div>
|
||||
|
||||
<p class="admin_copy motor_test_neutral_copy">
|
||||
Das entspricht [config_drive_motor_neutral.py]: ein gemeinsamer `drive_pwm_neutral`-Wert für alle Fahrmotoren. Mit `-` und `+` sendest du den Stillstands-PWM direkt an alle Antriebe, bis wirklich alles steht.
|
||||
Mit `-` und `+` stellst du den Stillstands-PWM jetzt pro Fahrmotor einzeln ein. `Speichern` schreibt alle sechs Werte dauerhaft in die `exomy.yaml`.
|
||||
</p>
|
||||
|
||||
<div class="motor_test_neutral_toolbar">
|
||||
@@ -146,22 +146,7 @@
|
||||
<button id="drive_neutral_save_button" class="button buttonA" onclick="saveDriveNeutralValue()">Speichern</button>
|
||||
</div>
|
||||
|
||||
<article class="drive_neutral_card" id="drive_neutral_card">
|
||||
<div class="steering_neutral_card_header">
|
||||
<span class="info_label">Drive PWM Neutral</span>
|
||||
<strong>Alle Fahrmotoren</strong>
|
||||
</div>
|
||||
|
||||
<div class="steering_neutral_value_row drive_neutral_value_row">
|
||||
<button class="button buttonWarm steering_neutral_adjust" onclick="adjustDriveNeutralValue(-5)">-</button>
|
||||
<strong id="drive_neutral_value" class="steering_neutral_value">-</strong>
|
||||
<button class="button buttonA steering_neutral_adjust" onclick="adjustDriveNeutralValue(5)">+</button>
|
||||
</div>
|
||||
|
||||
<div class="steering_neutral_meta">
|
||||
<span id="drive_neutral_pins">Pins: -</span>
|
||||
</div>
|
||||
</article>
|
||||
<div id="drive_neutral_grid" class="steering_neutral_grid"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -188,9 +173,8 @@
|
||||
var activeMotorTestTab = 'impulse';
|
||||
var steeringNeutralValues = {};
|
||||
var steeringNeutralSavedValues = {};
|
||||
var driveNeutralValue = null;
|
||||
var driveNeutralSavedValue = null;
|
||||
var driveNeutralPins = {};
|
||||
var driveNeutralValues = {};
|
||||
var driveNeutralSavedValues = {};
|
||||
var STEERING_NEUTRAL_STEP = 5;
|
||||
var WHEEL_ORDER = ['fl', 'fr', 'cl', 'cr', 'rl', 'rr'];
|
||||
|
||||
@@ -288,7 +272,7 @@
|
||||
loadSteeringNeutralValues();
|
||||
}
|
||||
|
||||
if (isDriveNeutral && driveNeutralValue === null) {
|
||||
if (isDriveNeutral && !Object.keys(driveNeutralValues).length) {
|
||||
loadDriveNeutralValue();
|
||||
}
|
||||
}
|
||||
@@ -348,38 +332,59 @@
|
||||
button.disabled = isNeutralLoading || isNeutralPreviewRunning || isNeutralSaving || !hasUnsavedNeutralChanges();
|
||||
}
|
||||
|
||||
function renderDriveNeutralCard() {
|
||||
var valueNode = document.getElementById('drive_neutral_value');
|
||||
var pinsNode = document.getElementById('drive_neutral_pins');
|
||||
var card = document.getElementById('drive_neutral_card');
|
||||
|
||||
if (valueNode) {
|
||||
valueNode.textContent = driveNeutralValue === null ? '-' : String(driveNeutralValue);
|
||||
function renderDriveNeutralCards() {
|
||||
var grid = document.getElementById('drive_neutral_grid');
|
||||
if (!grid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pinsNode) {
|
||||
var pinValues = WHEEL_ORDER
|
||||
.filter(function (wheel) { return Object.prototype.hasOwnProperty.call(driveNeutralPins, wheel); })
|
||||
.map(function (wheel) { return wheel.toUpperCase() + ':' + driveNeutralPins[wheel]; });
|
||||
pinsNode.textContent = pinValues.length ? 'Pins: ' + pinValues.join(' ') : 'Pins: -';
|
||||
}
|
||||
var html = [];
|
||||
WHEEL_ORDER.forEach(function (wheel) {
|
||||
var entry = driveNeutralValues[wheel];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (card) {
|
||||
var isDirty = driveNeutralValue !== null && driveNeutralSavedValue !== null && Number(driveNeutralValue) !== Number(driveNeutralSavedValue);
|
||||
card.classList.toggle('is_dirty', isDirty);
|
||||
}
|
||||
var savedEntry = driveNeutralSavedValues[wheel];
|
||||
var isDirty = !savedEntry || Number(savedEntry.value) !== Number(entry.value);
|
||||
html.push(
|
||||
'<article class="steering_neutral_card steering_neutral_card_compact' + (isDirty ? ' is_dirty' : '') + '">' +
|
||||
'<div class="steering_neutral_compact_row">' +
|
||||
'<div class="steering_neutral_card_header steering_neutral_card_header_compact">' +
|
||||
'<strong class="steering_neutral_title">' + entry.wheel_label + '</strong>' +
|
||||
'</div>' +
|
||||
'<div class="steering_neutral_value_row steering_neutral_value_row_compact">' +
|
||||
'<button class="button buttonWarm steering_neutral_adjust" onclick="adjustDriveNeutralValue(\'' + wheel + '\', -' + STEERING_NEUTRAL_STEP + ')">-</button>' +
|
||||
'<strong class="steering_neutral_value">' + entry.value + '</strong>' +
|
||||
'<button class="button buttonA steering_neutral_adjust" onclick="adjustDriveNeutralValue(\'' + wheel + '\', ' + STEERING_NEUTRAL_STEP + ')">+</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</article>'
|
||||
);
|
||||
});
|
||||
|
||||
grid.innerHTML = html.join('');
|
||||
updateDriveNeutralSaveButton();
|
||||
}
|
||||
|
||||
function hasUnsavedDriveNeutralChanges() {
|
||||
return WHEEL_ORDER.some(function (wheel) {
|
||||
var currentEntry = driveNeutralValues[wheel];
|
||||
var savedEntry = driveNeutralSavedValues[wheel];
|
||||
if (!currentEntry || !savedEntry) {
|
||||
return false;
|
||||
}
|
||||
return Number(currentEntry.value) !== Number(savedEntry.value);
|
||||
});
|
||||
}
|
||||
|
||||
function updateDriveNeutralSaveButton() {
|
||||
var button = document.getElementById('drive_neutral_save_button');
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hasChanges = driveNeutralValue !== null && driveNeutralSavedValue !== null && Number(driveNeutralValue) !== Number(driveNeutralSavedValue);
|
||||
button.disabled = isDriveNeutralLoading || isDriveNeutralPreviewRunning || isDriveNeutralSaving || !hasChanges;
|
||||
button.disabled = isDriveNeutralLoading || isDriveNeutralPreviewRunning || isDriveNeutralSaving || !hasUnsavedDriveNeutralChanges();
|
||||
}
|
||||
|
||||
async function fetchMotorTestStatus() {
|
||||
@@ -606,7 +611,7 @@
|
||||
|
||||
isDriveNeutralLoading = true;
|
||||
updateDriveNeutralSaveButton();
|
||||
setMotorTestStatus('Lade Fahr-Neutralwert...');
|
||||
setMotorTestStatus('Lade Fahr-Neutralwerte...');
|
||||
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/motor-test/drive-neutral');
|
||||
@@ -616,37 +621,44 @@
|
||||
throw new Error(data.status || ('status ' + response.status));
|
||||
}
|
||||
|
||||
driveNeutralValue = Number(data.drive_neutral && data.drive_neutral.value);
|
||||
driveNeutralSavedValue = driveNeutralValue;
|
||||
driveNeutralPins = data.drive_neutral && data.drive_neutral.pins ? data.drive_neutral.pins : {};
|
||||
renderDriveNeutralCard();
|
||||
driveNeutralValues = data.drive_neutral && data.drive_neutral.values ? data.drive_neutral.values : {};
|
||||
driveNeutralSavedValues = JSON.parse(JSON.stringify(driveNeutralValues));
|
||||
renderDriveNeutralCards();
|
||||
renderContainerStatus(data);
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwert geladen');
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwerte geladen');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setMotorTestStatus('Fehler');
|
||||
alert(error.message || 'Der Fahr-Neutralwert konnte nicht geladen werden.');
|
||||
alert(error.message || 'Die Fahr-Neutralwerte konnten nicht geladen werden.');
|
||||
} finally {
|
||||
isDriveNeutralLoading = false;
|
||||
updateDriveNeutralSaveButton();
|
||||
}
|
||||
}
|
||||
|
||||
async function adjustDriveNeutralValue(delta) {
|
||||
async function adjustDriveNeutralValue(wheel, delta) {
|
||||
if (isContainerActionRunning || isMotorTestRunning || isNeutralLoading || isNeutralPreviewRunning || isNeutralSaving || isDriveNeutralLoading || isDriveNeutralPreviewRunning || isDriveNeutralSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (driveNeutralValue === null) {
|
||||
var entry = driveNeutralValues[wheel];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
driveNeutralValue = Math.max(0, Math.min(4095, Number(driveNeutralValue) + Number(delta)));
|
||||
renderDriveNeutralCard();
|
||||
entry.value = Math.max(0, Math.min(4095, Number(entry.value) + Number(delta)));
|
||||
renderDriveNeutralCards();
|
||||
|
||||
isDriveNeutralPreviewRunning = true;
|
||||
updateDriveNeutralSaveButton();
|
||||
setMotorTestStatus('Fahre Fahr-Neutralwert an...');
|
||||
setMotorTestStatus('Fahre Fahr-Neutralwerte an...');
|
||||
|
||||
var values = {};
|
||||
WHEEL_ORDER.forEach(function (wheelKey) {
|
||||
if (driveNeutralValues[wheelKey]) {
|
||||
values[wheelKey] = Number(driveNeutralValues[wheelKey].value);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/motor-test/drive-neutral/preview', {
|
||||
@@ -655,7 +667,7 @@
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
value: driveNeutralValue
|
||||
values: values
|
||||
})
|
||||
});
|
||||
var data = await response.json();
|
||||
@@ -664,10 +676,10 @@
|
||||
throw new Error(data.status || ('status ' + response.status));
|
||||
}
|
||||
|
||||
driveNeutralPins = data.drive_neutral && data.drive_neutral.pins ? data.drive_neutral.pins : driveNeutralPins;
|
||||
renderDriveNeutralCard();
|
||||
driveNeutralValues = data.drive_neutral && data.drive_neutral.values ? data.drive_neutral.values : driveNeutralValues;
|
||||
renderDriveNeutralCards();
|
||||
renderContainerStatus(data);
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwert angefahren');
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwerte angefahren');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setMotorTestStatus('Fehler');
|
||||
@@ -683,13 +695,21 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (driveNeutralValue === null || driveNeutralSavedValue === null || Number(driveNeutralValue) === Number(driveNeutralSavedValue)) {
|
||||
if (!hasUnsavedDriveNeutralChanges()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDriveNeutralSaving = true;
|
||||
updateDriveNeutralSaveButton();
|
||||
setMotorTestStatus('Speichere Fahr-Neutralwert...');
|
||||
setMotorTestStatus('Speichere Fahr-Neutralwerte...');
|
||||
|
||||
var values = {};
|
||||
WHEEL_ORDER.forEach(function (wheel) {
|
||||
var entry = driveNeutralValues[wheel];
|
||||
if (entry) {
|
||||
values[entry.key] = Number(entry.value);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
var response = await fetch(adminApiBase + '/api/motor-test/drive-neutral/save', {
|
||||
@@ -698,7 +718,7 @@
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
value: driveNeutralValue
|
||||
values: values
|
||||
})
|
||||
});
|
||||
var data = await response.json();
|
||||
@@ -707,16 +727,15 @@
|
||||
throw new Error(data.status || ('status ' + response.status));
|
||||
}
|
||||
|
||||
driveNeutralValue = Number(data.drive_neutral && data.drive_neutral.value);
|
||||
driveNeutralSavedValue = driveNeutralValue;
|
||||
driveNeutralPins = data.drive_neutral && data.drive_neutral.pins ? data.drive_neutral.pins : driveNeutralPins;
|
||||
renderDriveNeutralCard();
|
||||
driveNeutralValues = data.drive_neutral && data.drive_neutral.values ? data.drive_neutral.values : driveNeutralValues;
|
||||
driveNeutralSavedValues = JSON.parse(JSON.stringify(driveNeutralValues));
|
||||
renderDriveNeutralCards();
|
||||
renderContainerStatus(data);
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwert gespeichert');
|
||||
setMotorTestStatus(data.status || 'Fahr-Neutralwerte gespeichert');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setMotorTestStatus('Fehler');
|
||||
alert(error.message || 'Der Fahr-Neutralwert konnte nicht gespeichert werden.');
|
||||
alert(error.message || 'Die Fahr-Neutralwerte konnten nicht gespeichert werden.');
|
||||
} finally {
|
||||
isDriveNeutralSaving = false;
|
||||
updateDriveNeutralSaveButton();
|
||||
|
||||
@@ -89,6 +89,10 @@
|
||||
<span class="info_label">CPU-Temperatur</span>
|
||||
<strong id="info_temp">-</strong>
|
||||
</div>
|
||||
<div class="info_card">
|
||||
<span class="info_label">Unterspannung</span>
|
||||
<strong id="info_undervoltage" class="state_unknown">-</strong>
|
||||
</div>
|
||||
<div class="info_card">
|
||||
<span class="info_label">Uptime</span>
|
||||
<strong id="info_uptime">-</strong>
|
||||
@@ -145,12 +149,47 @@
|
||||
node.classList.add('state_unknown');
|
||||
}
|
||||
|
||||
function setUndervoltageState(id, text, state) {
|
||||
var node = document.getElementById(id);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.textContent = text || '-';
|
||||
node.classList.remove('state_active', 'state_inactive', 'state_unknown');
|
||||
|
||||
if (state === 'active') {
|
||||
node.classList.add('state_inactive');
|
||||
node.style.animation = 'statusBlink 0.9s step-end infinite';
|
||||
return;
|
||||
}
|
||||
|
||||
node.style.animation = '';
|
||||
|
||||
if (state === 'past') {
|
||||
node.classList.add('state_unknown');
|
||||
return;
|
||||
}
|
||||
|
||||
if (state === 'clear') {
|
||||
node.classList.add('state_active');
|
||||
return;
|
||||
}
|
||||
|
||||
node.classList.add('state_unknown');
|
||||
}
|
||||
|
||||
function renderStatus(data) {
|
||||
setAdminStatus(data.status || 'Bereit');
|
||||
|
||||
setText('info_ips', data.system && data.system.ips);
|
||||
setText('info_wifi', data.system && data.system.wifi);
|
||||
setText('info_temp', data.system && data.system.cpu_temperature);
|
||||
setUndervoltageState(
|
||||
'info_undervoltage',
|
||||
data.system && data.system.undervoltage,
|
||||
data.system && data.system.undervoltage_state
|
||||
);
|
||||
setText('info_uptime', data.system && data.system.uptime);
|
||||
setText('info_disk', data.system && data.system.disk_free);
|
||||
|
||||
|
||||
@@ -187,6 +187,12 @@ header {
|
||||
color: #fff;
|
||||
box-shadow: 0 0 24px rgba(0,136,255,.45), inset 0 0 16px rgba(255,255,255,.1);
|
||||
}
|
||||
@keyframes mode-flash {
|
||||
0% { box-shadow: 0 0 0px rgba(0,255,145,0), background-color: var(--pri); }
|
||||
30% { box-shadow: 0 0 28px rgba(0,255,145,.9), background-color: #00ff91; }
|
||||
100% { box-shadow: 0 0 24px rgba(0,136,255,.45), background-color: var(--pri); }
|
||||
}
|
||||
.dbtn.flash { animation: mode-flash .5s ease-out forwards; }
|
||||
|
||||
#cam-panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||
.cam-port {
|
||||
@@ -205,19 +211,6 @@ header {
|
||||
background: #05070d;
|
||||
z-index: 1;
|
||||
}
|
||||
.c-scanlines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 3px,
|
||||
rgba(0,0,0,.09) 3px,
|
||||
rgba(0,0,0,.09) 4px
|
||||
);
|
||||
}
|
||||
.c-vignette {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -230,42 +223,19 @@ header {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
.c-xhair::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: rgba(0,255,145,.45);
|
||||
transform: translateY(-50%);
|
||||
@keyframes xh-spin { to { transform: rotate(360deg); } }
|
||||
@keyframes xh-pulse {
|
||||
0% { transform: scale(1); opacity: .7; }
|
||||
100% { transform: scale(2.8); opacity: 0; }
|
||||
}
|
||||
.c-xhair::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: rgba(0,255,145,.45);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.xc {
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-color: rgba(0,255,145,.7);
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
.xc.tl { top: 0; left: 0; border-top-width: 1.5px; border-left-width: 1.5px; }
|
||||
.xc.tr { top: 0; right: 0; border-top-width: 1.5px; border-right-width: 1.5px; }
|
||||
.xc.bl { bottom: 0; left: 0; border-bottom-width: 1.5px; border-left-width: 1.5px; }
|
||||
.xc.br { bottom: 0; right: 0; border-bottom-width: 1.5px; border-right-width: 1.5px; }
|
||||
.xh-rot { animation: xh-spin 14s linear infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.xh-rot-rev { animation: xh-spin 8s linear infinite reverse; transform-box: fill-box; transform-origin: center; }
|
||||
.xh-pulse { animation: xh-pulse 2.5s ease-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.c-rec {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
@@ -328,11 +298,20 @@ header {
|
||||
.sval.g { color: var(--ok); }
|
||||
.sval.o { color: var(--sec); }
|
||||
.sval.b { color: var(--pri); }
|
||||
.sval.alert {
|
||||
color: #D50000;
|
||||
animation: uv-blink 0.9s step-end infinite;
|
||||
}
|
||||
.sval.warntext { color: var(--sec); }
|
||||
.sval-conn { display: flex; align-items: center; gap: 5px; color: var(--ok); font-size: 10px; }
|
||||
.sbar-wrap { display: flex; align-items: center; gap: 8px; }
|
||||
.sbar { width: 52px; height: 3px; background: rgba(0,136,255,.12); border-radius: 1px; overflow: hidden; }
|
||||
.sbar-i { height: 100%; border-radius: 1px; background: var(--pri); }
|
||||
.sbar-i.w { background: var(--sec); }
|
||||
@keyframes uv-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
#joy-panel .p-hdr { padding: 14px 12px; }
|
||||
.mode-toggle { display: flex; gap: 2px; margin-left: auto; }
|
||||
@@ -563,10 +542,41 @@ header {
|
||||
STREAM: 8081
|
||||
</div>
|
||||
<div class="c-xhair">
|
||||
<div class="xc tl"></div><div class="xc tr"></div>
|
||||
<div class="xc bl"></div><div class="xc br"></div>
|
||||
<svg viewBox="-90 -90 180 180" xmlns="http://www.w3.org/2000/svg" width="180" height="180">
|
||||
<!-- äußerer rotierender Strichelring -->
|
||||
<circle class="xh-rot" cx="0" cy="0" r="82" fill="none" stroke="rgba(0,255,145,.2)" stroke-width="1" stroke-dasharray="4 10"/>
|
||||
<!-- mittlerer Ring, Gegenrichtung -->
|
||||
<circle class="xh-rot-rev" cx="0" cy="0" r="60" fill="none" stroke="rgba(0,255,145,.15)" stroke-width=".75" stroke-dasharray="2 16"/>
|
||||
<!-- innerer fester Ring -->
|
||||
<circle cx="0" cy="0" r="16" fill="none" stroke="rgba(0,255,145,.3)" stroke-width=".75"/>
|
||||
<!-- Kreuzlinien mit Lücke in der Mitte -->
|
||||
<line x1="-86" y1="0" x2="-19" y2="0" stroke="rgba(0,255,145,.6)" stroke-width="1"/>
|
||||
<line x1="19" y1="0" x2="86" y2="0" stroke="rgba(0,255,145,.6)" stroke-width="1"/>
|
||||
<line x1="0" y1="-86" x2="0" y2="-19" stroke="rgba(0,255,145,.6)" stroke-width="1"/>
|
||||
<line x1="0" y1="19" x2="0" y2="86" stroke="rgba(0,255,145,.6)" stroke-width="1"/>
|
||||
<!-- Skalenstriche bei r=60 -->
|
||||
<line x1="-60" y1="-6" x2="-60" y2="6" stroke="rgba(0,255,145,.45)" stroke-width=".75"/>
|
||||
<line x1="60" y1="-6" x2="60" y2="6" stroke="rgba(0,255,145,.45)" stroke-width=".75"/>
|
||||
<line x1="-6" y1="-60" x2="6" y2="-60" stroke="rgba(0,255,145,.45)" stroke-width=".75"/>
|
||||
<line x1="-6" y1="60" x2="6" y2="60" stroke="rgba(0,255,145,.45)" stroke-width=".75"/>
|
||||
<!-- Skalenstriche bei r=38 -->
|
||||
<line x1="-38" y1="-3.5" x2="-38" y2="3.5" stroke="rgba(0,255,145,.3)" stroke-width=".5"/>
|
||||
<line x1="38" y1="-3.5" x2="38" y2="3.5" stroke="rgba(0,255,145,.3)" stroke-width=".5"/>
|
||||
<line x1="-3.5" y1="-38" x2="3.5" y2="-38" stroke="rgba(0,255,145,.3)" stroke-width=".5"/>
|
||||
<line x1="-3.5" y1="38" x2="3.5" y2="38" stroke="rgba(0,255,145,.3)" stroke-width=".5"/>
|
||||
<!-- Eckklammern -->
|
||||
<path d="M-74,-58 L-74,-74 L-58,-74" fill="none" stroke="rgba(0,255,145,.75)" stroke-width="1.5"/>
|
||||
<path d="M58,-74 L74,-74 L74,-58" fill="none" stroke="rgba(0,255,145,.75)" stroke-width="1.5"/>
|
||||
<path d="M-74,58 L-74,74 L-58,74" fill="none" stroke="rgba(0,255,145,.75)" stroke-width="1.5"/>
|
||||
<path d="M58,74 L74,74 L74,58" fill="none" stroke="rgba(0,255,145,.75)" stroke-width="1.5"/>
|
||||
<!-- pulsierender Ring -->
|
||||
<circle class="xh-pulse" cx="0" cy="0" r="6" fill="none" stroke="rgba(0,255,145,.7)" stroke-width="1"/>
|
||||
<!-- Mittelpunkt -->
|
||||
<circle cx="0" cy="0" r="2.5" fill="rgba(0,255,145,.9)"/>
|
||||
<circle cx="0" cy="0" r="1" fill="rgba(255,255,255,.7)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="c-scanlines"></div>
|
||||
|
||||
<div class="c-vignette"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -633,6 +643,10 @@ header {
|
||||
<span class="slbl">IP Adressen</span>
|
||||
<span class="sval b" id="stat-ips">--</span>
|
||||
</div>
|
||||
<div class="srow warn">
|
||||
<span class="slbl">Unterspannung</span>
|
||||
<span class="sval b" id="stat-undervoltage">--</span>
|
||||
</div>
|
||||
<div class="srow info">
|
||||
<span class="slbl">Uptime Pi</span>
|
||||
<span class="sval b" id="stat-uptime">--</span>
|
||||
@@ -704,6 +718,7 @@ header {
|
||||
var hostUrl = window.location.hostname;
|
||||
var ros = null;
|
||||
var joyListener = null;
|
||||
var roverCommandListener = null;
|
||||
var publishTimer = null;
|
||||
var statusTimer = null;
|
||||
var axes = [0, 0, 0, 0, 0, 0];
|
||||
@@ -771,6 +786,45 @@ function updateModeDisplay() {
|
||||
setText("stat-mode", label);
|
||||
}
|
||||
|
||||
function applyDriveMode(mode, flash) {
|
||||
var changed = mode !== driveMode;
|
||||
driveMode = mode;
|
||||
document.querySelectorAll(".dbtn[data-mode]").forEach(function (btn) {
|
||||
var isActive = btn.dataset.mode === mode;
|
||||
btn.classList.toggle("active", isActive);
|
||||
if (isActive && changed && flash) {
|
||||
btn.classList.remove("flash");
|
||||
void btn.offsetWidth;
|
||||
btn.classList.add("flash");
|
||||
}
|
||||
});
|
||||
updateModeDisplay();
|
||||
updateControlLocks();
|
||||
}
|
||||
|
||||
function mirrorJoyThumb(x, y) {
|
||||
if (dragging || inputMode !== "joystick") return;
|
||||
var data = joyMetrics();
|
||||
var dx = x * data.maxD;
|
||||
var dy = -y * data.maxD;
|
||||
var thumb = document.getElementById("jthumb");
|
||||
thumb.style.transition = "left .05s, top .05s";
|
||||
thumb.style.left = "calc(50% + " + dx + "px)";
|
||||
thumb.style.top = "calc(50% + " + dy + "px)";
|
||||
thumb.style.transform = "translate(-50%,-50%)";
|
||||
updateAxesDisplay(x, y);
|
||||
}
|
||||
|
||||
function locomotionModeToUi(modeValue) {
|
||||
if (modeValue === 2) {
|
||||
return "turn-on-point";
|
||||
}
|
||||
if (modeValue === 3) {
|
||||
return "crab";
|
||||
}
|
||||
return "ackermann";
|
||||
}
|
||||
|
||||
function updateMotorDisplay() {
|
||||
setText("stat-motors", motorsEnabled ? "Aktiv" : "Aus");
|
||||
}
|
||||
@@ -787,11 +841,35 @@ function setRosStatus(text) {
|
||||
setText("mission-state", text === "Verbunden" ? "Nominal" : text);
|
||||
}
|
||||
|
||||
function updateUndervoltageDisplay(text, state) {
|
||||
var el = document.getElementById("stat-undervoltage");
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.textContent = text || "--";
|
||||
el.classList.remove("b", "g", "o", "alert", "warntext");
|
||||
|
||||
if (state === "active") {
|
||||
el.classList.add("alert");
|
||||
return;
|
||||
}
|
||||
if (state === "past") {
|
||||
el.classList.add("warntext");
|
||||
return;
|
||||
}
|
||||
if (state === "clear") {
|
||||
el.classList.add("g");
|
||||
return;
|
||||
}
|
||||
el.classList.add("b");
|
||||
}
|
||||
|
||||
function updateServiceState(payload) {
|
||||
var system = payload.system || {};
|
||||
var services = payload.services || {};
|
||||
setText("stat-wifi", system.wifi || "unbekannt");
|
||||
setText("stat-ips", system.ips || "unbekannt");
|
||||
updateUndervoltageDisplay(system.undervoltage || "unbekannt", system.undervoltage_state || "unknown");
|
||||
setText("stat-uptime", system.uptime || "unbekannt");
|
||||
setText("stat-disk", system.disk_free || "unbekannt");
|
||||
setText("val-cpu", system.cpu_usage || "--");
|
||||
@@ -832,10 +910,10 @@ function publishJoy() {
|
||||
}
|
||||
|
||||
function startPublishing() {
|
||||
publishJoy();
|
||||
if (publishTimer) {
|
||||
clearInterval(publishTimer);
|
||||
return;
|
||||
}
|
||||
publishJoy();
|
||||
publishTimer = window.setInterval(publishJoy, 50);
|
||||
}
|
||||
|
||||
@@ -868,12 +946,7 @@ function pulseButton(buttonIndex) {
|
||||
}
|
||||
|
||||
function selectDriveMode(mode, buttonIndex) {
|
||||
driveMode = mode;
|
||||
document.querySelectorAll(".dbtn[data-mode]").forEach(function (btn) {
|
||||
btn.classList.toggle("active", btn.dataset.mode === mode);
|
||||
});
|
||||
updateModeDisplay();
|
||||
updateControlLocks();
|
||||
applyDriveMode(mode);
|
||||
pulseButton(buttonIndex);
|
||||
}
|
||||
|
||||
@@ -940,6 +1013,7 @@ function moveThumb(clientX, clientY) {
|
||||
var nx = dx / data.maxD;
|
||||
var ny = -dy / data.maxD;
|
||||
setAxes(nx, ny);
|
||||
publishJoy();
|
||||
startPublishing();
|
||||
}
|
||||
|
||||
@@ -1013,6 +1087,30 @@ window.addEventListener("load", function () {
|
||||
messageType: "sensor_msgs/Joy"
|
||||
});
|
||||
|
||||
roverCommandListener = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: "/rover_command",
|
||||
messageType: "exomy/RoverCommand"
|
||||
});
|
||||
|
||||
roverCommandListener.subscribe(function (message) {
|
||||
applyDriveMode(locomotionModeToUi(message.locomotion_mode), true);
|
||||
motorsEnabled = !!message.motors_enabled;
|
||||
updateMotorDisplay();
|
||||
});
|
||||
|
||||
var joySubscriber = new ROSLIB.Topic({
|
||||
ros: ros,
|
||||
name: "/joy",
|
||||
messageType: "sensor_msgs/Joy"
|
||||
});
|
||||
joySubscriber.subscribe(function (message) {
|
||||
if (message.header.frame_id === "webgui") return;
|
||||
var x = (message.axes[0] || 0);
|
||||
var y = -(message.axes[1] || 0);
|
||||
mirrorJoyThumb(x, y);
|
||||
});
|
||||
|
||||
document.querySelectorAll(".dbtn[data-mode]").forEach(function (button) {
|
||||
button.addEventListener("click", function () {
|
||||
selectDriveMode(button.dataset.mode, parseInt(button.dataset.button, 10));
|
||||
|
||||
@@ -455,6 +455,15 @@ h2 {
|
||||
color: #8a6b35;
|
||||
}
|
||||
|
||||
@keyframes statusBlink {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-page {
|
||||
color: #1b2730;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user