Roadmap Homing
This commit is contained in:
@@ -132,6 +132,33 @@ X-Position (`--x-mm`) wird aus den triangulierten Board-Marker-Positionen bestim
|
|||||||
|
|
||||||
## Implementierungsplan: Homing-UI
|
## 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 `<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`
|
### Phase 1 — Backend-Route `POST /api/homing/run`
|
||||||
|
|
||||||
**Datei:** `server/server.js` (neue Route) + `server/homingOrchestrator.js` (neue Datei)
|
**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
|
```javascript
|
||||||
// server/homingOrchestrator.js
|
// server/homingOrchestrator.js
|
||||||
export async function runHoming({ robotJsonPath, boardDataDir, send, options }) {
|
export async function runHoming({ robotJsonPath, send }) {
|
||||||
|
|
||||||
// 1. Snapshot (Webcam-API)
|
// 1–3b: bestehende Board-Pipeline wiederverwenden
|
||||||
send({ type: 'step', step: 1, text: 'Foto aufnehmen …' });
|
// (Foto + 1_detect + 2_camera + 3b_poses, pro Kamera geloopt)
|
||||||
const runDir = await takeSnapshot(); // WEBCAM_URL
|
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
|
// 4. X-Position aus triangulierten Markern bestimmen
|
||||||
send({ type: 'step', step: 2, text: 'Marker erkennen …' });
|
const xMm = estimateXFromMarkers(arucoJson); // siehe Abschnitt „X-Position"
|
||||||
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
|
|
||||||
|
|
||||||
// 4b. Gelenkwinkel sequenziell, Link für Link
|
// 4b. Gelenkwinkel sequenziell, Link für Link
|
||||||
const links = ['Arm1', 'Ellbow', 'Arm2', 'Hand'];
|
const links = ['Arm1', 'Ellbow', 'Arm2', 'Hand'];
|
||||||
let fromState = null;
|
let fromState = null;
|
||||||
for (const link of links) {
|
for (const link of links) {
|
||||||
send({ type: 'step', text: `Gelenkwinkel ${link} …` });
|
send({ type: 'step', text: `Gelenkwinkel ${link} …` });
|
||||||
const args = ['4b_revolute_angle.py',
|
const args = [SCRIPT_4B,
|
||||||
'--robot', robotJsonPath, '--aruco', arucoJson,
|
'--robot', robotJsonPath, '--aruco', arucoJson,
|
||||||
'--link', link,
|
'--link', link,
|
||||||
'--output', path.join(runDir, `state_${link}.json`),
|
'--output', path.join(runDir, `state_${link}.json`),
|
||||||
];
|
];
|
||||||
if (fromState) args.push('--from-state', fromState);
|
if (fromState) args.push('--from-state', fromState);
|
||||||
else args.push('--x-mm', String(xMm));
|
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
|
// Ergebnis: letztes state_*.json enthält den vollständigen accumulated_state
|
||||||
const finalState = JSON.parse(fs.readFileSync(fromState));
|
const finalState = JSON.parse(fs.readFileSync(fromState, 'utf8'));
|
||||||
send({ type: 'done', state: finalState.accumulated_state, runDir });
|
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:**
|
**Route:**
|
||||||
```javascript
|
```javascript
|
||||||
app.post('/api/homing/run', async (req, res) => {
|
app.post('/api/homing/run', async (req, res) => {
|
||||||
// SSE-Header
|
|
||||||
res.setHeader('Content-Type', 'text/event-stream');
|
res.setHeader('Content-Type', 'text/event-stream');
|
||||||
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
|
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();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -298,8 +320,13 @@ automatisch die aktuelle Konfiguration.
|
|||||||
[Jetzt] Arm-Marker eintragen (Nutzer)
|
[Jetzt] Arm-Marker eintragen (Nutzer)
|
||||||
→ Arm1 Joint-Origin Button klicken (bereits ausführbar)
|
→ 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
|
[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
|
→ Minimale UI: nur Log + Raw JSON
|
||||||
|
|
||||||
[2] public/homing.html
|
[2] public/homing.html
|
||||||
|
|||||||
Reference in New Issue
Block a user