boardViewer
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
# robot.json – Zugriff via appRobotDriver
|
||||
|
||||
> Stand: 2026-06-15
|
||||
> Beschreibt die geplante Umstellung: robot.json kommt vom appRobotDriver, nicht
|
||||
> mehr aus einer lokalen Datei.
|
||||
> **Status: umgesetzt** (2026-06-17) — `server/robotConfig.js` ist aktiv.
|
||||
> Dieses Dokument beschreibt Entwurf und Implementierung. Der Implementierungsplan
|
||||
> (Schritte 1–3) ist vollständig abgearbeitet.
|
||||
|
||||
---
|
||||
|
||||
## Ist-Zustand
|
||||
## Verhalten je Env-Variable
|
||||
|
||||
`appRobotHoming` liest und schreibt die Roboter-Konfiguration direkt aus einer
|
||||
| Variable | nicht gesetzt | gesetzt |
|
||||
|----------|--------------|---------|
|
||||
| `ROBOT_URL` | Kein Driver-Kontakt; alle Lese-/Schreibvorgänge direkt auf die lokale Datei | `fetchRobot()` liest von `GET {ROBOT_URL}/api/robot/config`; `pushRobot()` schreibt nach `POST {ROBOT_URL}/api/robot/config` |
|
||||
| `ROBOT_JSON` | Standardpfad `scripts/robot_1781069752019.json` | Angegebener Pfad wird als lokale Cache-Datei verwendet |
|
||||
|
||||
Beide Variablen nicht gesetzt = **reiner Lokal-Modus**, identisch zum Verhalten vor dem Umbau.
|
||||
|
||||
---
|
||||
|
||||
## Ehemaliger Ist-Zustand (vor 2026-06-17)
|
||||
|
||||
`appRobotHoming` las und schrieb die Roboter-Konfiguration direkt aus einer
|
||||
lokalen Datei:
|
||||
|
||||
```
|
||||
@@ -16,8 +27,8 @@ ROBOT_JSON = process.env.ROBOT_JSON
|
||||
|| 'scripts/robot_1781069752019.json'
|
||||
```
|
||||
|
||||
Die Python-Skripte erhalten den Dateipfad als CLI-Argument (`-robot`, `--robot`).
|
||||
Alle Kalibrierungs-Endpoints schreiben ebenfalls in diese Datei.
|
||||
Die Python-Skripte erhielten den Dateipfad als CLI-Argument (`-robot`, `--robot`).
|
||||
Alle Kalibrierungs-Endpoints schrieben ebenfalls in diese Datei.
|
||||
|
||||
**Problem:** Der appRobotDriver besitzt die maßgebliche Konfiguration — nicht das
|
||||
Homing-System. Nach einem Neustart könnten Konfiguration und Driver auseinanderlaufen.
|
||||
|
||||
135
doc/homingAPI.md
135
doc/homingAPI.md
@@ -1,10 +1,12 @@
|
||||
# Homing Offline-API
|
||||
|
||||
> Beschreibung eines geplanten HTTP-Endpunkts, der die vollständige Homing-Pipeline
|
||||
> (Schritte 1–5) ohne Live-Kameras betreibt. Gedacht für die **Simulations-Pipeline**
|
||||
> (`appRobotRendering`), die synthetische oder aufgezeichnete Bilder liefert und die
|
||||
> aktuelle Pose-Erkennung von appRobotHoming nutzen möchte — ohne den Umweg über
|
||||
> echte Kameras und den WebCam-Service. Stand: 2026-06-16, **noch nicht implementiert**.
|
||||
> **Status: implementiert** (2026-06-18).
|
||||
> `POST /api/homing/run-offline` ist aktiv in `server/server.js`.
|
||||
> Abhängigkeit: `multer` (npm-Package, bereits installiert).
|
||||
>
|
||||
> Gedacht für die **Simulations-Pipeline** (`appRobotRendering`), die synthetische oder
|
||||
> aufgezeichnete Bilder liefert und die aktuelle Pose-Erkennung von appRobotHoming nutzen
|
||||
> möchte — ohne den Umweg über echte Kameras und den WebCam-Service.
|
||||
|
||||
---
|
||||
|
||||
@@ -35,7 +37,7 @@ Ergebnisdateien zurückgeben.
|
||||
|---|---|---|
|
||||
| Bilder | WebCam-Service liefert auf Abruf | Caller liefert im Request |
|
||||
| NPZ | Server sucht neueste Session in `data/calibration/` | Caller liefert im Request |
|
||||
| `robot.json` | Server nutzt `ROBOT_JSON`-Env | Caller liefert im Request |
|
||||
| `robot.json` | Server nutzt `robotConfig.js` (Driver oder lokale Cache-Datei) | Caller liefert im Request |
|
||||
| Pipeline (1→2→3b→4b→5) | identisch | identisch |
|
||||
| Antwort | SSE-Stream während Lauf | Synchrones JSON mit allen Ergebnisdateien |
|
||||
| Zweck | Produktions-Homing | Simulation / Replay |
|
||||
@@ -119,6 +121,110 @@ Fehlerresponse immer `{ "error": "…", "log": ["…"] }`.
|
||||
|
||||
---
|
||||
|
||||
## Aufruf-Beispiele
|
||||
|
||||
### curl
|
||||
|
||||
```bash
|
||||
curl -X POST https://thinkcentre.local:2093/api/homing/run-offline \
|
||||
-F "images=@cam0.jpg" \
|
||||
-F "images=@cam1.jpg" \
|
||||
-F "images=@cam2.jpg" \
|
||||
-F "calibrations=@cam0_calibration.npz" \
|
||||
-F "calibrations=@cam1_calibration.npz" \
|
||||
-F "calibrations=@cam2_calibration.npz" \
|
||||
-F "robot=@robot_1781069752019.json;type=application/json" \
|
||||
-F "refSet=A0"
|
||||
```
|
||||
|
||||
Ohne `refSet` (alle Board-Marker als Referenz):
|
||||
```bash
|
||||
curl -X POST https://thinkcentre.local:2093/api/homing/run-offline \
|
||||
-F "images=@cam0.jpg" -F "images=@cam1.jpg" -F "images=@cam2.jpg" \
|
||||
-F "calibrations=@cam0_calibration.npz" -F "calibrations=@cam1_calibration.npz" \
|
||||
-F "calibrations=@cam2_calibration.npz" \
|
||||
-F "robot=@robot_1781069752019.json;type=application/json"
|
||||
```
|
||||
|
||||
### JavaScript / fetch (Browser oder Node)
|
||||
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
|
||||
// Bilder – Dateiname MUSS {cameraId}.jpg sein
|
||||
formData.append('images', cam0Blob, 'cam0.jpg');
|
||||
formData.append('images', cam1Blob, 'cam1.jpg');
|
||||
formData.append('images', cam2Blob, 'cam2.jpg');
|
||||
|
||||
// Kalibrierungen – Dateiname MUSS {cameraId}_calibration.npz sein
|
||||
formData.append('calibrations', cam0NpzBlob, 'cam0_calibration.npz');
|
||||
formData.append('calibrations', cam1NpzBlob, 'cam1_calibration.npz');
|
||||
formData.append('calibrations', cam2NpzBlob, 'cam2_calibration.npz');
|
||||
|
||||
// robot.json
|
||||
formData.append('robot', new Blob([JSON.stringify(robotJson)], { type: 'application/json' }), 'robot.json');
|
||||
|
||||
// optional
|
||||
formData.append('refSet', 'A0');
|
||||
|
||||
const res = await fetch('/api/homing/run-offline', { method: 'POST', body: formData });
|
||||
const data = await res.json();
|
||||
// data.state → { x, y, z, a, b, c, e }
|
||||
// data.files → { "aruco_marker_poses.json": {...}, "state_Arm1.json": {...}, … }
|
||||
// data.log → ["▶ Kameras: cam0, cam1, cam2", …]
|
||||
// data.runDir → "20260618_143022"
|
||||
```
|
||||
|
||||
### Python (requests)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = 'https://thinkcentre.local:2093/api/homing/run-offline'
|
||||
|
||||
files = [
|
||||
('images', ('cam0.jpg', open('cam0.jpg', 'rb'), 'image/jpeg')),
|
||||
('images', ('cam1.jpg', open('cam1.jpg', 'rb'), 'image/jpeg')),
|
||||
('images', ('cam2.jpg', open('cam2.jpg', 'rb'), 'image/jpeg')),
|
||||
('calibrations', ('cam0_calibration.npz', open('cam0_calibration.npz', 'rb'), 'application/octet-stream')),
|
||||
('calibrations', ('cam1_calibration.npz', open('cam1_calibration.npz', 'rb'), 'application/octet-stream')),
|
||||
('calibrations', ('cam2_calibration.npz', open('cam2_calibration.npz', 'rb'), 'application/octet-stream')),
|
||||
('robot', ('robot.json', open('robot.json', 'rb'), 'application/json')),
|
||||
]
|
||||
data = {'refSet': 'A0'} # optional
|
||||
|
||||
resp = requests.post(url, files=files, data=data, verify=False, timeout=120)
|
||||
result = resp.json()
|
||||
print(result['state']) # {'x': 312.4, 'y': 44.8, 'z': -12.1, ...}
|
||||
```
|
||||
|
||||
### Wichtige Regeln für Dateinamen
|
||||
|
||||
| Feld | Pflichtformat | Beispiel |
|
||||
|------|--------------|---------|
|
||||
| `images` | `{cameraId}.jpg` | `cam0.jpg`, `cam1.jpg` |
|
||||
| `calibrations` | `{cameraId}_calibration.npz` | `cam0_calibration.npz` |
|
||||
| `robot` | beliebig (wird intern als `robot_run.json` gespeichert) | `robot.json` |
|
||||
|
||||
Das Pairing Bild ↔ NPZ erfolgt rein über den `cameraId`-Prefix.
|
||||
Eine Kamera ohne passende NPZ wird übersprungen (kein Fehler, aber Log-Eintrag).
|
||||
|
||||
### HTTP-Statuscodes
|
||||
|
||||
| Code | Bedeutung |
|
||||
|------|-----------|
|
||||
| `200` | Erfolg: `{ ok: true, runDir, state, files, log }` |
|
||||
| `400` | Pflichtfelder fehlen oder `robot.json` ist kein valides JSON |
|
||||
| `422` | Pipeline abgebrochen: `aruco_marker_poses.json` nicht erzeugt (< 2 Kamera-Posen) |
|
||||
| `500` | Homing fehlgeschlagen (4b-Kette + Fallback gescheitert) |
|
||||
|
||||
### Timeout-Hinweis
|
||||
|
||||
Die Pipeline kann 20–60 s dauern. Client-Timeout auf **≥ 120 s** setzen
|
||||
(curl: `--max-time 120`, requests: `timeout=120`, nginx/Caddy: `proxy_read_timeout 120s`).
|
||||
|
||||
---
|
||||
|
||||
## Datenfluss (detailliert)
|
||||
|
||||
```
|
||||
@@ -179,7 +285,7 @@ holt, sondern aus dem vorab befüllten `{runDir}`.
|
||||
|
||||
---
|
||||
|
||||
## Umsetzungsplan
|
||||
## Umsetzungsplan (abgeschlossen 2026-06-18)
|
||||
|
||||
### Schritt 1 — `runBoardPipelineOffline(runDir, send, opts)` (Kern)
|
||||
|
||||
@@ -260,7 +366,7 @@ setzen. Alternativ: SSE-Variante (gleiche Daten, aber inkrementell gestreamt).
|
||||
### Schritt 5 — Aufräumen temporärer `robot.json`
|
||||
|
||||
**Was:** Die hochgeladene `robot.json` wird nur für diesen Lauf im `{runDir}` als
|
||||
`robot_run.json` gespeichert. Sie liegt **nicht** an der Stelle von `ROBOT_JSON`
|
||||
`robot_run.json` gespeichert. Sie liegt **nicht** an der Stelle von `robotCachePath` (aus `robotConfig.js`)
|
||||
und wird daher nie vom Live-Modus versehentlich gelesen. Kein Konflikt.
|
||||
|
||||
**Risiko:** keines — rein additive Datei in einem neuen Verzeichnis.
|
||||
@@ -329,15 +435,12 @@ dieser Implementierung).
|
||||
|
||||
---
|
||||
|
||||
## Offene Entscheidungen
|
||||
## Offene Entscheidungen (getroffen)
|
||||
|
||||
- [ ] `multer` hinzufügen (`npm install multer`) oder `busboy` nutzen?
|
||||
- [ ] Offline-Runs in `data/homing-offline/` oder `data/homing/` speichern?
|
||||
(`data/homing/` würde sie im boardViewer sichtbar machen, was praktisch sein kann)
|
||||
- [ ] Timeout-Handling: synchrone Antwort mit Timeout-Header-Hinweis **oder** SSE-Stream
|
||||
wie Live-Modus (Client liest bis `done`-Event)?
|
||||
- [ ] Soll `5_pose_estimation.py` immer laufen (auch nach vollständiger 4b-Kette),
|
||||
oder nur als Fallback wie im Live-Modus?
|
||||
- [x] `multer` (v2.2.0) — installiert, `diskStorage` schreibt direkt in `{runDir}`
|
||||
- [x] Offline-Runs in `data/homing-offline/` (eigenes Verzeichnis, nicht im boardViewer)
|
||||
- [x] Synchrone Antwort — Pipeline läuft durch, dann JSON; Client-Timeout ≥ 120 s empfohlen
|
||||
- [x] `5_pose_estimation.py` nur als Fallback (identisch zum Live-Modus)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user