Files
appRobotDriver/public/app.js
2026-06-12 18:16:15 +02:00

295 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('DOMContentLoaded', function() {
function fmt(v) {
if (v === undefined || v === null || isNaN(v)) return '';
return Number(v).toFixed(0);
}
function updatePosition() {
fetch('/api/position')
.then(res => res.json())
.then(data => {
const p = data.position || {};
const m = data.motorCounts || {};
document.getElementById('state-x').textContent = fmt(p.x);
document.getElementById('state-y').textContent = fmt(p.y);
document.getElementById('state-z').textContent = fmt(p.z);
document.getElementById('state-phi').textContent = fmt(p.a*180/Math.PI);
document.getElementById('state-theta').textContent = fmt(p.b*180/Math.PI);
document.getElementById('state-psi').textContent = fmt(p.c*180/Math.PI);
document.getElementById('state-e').textContent = fmt(m.e*180/Math.PI);
/* Motor-Zustand */
document.getElementById('motor-x').textContent = fmt(m.x);
document.getElementById('motor-y').textContent = fmt(m.y*180/Math.PI);
document.getElementById('motor-z').textContent = fmt(m.z*180/Math.PI);
document.getElementById('motor-a').textContent = fmt(m.a*180/Math.PI);
document.getElementById('motor-b').textContent = fmt(m.b*180/Math.PI);
document.getElementById('motor-c').textContent = fmt(m.c*180/Math.PI);
document.getElementById('motor-e').textContent = fmt(m.e);
})
.catch(err => console.error('Error fetching position:', err));
}
function updateStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
// WebClients
const clientsUl = document.getElementById('clients');
clientsUl.innerHTML = '';
data.clients.forEach(client => {
const li = document.createElement('li');
li.textContent = client;
clientsUl.appendChild(li);
});
// Sender
const sendersUl = document.getElementById('senderList');
sendersUl.innerHTML = '';
data.senders.forEach(sender => {
const li = document.createElement('li');
const state = sender.state || 'disconnected';
const label = sender.url ? `${sender.name} (${sender.url}): ${state}` : `${sender.name}: ${state}`;
li.textContent = label;
li.classList.add(state.toLowerCase());
sendersUl.appendChild(li);
});
// Letzte Commands
const commandsUl = document.getElementById('commandList');
commandsUl.innerHTML = '';
data.lastCommands.forEach(cmd => {
const li = document.createElement('li');
li.textContent = cmd;
commandsUl.appendChild(li);
});
// Letzte Pings
const pingsUl = document.getElementById('pingList');
pingsUl.innerHTML = '';
data.lastPings.forEach(ping => {
const li = document.createElement('li');
li.textContent = ping;
pingsUl.appendChild(li);
});
})
.catch(error => console.error('Error fetching status:', error));
}
// ── Robot.json + History ─────────────────────────────────────────────────
let robotJsonActive = 'current';
let robotJsonLastSerialized = null;
function renderJsonTree(data, container) {
container.innerHTML = '';
const tree = document.createElement('div');
tree.className = 'json-tree';
for (const [key, value] of Object.entries(data)) {
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
const details = document.createElement('details');
const summary = document.createElement('summary');
summary.textContent = key;
details.appendChild(summary);
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(value, null, 2);
details.appendChild(pre);
tree.appendChild(details);
} else {
const row = document.createElement('div');
row.className = 'json-scalar';
row.innerHTML =
`<span class="json-key">${key}</span>` +
`<span class="json-val">${JSON.stringify(value)}</span>`;
tree.appendChild(row);
}
}
container.appendChild(tree);
}
function updateRobotJson() {
const url = robotJsonActive === 'current'
? '/api/robot'
: `/api/robot/history/${robotJsonActive}`;
fetch(url)
.then(res => res.ok ? res.json() : Promise.reject(res.status))
.then(data => {
const serialized = JSON.stringify(data);
document.getElementById('robotJsonLabel').textContent =
robotJsonActive === 'current' ? '(aktuell)' : `(${robotJsonActive})`;
if (serialized !== robotJsonLastSerialized) {
robotJsonLastSerialized = serialized;
renderJsonTree(data, document.getElementById('robotJsonTree'));
}
})
.catch(err => {
document.getElementById('robotJsonTree').textContent = `Fehler: ${err}`;
});
}
function setHistoryActive(ts) {
robotJsonActive = ts;
robotJsonLastSerialized = null; // Neuaufbau erzwingen beim Snapshot-Wechsel
updateRobotJson();
document.querySelectorAll('#robotHistoryList li').forEach(l => {
l.classList.toggle('rh-active', l.dataset.ts === ts);
});
}
function updateRobotHistory() {
fetch('/api/robot/history')
.then(res => res.json())
.then(({ history }) => {
const ul = document.getElementById('robotHistoryList');
ul.innerHTML = '';
const liCurrent = document.createElement('li');
liCurrent.textContent = 'robot.json (aktuell)';
liCurrent.dataset.ts = 'current';
if (robotJsonActive === 'current') liCurrent.classList.add('rh-active');
liCurrent.addEventListener('click', () => setHistoryActive('current'));
ul.appendChild(liCurrent);
history.forEach(entry => {
const ts = entry.filename.slice(6, -5); // robot_YYYYMMDD_HHmmss.json → YYYYMMDD_HHmmss
const li = document.createElement('li');
li.textContent = entry.filename;
li.dataset.ts = ts;
if (robotJsonActive === ts) li.classList.add('rh-active');
li.addEventListener('click', () => setHistoryActive(ts));
ul.appendChild(li);
});
})
.catch(err => console.error('Error fetching robot history:', err));
}
// ── Emergency Stop Panel ─────────────────────────────────────────────
// SVG-Button: Farbe + Text + Click-Handler je nach armed-Zustand wechseln.
// armed=true → rot "EMERGENCY STOP" → POST /api/emergency-stop
// armed=false → grün "START ROBOT" → POST /api/power-on
let _lastArmed = null;
function updateEmergencyStopButton(armed) {
if (armed === _lastArmed) return;
_lastArmed = armed;
const stops = document.querySelectorAll('#estopGrad stop');
const textPath = document.querySelector('#emergency-stop textPath');
const btnInner = document.querySelector('#emergency-stop circle:last-of-type');
const label = document.getElementById('armed-status');
if (armed) {
// Rot: Roboter bestromt → Klick = Emergency Stop
if (stops[0]) stops[0].setAttribute('stop-color', '#ff5555');
if (stops[1]) stops[1].setAttribute('stop-color', '#cc0000');
if (stops[2]) stops[2].setAttribute('stop-color', '#880000');
if (btnInner) btnInner.setAttribute('stroke', '#660000');
if (textPath) textPath.textContent = 'EMERGENCY STOP';
if (label) { label.textContent = '● Bestromt'; label.className = 'estop-armed-label armed'; }
} else {
// Grün: Strom AUS → Klick = Strom einschalten (Start Robot)
if (stops[0]) stops[0].setAttribute('stop-color', '#88ff99');
if (stops[1]) stops[1].setAttribute('stop-color', '#00aa44');
if (stops[2]) stops[2].setAttribute('stop-color', '#005522');
if (btnInner) btnInner.setAttribute('stroke', '#003311');
if (textPath) textPath.textContent = 'START ROBOT';
if (label) { label.textContent = '○ Kein Strom'; label.className = 'estop-armed-label disarmed'; }
}
const div = document.getElementById('emergency-stop');
if (div) {
div.onclick = armed
? () => fetch('/api/emergency-stop', { method: 'POST' })
: () => fetch('/api/power-on', { method: 'POST' });
}
}
async function pollPowerStatus() {
try {
const res = await fetch('/api/power-status');
if (!res.ok) return;
const data = await res.json();
if (data.ok) updateEmergencyStopButton(data.armed);
} catch { /* Netzwerkfehler → Button bleibt im letzten Zustand */ }
}
pollPowerStatus();
setInterval(pollPowerStatus, 2000);
const btnAlarmUnlock = document.getElementById('btn-alarm-unlock');
const alarmUnlockStatus = document.getElementById('alarm-unlock-status');
if (btnAlarmUnlock) {
btnAlarmUnlock.addEventListener('click', async () => {
btnAlarmUnlock.disabled = true;
alarmUnlockStatus.textContent = 'Wird ausgeführt…';
alarmUnlockStatus.className = 'estop-status';
try {
const res = await fetch('/api/alarm-unlock', { method: 'POST' });
const data = await res.json();
if (data.ok) {
alarmUnlockStatus.textContent = '✅ Alarm entsperrt';
alarmUnlockStatus.className = 'estop-status ok';
} else {
const failed = (data.results || [])
.filter(r => !r.ok && !r.skipped)
.map(r => r.name)
.join(', ');
alarmUnlockStatus.textContent = `⚠️ Fehlgeschlagen: ${failed || 'unbekannt'}`;
alarmUnlockStatus.className = 'estop-status err';
}
} catch (err) {
alarmUnlockStatus.textContent = `❌ Fehler: ${err.message}`;
alarmUnlockStatus.className = 'estop-status err';
} finally {
btnAlarmUnlock.disabled = false;
}
});
}
updateStatus();
updatePosition();
updateRobotJson();
updateRobotHistory();
setInterval(() => {
updateStatus();
updatePosition();
if (robotJsonActive === 'current') updateRobotJson();
}, 1000);
setInterval(updateRobotHistory, 30000);
});
document.querySelectorAll('.section').forEach(sec => {
const id = sec.dataset.id;
const saved = localStorage.getItem('section_' + id);
if (saved === 'collapsed') sec.classList.add('collapsed');
sec.querySelector('h2').addEventListener('click', () => {
sec.classList.toggle('collapsed');
localStorage.setItem(
'section_' + id,
sec.classList.contains('collapsed') ? 'collapsed' : 'open'
);
});
});
/* Initial-Zustand
['clients','pings'].forEach(id => {
if (!localStorage.getItem('section_' + id))
localStorage.setItem('section_' + id, 'collapsed');
});
*/