Files
appRobotHoming/doc/Homing.md
2026-06-25 17:34:41 +02:00

165 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Homing appRobotHoming
> Stand: 2026-06-15
> Homing läuft bei **jedem Einschalten** — schnell, vollautomatisch, ohne mechanische Endschalter.
---
## Was ist Homing?
Der Roboter weiss beim Einschalten nicht, wo er steht.
Die Kameras schauen auf die ArUco-Marker am Roboter und berechnen daraus
die vollständige Pose aller Gelenke.
**Homing** (dieser Prozess): bei jedem Einschalten, automatisch.
**Kalibrierung** (`doc/Kalibrierung.md`): nur nach mechanischen Änderungen.
Dieses Dokument ist der **allgemeine Ablauf**. Technische Detail-Doku je Teilstrecke:
| Doku | Inhalt |
|---|---|
| [`Homing_0_Camera.md`](Homing_0_Camera.md) | Foto → Kamera-Pose → trianguliertes `aruco_marker_poses.json` (Schritte 13b) |
| [`Homing_1_StepByStep.md`](Homing_1_StepByStep.md) | `aruco_marker_poses.json` → Gelenkwinkel je Link (4b-Kette, Schätz-Methoden/Fallbacks) |
| `Homing_2_improvement.md` *(geplant)* | Bekannte Probleme, Genauigkeits-Befunde, Verbesserungsideen |
| [`Homing_5_Pose.md`](Homing_5_Pose.md) | Verfeinerungsschritt NACH der 4b-Kette (Bundle-Adjustment, `5_pose_estimation.py`) — noch nicht verdrahtet |
---
## Kinematik-Kette
```
Board (ROOT, fest) ← Referenz aller Kameras
├── Base linear x axis=[1,0,0] ← Slider-Position
├── Arm1 revolute y axis=[-1,0,0] ← Schultergelenk
├── Ellbow revolute z axis=[-1,0,0] ← Ellbogen
├── Arm2 revolute a axis=[0,-1,0] ← Unterarm-Drehung
├── Hand revolute b axis=[1,0,0] ← Handgelenk
├── Palm revolute c axis=[0,-1,0] ← Handfläche
└── FingerA/B linear e axis=±[1,0,0] ← Greifer (symmetrisch)
```
**Ergebnis-State:** `{ x_mm, y_deg, z_deg, a_deg, b_deg, c_deg, e_mm }`
---
## Voraussetzungen
Homing setzt eine abgeschlossene Kalibrierung voraus:
| Was | Status |
|-----|--------|
| Kamera-Intrinsik (NPZ) | ✅ |
| Board-Marker-Positionen | ✅ |
| X-Achsen-Richtung | ✅ |
| Arm1 Joint-Origin Y/Z | ✅ Button vorhanden und ausführbar |
| Arm-Marker in robot.json | 🔶 Position manuell eintragen; Spin-Verifikation via Kalibrierung → Tab „Marker" (→ `doc/Kalibrierung_Marker.md`) |
---
## Ablauf
```
Foto alle Kameras
1_detect_aruco_observations.py (pro Kamera, mit NPZ)
2_estimate_camera_from_observations.py (pro Kamera)
3b_corner_marker_poses.py (einmal, benötigt ≥2 Kamera-Posen)
│ → aruco_marker_poses.json
X-Position aus Marker-Positionen schätzen
│ → x_mm (pro Arm-Marker: beobachtetes_x Modell_x(slider=0), gemittelt)
4b_revolute_angle.py --link Arm1 --x-mm {x_mm}
│ → state_Arm1.json (accumulated_state)
4b_revolute_angle.py --link Ellbow --from-state state_Arm1.json
│ → state_Ellbow.json
4b_revolute_angle.py --link Arm2 --from-state state_Ellbow.json
│ → state_Arm2.json
4b_revolute_angle.py --link Hand --from-state state_Arm2.json
│ → state_Hand.json ← accumulated_state: x,y,z,a,b (4b-Primärkette)
│ Fallback 5_pose_estimation.py liefert alle 7 (…,c,e). Nur bekannte
│ Achsen gehen ins G92; wirklich fehlende werden weggelassen.
G92 über Driver-WebSocket (DRIVER_WS_URL) — setzt Motorposition ohne Bewegung
```
**Schritte 13b** sind dieselbe Board-Pipeline wie in der Kalibrierung.
Sie sind in `runBoardPipeline()` (`server/server.js`) als gemeinsame Funktion ausgelagert.
**4b-Schleife**: sequenziell von root nach tip; jedes Script bekommt den Zustand des
vorherigen Schritts über `--from-state`. Der erste Aufruf erhält die geschätzte
X-Slider-Position über `--x-mm`.
---
## Implementierung
| Komponente | Datei | Beschreibung |
|-----------|-------|--------------|
| Board-Pipeline | `server/server.js``runBoardPipeline(runDir, send)` | Foto + Scripts 1, 2, 3b; von Board-Run und Homing genutzt |
| X-Schätzung | `server/homingOrchestrator.js``estimateXFromMarkers()` | Pro Arm-Marker `beobachtetes_x Modell_x(slider=0)`, gemittelt — rechnet den kinematischen Gelenk-Offset (z.B. Arm1.origin.x=110) heraus. Nur x-zuverlässige Ketten (x-Rotation: Arm1/Ellbow). Fallback: roher Mittelwert |
| Homing-Orchestrator | `server/homingOrchestrator.js``runHoming()` | Kompletter Ablauf als SSE-Stream |
| Backend-Route | `POST /api/homing/run` | SSE-Stream, startet `runHoming()` |
| State senden | `POST /api/homing/send-state` | Baut `G92` (fehlende c/e → 0) und sendet es als Plain-Text-G-Code über den Driver-WebSocket (`DRIVER_WS_URL`, `server/driverClient.js`). Der Driver verarbeitet G92 intern als M92 = Motorposition setzen ohne Bewegung. Kein HTTP `/api/state` (gibt es am Driver nicht) |
| Run-Daten | `GET /api/homing/run-data?run=ts` | Debug-Bilder (base64) + finalState |
| Frontend | `public/index.html` + `public/client.js` | Homing-Buttons, Fortschrittsbalken, Tree View; schreibt Teil-Pose als `G92`-GCode ins Eingabefeld |
| Board-Viewer (Homing) | `public/boardViewer.html?mode=homing` | Skelett + Arm-Marker per FK (Three.js): Marker-Quadrat spin-korrekt rotiert + Orientierungszeiger zu Ecke 0 (Modell-Seite); gemessene Marker als Kugeln + Fehlerlinien; progressiver Update je erkanntem Gelenk |
**Lauf-Verzeichnisse:** `data/homing/{timestamp}/`
---
## SSE-Event-Typen
Das Backend streamt während des Homing-Laufs folgende Events:
| `type` | Felder | Bedeutung |
|--------|--------|-----------|
| `log` | `text` | Zeile aus Script-Ausgabe |
| `step` | `step`, `total`, `text` | Fortschritt (16) |
| `analysis` | `key`, `value` | Zwischenergebnis (x_mm, state_Arm1, …) |
| `error` | `text` | Fehler (Script-Exit ≠ 0 o.ä.) |
| `done` | `exitCode`, `state?`, `runDir` | Abschluss; `state` nur bei Erfolg |
---
## robot.json — Ladestrategie
**Aktuell:** Lokale Datei
```javascript
ROBOT_JSON = process.env.ROBOT_JSON || 'scripts/robot_1781069752019.json'
```
**Geplant** (wenn Driver `GET ROBOT_URL/api/robot/config` implementiert):
```javascript
async function loadRobotConfig() {
if (ROBOT_URL) {
const res = await fetch(new URL('/api/robot/config', ROBOT_URL));
return res.json();
}
return JSON.parse(await fs.readFile(ROBOT_JSON, 'utf8'));
}
```
Auswirkung: nur `ROBOT_JSON`-Variable ändern — alle Scripts bekommen automatisch
die aktuelle Konfiguration.
---
## Offene Punkte
- [~] **Arm-Marker eintragen** (Nutzer): `links.Arm1/Ellbow/Arm2/Hand.markers` in `robot.json` — Arm1 + Ellbow eingetragen, Arm2/Hand offen
- [~] **Erstmals testen**: Homing-Run mit echtem Roboter — Arm1 (x, y) und Ellbow (z) laufen durch; Arm2/Hand noch zu prüfen. Ellbow brach zunächst ab (4b: nur 2 Marker sichtbar, deren Verbindungsvektor parallel zur Gelenkachse liegt → keine Baseline), seit 2026-06-16 über Fallback-1/-2 in `4b_revolute_angle.py` gelöst — Details: [`Homing_1_StepByStep.md`](Homing_1_StepByStep.md)
- [x] **X-Schätzung verfeinern** (2026-06-14): `estimateXFromMarkers()` rechnet den kinematischen Gelenk-Offset heraus statt rohem Mittelwert — behebt den ~110 mm Versatz der Modell-Marker
- [x] **Unit-Test für X-Schätzung** (2026-06-14): reine Geometrie nach `server/homingXEstimate.cjs` ausgelöst, `test/homingXEstimate.test.js` (9 Tests, inkl. Regression gegen den Offset-Bug)
- [ ] **y-Restfehler** (~2°): erkannt 30° → ausgegeben 28°; vermutlich X-Rest-Rauschen + 4b-Fit-Residuum, noch zu untersuchen
- [x] **robot.json via Driver-API** (2026-06-17): `server/robotConfig.js``fetchRobot()`/`pushRobot()`/`robotCachePath`; automatischer Fallback auf lokale Datei wenn `ROBOT_URL` nicht gesetzt