Alles verbessert

This commit is contained in:
2026-05-21 19:30:33 +02:00
parent 5dccc8985e
commit 33b71416ff
19 changed files with 855 additions and 229 deletions
+84 -65
View File
@@ -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();
+39
View File
@@ -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);
+155 -57
View File
@@ -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));
+9
View File
@@ -455,6 +455,15 @@ h2 {
color: #8a6b35;
}
@keyframes statusBlink {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.25;
}
}
.admin-page {
color: #1b2730;
overflow-y: auto;