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 = `${key}` + `${JSON.stringify(value)}`; 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)); } 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'); }); */