diff --git a/public/client.js b/public/client.js index 37f495b..570acfe 100755 --- a/public/client.js +++ b/public/client.js @@ -325,6 +325,202 @@ async function onFotoClick() { display.appendChild(wrapper); } +// ── Homing ──────────────────────────────────────────────────────────────────── + +let _homingState = null; + +function setHomingStatus(label, cls) { + const el = document.getElementById('homing-status'); + if (!el) return; + el.textContent = label; + el.className = `status-badge ${cls}`; +} + +function setHomingProgress(step, total, text) { + const wrap = document.getElementById('homing-progress'); + const bar = document.getElementById('homing-progress-bar'); + const txt = document.getElementById('homing-progress-text'); + if (!wrap) return; + wrap.style.display = 'block'; + if (bar) bar.style.width = (total > 0 ? Math.round(step / total * 100) : 0) + '%'; + if (txt) txt.textContent = text || `Schritt ${step} / ${total}`; +} + +function showHomingResult(state) { + // Raw JSON + const jsonEl = document.getElementById('result-json'); + if (jsonEl) jsonEl.value = JSON.stringify(state, null, 2); + + // Tree View: Labels + Einheiten statt generischem renderTree + const treeEl = document.getElementById('result-tree'); + if (treeEl) { + const LABELS = { + x_mm: 'x (Slider)', y_deg: 'y (Arm1)', z_deg: 'z (Ellbow)', + a_deg: 'a (Arm2)', b_deg: 'b (Hand)', c_deg: 'c (Palm)', + e_mm: 'e (Greifer)', + }; + const UNITS = { + x_mm: 'mm', y_deg: '°', z_deg: '°', + a_deg: '°', b_deg: '°', c_deg: '°', e_mm: 'mm', + }; + let html = ''; + for (const [key, val] of Object.entries(state)) { + const label = LABELS[key] ?? key; + const unit = UNITS[key] ?? ''; + const valStr = typeof val === 'number' ? val.toFixed(2) : String(val ?? '–'); + html += `
+ ${label} + ${valStr} ${unit} +
`; + } + treeEl.innerHTML = html || ''; + } +} + +async function loadHomingImages(runDir) { + const display = document.getElementById('snapshot-info-picture'); + if (!display || !runDir) return; + try { + const res = await fetch(`/api/homing/run-data?run=${encodeURIComponent(runDir)}`); + if (!res.ok) return; + const data = await res.json(); + + let html = '
'; + for (const img of (data.images ?? [])) { + html += `
+ ${img.filename} +
+ ${img.filename} +
+
`; + } + html += '
'; + display.innerHTML = html; + } catch { /* nicht kritisch */ } +} + +async function runHoming() { + // UI zurücksetzen + clearTextarea('log'); + clearTextarea('analysis-log'); + clearTextarea('result-json'); + clearElement('result-tree'); + clearElement('snapshot-table'); + clearElement('snapshot-info-picture'); + + const btnRun = document.getElementById('btn-homing-run'); + const btnSend = document.getElementById('btn-homing-send'); + _homingState = null; + + if (btnRun) btnRun.disabled = true; + if (btnSend) { + btnSend.disabled = true; + btnSend.style.opacity = '.4'; + btnSend.style.cursor = 'not-allowed'; + } + setHomingStatus('● Läuft …', 'wip'); + setHomingProgress(0, 6, 'Verbinde …'); + + try { + const response = await fetch('/api/homing/run', { method: 'POST' }); + if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buf = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buf += decoder.decode(value, { stream: true }); + const lines = buf.split('\n'); + buf = lines.pop(); + + for (const line of lines) { + if (!line.startsWith('data: ')) continue; + let evt; + try { evt = JSON.parse(line.slice(6)); } catch { continue; } + + switch (evt.type) { + case 'log': + appendLog(evt.text ?? ''); + break; + + case 'step': + setHomingProgress(evt.step, evt.total, evt.text); + appendLog(`[${evt.step}/${evt.total}] ${evt.text || ''}`); + break; + + case 'analysis': { + const el = document.getElementById('analysis-log'); + if (el) { + el.value += `${evt.key}:\n${JSON.stringify(evt.value, null, 2)}\n\n`; + el.scrollTop = el.scrollHeight; + } + break; + } + + case 'error': + appendLog(evt.text ?? ''); + setHomingStatus('✗ Fehler', 'open'); + break; + + case 'done': + if (evt.state) { + _homingState = evt.state; + showHomingResult(evt.state); + if (btnSend) { + btnSend.disabled = false; + btnSend.style.opacity = ''; + btnSend.style.cursor = ''; + } + setHomingStatus('✓ Fertig', 'done'); + setHomingProgress(6, 6, 'Homing abgeschlossen'); + } else { + setHomingStatus('✗ Fehler', 'open'); + setHomingProgress(6, 6, 'Fehler aufgetreten'); + } + if (evt.runDir) await loadHomingImages(evt.runDir); + break; + } + } + } + } catch (err) { + appendLog(`❌ Verbindungsfehler: ${err.message}`); + setHomingStatus('✗ Fehler', 'open'); + } finally { + if (btnRun) btnRun.disabled = false; + } +} + +async function sendHomingToRobot() { + if (!_homingState) return; + const btnSend = document.getElementById('btn-homing-send'); + if (btnSend) btnSend.disabled = true; + + try { + const res = await fetch('/api/homing/send-state', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ state: _homingState }), + }); + const data = await res.json(); + if (res.ok) { + appendLog('✅ State erfolgreich an Roboter gesendet'); + setHomingStatus('✓ Gesendet', 'done'); + } else { + appendLog(`❌ Fehler beim Senden: ${data.error ?? JSON.stringify(data)}`); + if (btnSend) btnSend.disabled = false; + } + } catch (err) { + appendLog(`❌ Netzwerkfehler: ${err.message}`); + if (btnSend) btnSend.disabled = false; + } +} + async function onCommandClick(btn) { const cmd = btn.dataset.cmd; const payloadSelector = btn.dataset.payload; @@ -346,6 +542,14 @@ async function onCommandClick(btn) { } function setupUi() { + // Homing-Buttons + const homingRunBtn = document.getElementById('btn-homing-run'); + if (homingRunBtn) homingRunBtn.addEventListener('click', runHoming); + + const homingSendBtn = document.getElementById('btn-homing-send'); + if (homingSendBtn) homingSendBtn.addEventListener('click', sendHomingToRobot); + + // Ältere Buttons (Fallback / Debug) const calculateBtn = document.getElementById("btn-calculate"); if (calculateBtn) { calculateBtn.addEventListener("click", onCalculateClick); diff --git a/public/index.html b/public/index.html index 39cca49..9f7c923 100755 --- a/public/index.html +++ b/public/index.html @@ -15,30 +15,40 @@

Aktionen

-
- - + +
+ + + ○ Warte +
+ + + + +
+ + - - - - - - - + + - - - - - +
diff --git a/public/styles.css b/public/styles.css index 87f74bc..4ccfb68 100755 --- a/public/styles.css +++ b/public/styles.css @@ -224,4 +224,46 @@ textarea { #snapshot-info-picture { margin-top: 10px; +} + +/* ===== STATUS-BADGE (Homing) ===== */ + +.status-badge { + display: inline-block; + padding: 2px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + background: #1e293b; + color: #94a3b8; +} +.status-badge.open { color: #f59e0b; } +.status-badge.wip { color: #60a5fa; } +.status-badge.done { color: #34d399; background: #064e3b; } + +/* ===== HOMING FORTSCHRITTSBALKEN ===== */ + +#homing-progress { + margin-top: 12px; +} + +#homing-progress-track { + background: #1e293b; + border-radius: 3px; + height: 5px; + overflow: hidden; +} + +#homing-progress-bar { + height: 100%; + background: var(--accent); + width: 0%; + transition: width .4s ease; +} + +#homing-progress-text { + font-size: 11px; + color: var(--muted); + display: block; + margin-top: 4px; } \ No newline at end of file