Files
appRobotWebcam/README.md
2026-06-10 07:56:33 +02:00

211 lines
5.6 KiB
Markdown
Raw 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.
# 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 |