Homing nicht als SubPage
This commit is contained in:
204
public/client.js
204
public/client.js
@@ -325,6 +325,202 @@ async function onFotoClick() {
|
|||||||
display.appendChild(wrapper);
|
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 += `<div style="display:flex;gap:8px;padding:3px 0;font-family:ui-monospace,monospace;font-size:13px">
|
||||||
|
<span style="min-width:130px;color:#94a3b8">${label}</span>
|
||||||
|
<span style="font-weight:600">${valStr} ${unit}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
treeEl.innerHTML = html || '<span style="color:#64748b">–</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">';
|
||||||
|
for (const img of (data.images ?? [])) {
|
||||||
|
html += `<figure style="margin:0">
|
||||||
|
<img src="data:image/jpeg;base64,${img.contentBase64}"
|
||||||
|
style="max-width:380px;height:auto;border:1px solid #1f2937;border-radius:4px;display:block"
|
||||||
|
alt="${img.filename}">
|
||||||
|
<figcaption style="font-size:11px;color:#64748b;text-align:center;margin-top:3px">
|
||||||
|
${img.filename}
|
||||||
|
</figcaption>
|
||||||
|
</figure>`;
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
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) {
|
async function onCommandClick(btn) {
|
||||||
const cmd = btn.dataset.cmd;
|
const cmd = btn.dataset.cmd;
|
||||||
const payloadSelector = btn.dataset.payload;
|
const payloadSelector = btn.dataset.payload;
|
||||||
@@ -346,6 +542,14 @@ async function onCommandClick(btn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupUi() {
|
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");
|
const calculateBtn = document.getElementById("btn-calculate");
|
||||||
if (calculateBtn) {
|
if (calculateBtn) {
|
||||||
calculateBtn.addEventListener("click", onCalculateClick);
|
calculateBtn.addEventListener("click", onCalculateClick);
|
||||||
|
|||||||
@@ -15,30 +15,40 @@
|
|||||||
<div class="section full">
|
<div class="section full">
|
||||||
<h2>Aktionen</h2>
|
<h2>Aktionen</h2>
|
||||||
|
|
||||||
<div class="controls">
|
<!-- Homing – primäre Aktion -->
|
||||||
<button data-cmd="HOME">HOME</button>
|
<div class="controls" style="flex-wrap:wrap;gap:10px;align-items:center">
|
||||||
<button data-cmd="PING">PING</button>
|
<button id="btn-homing-run">📷 Foto & Homing berechnen</button>
|
||||||
|
<button id="btn-homing-send" disabled
|
||||||
|
style="opacity:.4;cursor:not-allowed"
|
||||||
|
title="Erst Homing ausführen">
|
||||||
|
✅ An Roboter senden
|
||||||
|
</button>
|
||||||
|
<span id="homing-status" class="status-badge open">○ Warte</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fortschrittsbalken -->
|
||||||
|
<div id="homing-progress" style="display:none;margin-top:12px">
|
||||||
|
<div id="homing-progress-track">
|
||||||
|
<div id="homing-progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<span id="homing-progress-text"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sekundäre Aktionen -->
|
||||||
|
<div class="controls" style="margin-top:14px;flex-wrap:wrap;gap:8px;padding-top:12px;border-top:1px solid #1e293b">
|
||||||
|
<button data-cmd="HOME" style="font-size:12px;padding:4px 12px">HOME</button>
|
||||||
|
<button data-cmd="PING" style="font-size:12px;padding:4px 12px">PING</button>
|
||||||
<input
|
<input
|
||||||
id="gcodePayload"
|
id="gcodePayload"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="G-Code / Motorbefehl"
|
placeholder="G-Code / Motorbefehl"
|
||||||
|
style="font-size:12px;padding:4px 8px"
|
||||||
/>
|
/>
|
||||||
|
<button data-cmd="GCODEMOTOR" data-payload="#gcodePayload"
|
||||||
<button data-cmd="GCODEMOTOR" data-payload="#gcodePayload">
|
style="font-size:12px;padding:4px 12px">GCodeMotor</button>
|
||||||
GCodeMotor
|
<button id="btn-foto" style="font-size:12px;padding:4px 12px">Foto-Vorschau</button>
|
||||||
</button>
|
|
||||||
|
|
||||||
<button id="btn-calculate">Read Position from Markers</button>
|
|
||||||
|
|
||||||
<button id="btn-foto">Foto</button>
|
|
||||||
|
|
||||||
<a href="/calibration.html">
|
<a href="/calibration.html">
|
||||||
<button type="button">Calibration Page</button>
|
<button type="button" style="font-size:12px;padding:4px 12px">Kalibrierung</button>
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/homing.html">
|
|
||||||
<button type="button">Homing</button>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -225,3 +225,45 @@ textarea {
|
|||||||
#snapshot-info-picture {
|
#snapshot-info-picture {
|
||||||
margin-top: 10px;
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user