WebCam als schlanke alternative zum appVideoControl

This commit is contained in:
chk
2026-06-02 22:19:08 +02:00
commit 9b1ae2ae14
11 changed files with 842 additions and 0 deletions

152
doc/01_WebcamRoadmap.md Normal file
View File

@@ -0,0 +1,152 @@
# AppRobotWebcam Roadmap
## Ziel
Sauberer, fokussierter Webcam-Streaming-Service als Docker-Container.
Kein Robot-Control, kein ArUco nur zwei Verantwortlichkeiten:
1. **Live-Video** mit minimaler Latenz (MJPEG über WebSocket)
2. **Standbilder** auf Abruf via HTTP REST (`/api/snapshot/cam{n}`)
Das Homing-Projekt holt seine Standbilder über den Snapshot-Endpunkt keine weitere Kopplung nötig.
---
## Architektur
```
USB Kameras
FFmpeg (v4l2 → MJPEG)
Node.js Server (Express + ws)
├── WebSocket /ws/cam0, /ws/cam1 → Browser (Live-Stream)
└── HTTP GET /api/snapshot/cam0 → Homing-Projekt (JPEG)
```
**Stack-Entscheide (bereits umgesetzt):**
| Komponente | Wahl | Begründung |
|------------|------|------------|
| Webserver | Node.js + Express | Wartbar, grosses Ecosystem, user-präferiert |
| WebSocket | `ws`-Library | Schlank, bewährt, kein Overhead |
| Video-Capture | FFmpeg | Stabil, flexibel, MJPEG-passthrough möglich |
| Stream-Protokoll | MJPEG über WebSocket | Geringste Latenz, einfach im Browser |
| Snapshot-API | HTTP GET → raw JPEG | Einfachste Schnittstelle für Consumer |
| Container | `dockerfile_inline` in docker-compose | Kein separates Dockerfile, Portainer-tauglich |
---
## Tasks
### Phase 1 Grundgerüst ✅ (erledigt)
- [x] Projektstruktur anlegen
- [x] `package.json` mit Abhängigkeiten (express, ws)
- [x] `docker-compose.yaml` mit `dockerfile_inline` (Node.js + FFmpeg, kein separates Dockerfile)
- [x] `server.js` HTTP + WebSocket-Server, Graceful Shutdown
- [x] `src/deviceDetect.js` Kamera-Erkennung (env → by-id → /dev/video*)
- [x] `src/videoStream.js` FFmpegStreamer (MJPEG splitten, WebSocket broadcast, Auto-Restart mit Backoff)
- [x] `src/snapshotService.js` REST-Endpunkt: aktuellstes Frame aus laufendem Stream
- [x] `public/index.html` + `public/viewer.js` Basis-Viewer
### Phase 2 Deployment & Latenz-Baseline
- [ ] **Kamera-Zugriff im Container** verifizieren:
- `/dev/video0` und `/dev/video2` im Container sichtbar
- `group_add: video` greift (Zugriffsrechte)
- Fallback auf YUYV422 wenn MJPEG nicht unterstützt
- [ ] **Latenz messen** (Baseline):
- Uhr auf Kamera richten, Screenshot → Differenz ablesen
- Zielwert: <100 ms Ende-zu-Ende (Kamera → Browser-Pixel)
- [ ] **Multi-Format-Fallback** implementieren: MJPEG → YUYV422 → RGB24
- [ ] **Health-Endpunkt** `/health` erweitern: Kamera-Status, verbundene Clients, FPS
### Phase 3 Latenz optimieren
- [ ] **FFmpeg-Flags tunen** für minimale Latenz:
```
-fflags nobuffer -flags low_delay -probesize 32 -analyzeduration 0
```
- [ ] **Native MJPEG pass-through** testen:
Wenn Kamera MJPEG nativ bei Zielauflösung liefert → `-vcodec copy`
(kein Re-Encoding, minimale CPU-Last, minimale Latenz)
- [ ] **Canvas-Rendering** im Browser: `createImageBitmap()` statt Blob-URL-Overhead
- [ ] **WebRTC evaluieren**: <50 ms möglich, aber STUN/TURN-Komplexität in Docker
sinnvoll erst wenn MJPEG-Latenz >150 ms bleibt
### Phase 4 Hochauflösende Snapshots
**Aktuell**: Snapshot = letztes Frame aus dem Stream (Auflösung = Stream-Auflösung).
**Ziel**: Snapshot in originaler Kamera-Auflösung (z.B. 1280×960).
Drei Optionen (noch offen):
| Option | Vorgehen | Pro | Contra |
|--------|----------|-----|--------|
| A | Stream-Frame direkt nehmen | Sofort, kein Aufwand | Auflösung = Stream-Auflösung |
| B | Zweite FFmpeg-Pipeline 0.5 FPS High-Res | Immer verfügbar | CPU-Last, pipe:3 Komplexität |
| C | Einmaliger `ffmpeg -frames:v 1` on-demand | Hohe Qualität | ~500 ms Delay, Stream-Unterbrechung |
- [ ] Option wählen und implementieren
- [ ] Mit Homing-Projekt testen (Consumer von `/api/snapshot`)
- [ ] Snapshot-Metadaten in Response-Headern: Zeitstempel, Auflösung, Kamera-ID
- [ ] Optionaler Webhook: POST nach Snapshot an konfigurierbaren Endpunkt
### Phase 5 Robustheit & Produktion
- [ ] Kamera hot-plug: Stream-Neustart wenn `/dev/videoX` verschwindet/wiederkommt
- [ ] Resource Limits: `--memory 512m --cpus 1.0` in docker-compose
- [ ] HTTPS: Reverse Proxy (nginx/traefik) vorschalten kein TLS im App-Code
- [ ] JSON-Logging mit Level (info/warn/error)
- [ ] Kamera-Parameter per env var: `CAM0_WIDTH`, `CAM0_HEIGHT`, `CAM0_FPS`, `CAM0_QUALITY`
---
## Abgrenzung zu appRobotVideoControls
| Feature | appRobotVideoControls | appRobotWebcam |
|---------|-----------------------|----------------|
| Video-Streaming | ✅ | ✅ (verbessert) |
| Snapshots | ✅ (komplex, dual-pipe) | ✅ (HTTP REST, einfach) |
| Robot-Control (G-Code) | ✅ | ❌ anderes Projekt |
| ArUco / Homing | ✅ (Python+OpenCV) | ❌ anderes Projekt |
| Gamepad / Keyboard | ✅ | ❌ |
| HTTPS (self-signed) | ✅ | ❌ (Reverse Proxy empfohlen) |
| Separates Dockerfile | ✅ (gross, OpenCV-Build) | ❌ (inline in compose) |
---
## Ports & Netzwerk
| Service | Container-Port | Host-Port |
|---------|---------------|-----------|
| HTTP + WS | 8080 | 8444 |
```bash
# Netzwerk einmalig erstellen (falls noch nicht vorhanden)
docker network create appRobotNet
```
---
## Datei-Struktur
```
appRobotWebcam/
├── src/
│ ├── deviceDetect.js Kamera-Erkennung (env → by-id → /dev/video*)
│ ├── videoStream.js FFmpeg-MJPEG-Streamer + WebSocket-Broadcast
│ └── snapshotService.js REST-Router für /api/snapshot
├── public/
│ ├── index.html Basis-Viewer
│ └── viewer.js WebSocket-Client, MJPEG-Rendering
├── doc/
│ ├── 01_WebcamRoadmap.md (diese Datei)
│ └── 05_OptionalToDo_roadmap.md Control-Optionen
├── docker-compose.yaml Einzige Docker-Datei (dockerfile_inline)
├── package.json
└── server.js Einstiegspunkt
```

View File

@@ -0,0 +1,87 @@
## Roadmap - Optionale Punkte ##
---
## Control integrieren
Das aktuelle `appRobotVideoControls` koppelt zwei unabhängige Verantwortlichkeiten:
1. **Video-Streaming** (USB-Kameras → Browser)
2. **Robot-Control** (Browser → G-Code → Robot-Server)
### Warum wir trennen
| Problem | Auswirkung |
|---------|------------|
| Control-Code-Änderung → Video-Server-Restart | Stream unterbricht, Operator verliert Bild |
| Grosser Docker-Container | OpenCV + Python + FFmpeg + Control-Libs |
| Schwer testbar | Video-Stream nicht einfach simulierbar |
| Latenz-Konflikte | Video-Tuning ≠ Control-Anforderungen |
| Robot-Server offline | Video-Server wirft Fehler |
### Vorteil der Kopplung
Ein Browser-Tab für alles: Video + Steuerung zusammen sehr bequem für den Operator.
---
### Optionen für Control
**Option A: Getrennt bleiben (empfohlen für Phase 12)**
```
appRobotWebcam → Port 8444 (nur Video + Snapshots)
appRobotControl → Port 8445 (nur G-Code, Gamepad, Keyboard → Robot)
```
- Browser öffnet beide als Tabs oder iframe-Dashboard
- Nachteil: Zwei Services deployen und warten
**Option B: Control als optionales Modul in appRobotWebcam**
- Env var `ENABLE_CONTROL=true/false` schaltet Control-Code ein/aus
- Control-Code ist in der Codebase, aber inaktiv wenn nicht konfiguriert
- Nachteil: Code-Komplexität steigt, trotzdem ein Container
**Option C: Vollintegration wie bisher**
- Alles in einem Container wie `appRobotVideoControls`
- Einfachste UX, einfachstes Deployment
- Nachteil: Monolith, schwer zu warten und zu testen
### Empfehlung
Für Phase 12: **Option A** sauber trennen.
Wenn UX zum Problem wird (Operator-Feedback): **Option B** evaluieren.
Option C nur wenn Deployment-Einfachheit absolut dominiert.
---
## Homing-Projekt Anbindung
Das Homing-Projekt braucht Standbilder der Kameras für ArUco-Erkennung.
**Schnittstelle**: `GET /api/snapshot/cam{n}` → JPEG-Bild
`appRobotWebcam` liefert genau das. Keine weitere Kopplung nötig.
Das Homing-Projekt ist Consumer, `appRobotWebcam` ist Producer.
```
Homing-Projekt
└── HTTP GET http://approbotwebcam:8080/api/snapshot/cam0
└── HTTP GET http://approbotwebcam:8080/api/snapshot/cam1
ArUco-Erkennung → Kamera-Pose → Weltkoordinaten
```
---
## Dashboard / UI Konsolidierung
Wenn mehrere Services (Video, Control, Homing-Status) parallel laufen:
- **Option**: Leichtes Dashboard als eigene statische Seite (nginx) mit iframes
- **Option**: Portainer-UI zeigt alle Container-Status
- **Option**: Control-Panel in `appRobotWebcam` einbetten (Option B oben)
Dies ist bewusst aufgeschoben erst wenn klar ist welche Services produktiv laufen.