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}
+
+ `;
+ }
+ 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