Roadmap Homing

This commit is contained in:
chk
2026-06-14 16:29:01 +02:00
parent 275ab083fa
commit cce53cda54

View File

@@ -132,6 +132,33 @@ X-Position (`--x-mm`) wird aus den triangulierten Board-Marker-Positionen bestim
## Implementierungsplan: Homing-UI
### ⚠ Wichtig: Schritte 13b 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 520606). Sie erzeugt `<runDir>/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 <runDir>/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
// 13b: 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