diff --git a/doc/Homing_ROADMAP.md b/doc/Homing_ROADMAP.md index 20f72be..3fe5ef8 100644 --- a/doc/Homing_ROADMAP.md +++ b/doc/Homing_ROADMAP.md @@ -132,6 +132,33 @@ X-Position (`--x-mm`) wird aus den triangulierten Board-Marker-Positionen bestim ## Implementierungsplan: Homing-UI +### ⚠ Wichtig: Schritte 1–3b existieren bereits + +Die Kette **Foto → 1_detect → 2_camera → 3b_poses** ist bereits vollständig +implementiert und produktiv in `POST /api/board/run` (`server/server.js`, +ca. Zeile 520–606). Sie erzeugt `/aruco_marker_poses.json`. + +**Die echten, funktionierenden Aufrufe (NICHT neu erfinden):** + +```javascript +// Pro Kamera geloopt (jede Kamera hat eigene NPZ): +runScript([SCRIPT_1, '-i', imgPath, '-npz', npzPath, '-robot', ROBOT_JSON, + '-cameraId', camId, '-outDir', runDir, '--saveDebugImage']); + +runScript([SCRIPT_2, '-i', detJson, '-robot', ROBOT_JSON, '-outDir', runDir]); + +// Einmal, nach allen Kameras (braucht ≥2 _camera_pose.json): +runScript([SCRIPT_3B, '--evalDir', runDir, '--robot', ROBOT_JSON]); +// → schreibt /aruco_marker_poses.json +``` + +**Empfehlung:** Die Snapshot+1+2+3b-Logik aus `/api/board/run` in eine +gemeinsame Funktion `runBoardPipeline(runDir, send)` auslagern. Homing ruft +sie auf und hängt nur den 4b-Teil an. So gibt es keine Duplikate und keine +abweichenden Argumente. + +--- + ### Phase 1 — Backend-Route `POST /api/homing/run` **Datei:** `server/server.js` (neue Route) + `server/homingOrchestrator.js` (neue Datei) @@ -140,58 +167,53 @@ X-Position (`--x-mm`) wird aus den triangulierten Board-Marker-Positionen bestim ```javascript // server/homingOrchestrator.js -export async function runHoming({ robotJsonPath, boardDataDir, send, options }) { +export async function runHoming({ robotJsonPath, send }) { - // 1. Snapshot (Webcam-API) - send({ type: 'step', step: 1, text: 'Foto aufnehmen …' }); - const runDir = await takeSnapshot(); // WEBCAM_URL + // 1–3b: bestehende Board-Pipeline wiederverwenden + // (Foto + 1_detect + 2_camera + 3b_poses, pro Kamera geloopt) + send({ type: 'step', step: 1, text: 'Snapshot + Marker-Triangulation …' }); + const runDir = await runBoardPipeline(robotJsonPath, send); // aus server.js ausgelagert + const arucoJson = path.join(runDir, 'aruco_marker_poses.json'); - // 2. Marker erkennen - send({ type: 'step', step: 2, text: 'Marker erkennen …' }); - await runScript(['1_detect_aruco_observations.py', - '-i', ...camImages, '-robot', robotJsonPath, '-outDir', runDir], send); - - // 3. Kamera-Posen - send({ type: 'step', step: 3, text: 'Kamera-Posen schätzen …' }); - await runScript(['2_estimate_camera_from_observations.py', - '-i', runDir, '-robot', robotJsonPath, '-outDir', runDir], send); - - // 3b. 3D-Triangulation - send({ type: 'step', step: 4, text: '3D-Positionen triangulieren …' }); - await runScript(['3b_corner_marker_poses.py', - '-i', runDir, '-robot', robotJsonPath, '--outDir', runDir], send); - - // 4. X-Position aus triangulierten Board-Markern bestimmen - const xMm = estimateXFromMarkers(arucoJson); // aus position_mm der A0-Marker + // 4. X-Position aus triangulierten Markern bestimmen + const xMm = estimateXFromMarkers(arucoJson); // siehe Abschnitt „X-Position" // 4b. Gelenkwinkel sequenziell, Link für Link const links = ['Arm1', 'Ellbow', 'Arm2', 'Hand']; let fromState = null; for (const link of links) { send({ type: 'step', text: `Gelenkwinkel ${link} …` }); - const args = ['4b_revolute_angle.py', + const args = [SCRIPT_4B, '--robot', robotJsonPath, '--aruco', arucoJson, '--link', link, '--output', path.join(runDir, `state_${link}.json`), ]; if (fromState) args.push('--from-state', fromState); else args.push('--x-mm', String(xMm)); - fromState = await runScript(args, send); + const exit = await runScript(args, send); + if (exit !== 0) { send({ type: 'error', text: `4b ${link} Exit ${exit}` }); return; } + fromState = path.join(runDir, `state_${link}.json`); } - // Ergebnis - const finalState = JSON.parse(fs.readFileSync(fromState)); + // Ergebnis: letztes state_*.json enthält den vollständigen accumulated_state + const finalState = JSON.parse(fs.readFileSync(fromState, 'utf8')); send({ type: 'done', state: finalState.accumulated_state, runDir }); } ``` +> **Hinweis:** `runScript()` gibt den Exit-Code zurück (nicht den Pfad). +> Der State-Pfad wird separat gemerkt (`fromState`). + **Route:** ```javascript app.post('/api/homing/run', async (req, res) => { - // SSE-Header res.setHeader('Content-Type', 'text/event-stream'); const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`); - await runHoming({ robotJsonPath: ROBOT_JSON, boardDataDir, send }); + try { + await runHoming({ robotJsonPath: ROBOT_JSON, send }); + } catch (err) { + send({ type: 'error', text: String(err) }); + } res.end(); }); @@ -298,8 +320,13 @@ automatisch die aktuelle Konfiguration. [Jetzt] Arm-Marker eintragen (Nutzer) → Arm1 Joint-Origin Button klicken (bereits ausführbar) +[0] Refactor: Snapshot+1+2+3b aus /api/board/run + in runBoardPipeline(runDir, send) auslagern + → wird von Board-Run UND Homing genutzt + [1] POST /api/homing/run + homingOrchestrator.js - → Script-Kette als SSE orchestrieren + → runBoardPipeline() + 4b-Schleife als SSE + → SCRIPT_4B-Konstante in server.js ergänzen → Minimale UI: nur Log + Raw JSON [2] public/homing.html