211 lines
5.6 KiB
Markdown
211 lines
5.6 KiB
Markdown
# AppRobotWebcam
|
||
|
||
Webcam-Service für den AppRobot. Liefert Live-MJPEG-Streams und HD-Standbilder
|
||
über einen einzelnen HTTP-Port — als Docker-Container, ohne externe Streaming-Server.
|
||
|
||
## Was es tut
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Live-Stream** | MJPEG multipart im Browser `<img>`, ~139 ms Latenz |
|
||
| **HD-Snapshot** | Ein JPEG pro Kamera auf Knopfdruck oder per HTTP GET |
|
||
| **Snapshot alle** | Alle Kameras parallel in einem Schritt |
|
||
| **REST-API** | Kameraliste, Snapshots, Streams — für andere Container nutzbar |
|
||
|
||
## Kameras (aktuell)
|
||
|
||
| ID | Modell | Live | HD-Grab |
|
||
|---|---|---|---|
|
||
| cam0 | Logitech C270 | 640×480 | 1280×960 |
|
||
| cam1 | Logitech C270 | 640×480 | 1280×960 |
|
||
| cam2 | Logitech C920 | 640×480 | 1920×1080 |
|
||
|
||
Konfiguration ausschliesslich über `cameras.json` — kein Redeploy bei Kamera-Änderungen.
|
||
|
||
## Zugriff
|
||
|
||
```
|
||
http://<host>:8444/ Viewer
|
||
http://<host>:8444/api/stream/cam0 Live-MJPEG
|
||
http://<host>:8444/api/snapshot/cam0 640er JPEG
|
||
http://<host>:8444/api/snapshot/cam0/hires HD-JPEG
|
||
http://<host>:8444/api/cameras Kamera-Metadaten (JSON)
|
||
http://<host>:8444/health Status
|
||
```
|
||
|
||
## API-Referenz
|
||
|
||
Basis-URL: `http://<host>:8444`
|
||
|
||
---
|
||
|
||
### `GET /api/cameras`
|
||
|
||
Vollständige Kamera-Metadaten. Primärer Einstiegspunkt für andere Container.
|
||
|
||
```json
|
||
{
|
||
"cameras": [
|
||
{
|
||
"id": "cam0",
|
||
"name": "Kamera 0",
|
||
"position": "front",
|
||
"stream": true,
|
||
"hires": true,
|
||
"encode": "copybsf",
|
||
"mseCodec": null,
|
||
"note": "usb-046d_0825_3BB3FE20-video-index0",
|
||
"calibrationUrl": "/api/cameras/cam0/calibration"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`calibrationUrl` fehlt, wenn keine `.npz` unter `data/calibration/{id}/` liegt.
|
||
`encode`: `"copybsf"` (MJPEG-Copy, Default) | `"mjpeg"` (Re-Encode) | `"h264"` (GPU, MSE).
|
||
`mseCodec`: nur bei `encode="h264"` gesetzt, z.B. `"avc1.4D001F"`.
|
||
|
||
---
|
||
|
||
### `GET /api/cameras/{id}/calibration`
|
||
|
||
Liefert die `.npz`-Kalibrierungsdatei (Kameramatrix + Verzerrungskoeffizienten) als Binary.
|
||
|
||
```
|
||
Content-Type: application/octet-stream
|
||
Content-Disposition: attachment; filename="cam0_calibration.npz"
|
||
Cache-Control: public, max-age=86400
|
||
```
|
||
|
||
404 wenn keine Kalibrierung vorhanden. Einlesen in Python:
|
||
|
||
```python
|
||
import numpy as np, requests
|
||
d = np.load(requests.get(".../api/cameras/cam0/calibration", stream=True).raw)
|
||
K, D = d["camera_matrix"], d["dist_coeffs"]
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/snapshot/{id}`
|
||
|
||
Letztes Live-JPEG (Live-Auflösung, z.B. 640×480) aus dem RAM-Puffer.
|
||
Bei `stream: false`: one-shot — öffnet Gerät kurz, schließt es wieder.
|
||
|
||
```
|
||
Content-Type: image/jpeg
|
||
X-Camera-Id: cam0
|
||
X-Frame-Width: 640
|
||
X-Timestamp: 2026-06-10T07:30:00.000Z
|
||
Cache-Control: no-store
|
||
```
|
||
|
||
503 wenn kein Frame verfügbar (Kamera nicht erreichbar).
|
||
|
||
---
|
||
|
||
### `GET /api/snapshot/{id}/hires`
|
||
|
||
HD-JPEG (volle Auflösung, z.B. 1280×960 oder 1920×1080).
|
||
Pausiert den Live-Stream kurz, nimmt ein Einzelbild auf, startet Live neu.
|
||
Gleiche Response-Headers wie `/api/snapshot/{id}`.
|
||
|
||
---
|
||
|
||
### `GET /api/stream/{id}`
|
||
|
||
Live-Stream. Format hängt vom `encode`-Modus ab:
|
||
|
||
| encode | Content-Type | Player |
|
||
|---|---|---|
|
||
| `copybsf` / `mjpeg` | `multipart/x-mixed-replace; boundary=frame` | `<img src="...">` |
|
||
| `h264` | `video/mp4` (fragmentiertes MP4) | `<video>` via MSE |
|
||
|
||
Verbindung läuft bis der Client trennt. Langsame Clients droppen Frames (MJPEG)
|
||
bzw. werden getrennt (H.264, um Decode-Lücken zu vermeiden).
|
||
|
||
---
|
||
|
||
### `GET /api/config`
|
||
|
||
Laufzeit-Konfiguration aller Kameras.
|
||
|
||
```json
|
||
{
|
||
"liveSizes": ["320x240", "640x480", "1280x960"],
|
||
"encodes": ["copybsf", "mjpeg", "h264"],
|
||
"cameras": [
|
||
{
|
||
"id": "cam0",
|
||
"name": "Kamera 0",
|
||
"liveSize": "640x480",
|
||
"stream": true,
|
||
"encode": "copybsf"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/config`
|
||
|
||
Ändert Auflösung, Stream-Zustand oder Encoder einer oder mehrerer Kameras.
|
||
Änderungen werden in `cameras.json` gespeichert und sofort aktiv (Hot-Reload).
|
||
|
||
```json
|
||
// Request Body
|
||
{ "cameras": [{ "id": "cam0", "liveSize": "320x240", "stream": true, "encode": "copybsf" }] }
|
||
```
|
||
|
||
Alle Felder ausser `id` sind optional. Antwort: wie `GET /api/config`.
|
||
400 bei ungültiger Auflösung, unbekannter Kamera-ID oder falschem Typ.
|
||
|
||
---
|
||
|
||
### `GET /health`
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"cameras": [
|
||
{ "id": "cam0", "name": "Kamera 0", "device": "/dev/video0", "state": "running", "hasFrame": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
`state`: `"running"` | `"idle"` | `"stopping"` — Zustand des FFmpeg-Prozesses.
|
||
|
||
---
|
||
|
||
## Deploy (Portainer)
|
||
|
||
1. Portainer → Stacks → Web editor → `docker-compose.yaml` einfügen
|
||
2. `APP_PATH` auf den absoluten Pfad des Projektverzeichnisses setzen
|
||
3. Deploy — der Container baut sich selbst (Node + FFmpeg)
|
||
|
||
```yaml
|
||
# Minimal-Konfiguration:
|
||
APP_PATH=/home/user/appRobotWebcam
|
||
```
|
||
|
||
## Architektur
|
||
|
||
```
|
||
cameras.json → server.js → CameraSwitch (/dev/videoN)
|
||
├── Live: ffmpeg → MJPEG → Browser
|
||
└── Grab: Live stoppen → hires → zurück
|
||
```
|
||
|
||
Ein FFmpeg pro Kamera, nie zwei gleichzeitig. Das `close`-Event ist der harte Beweis
|
||
„Gerät frei" — kein Race, kein 106%-CPU-Bug (der mit go2rtc aufgetreten war).
|
||
|
||
## Dokumentation
|
||
|
||
| Datei | Inhalt |
|
||
|---|---|
|
||
| `doc/01_WebcamRoadmap.md` | Ziel, Architektur, Entwicklungsgeschichte |
|
||
| `doc/05_screenShot_roadmap.md` | HD-Grab, Encode-Qualität, Kamera-Eigenheiten |
|
||
| `doc/07_multipleCam_roadmap.md` | cameras.json-Referenz, Multi-Kamera-Setup |
|
||
| `doc/09_Bug_reports.md` | Bug-Dokumentation |
|