Umbau mit cameraSwitch Dokumentation
This commit is contained in:
@@ -1,7 +1,31 @@
|
||||
> # ⚙ ARCHITEKTUR-UPDATE (2026-06-05) — der Plan wird durch den Node-MJPEG-Schalter EINFACHER
|
||||
>
|
||||
> Diese Roadmap wurde für den **go2rtc**-Aufbau geschrieben. Der ist seit 2026-06-05 ersetzt:
|
||||
> **Node besitzt alle Kameras selbst** (`src/cameraSwitch.js`, eine `CameraSwitch` pro Gerät),
|
||||
> go2rtc ist weg. Das ändert mehrere Grundannahmen — überwiegend zugunsten weniger Aufwand:
|
||||
>
|
||||
> | Roadmap-Annahme (alt) | Neue Realität | Folge für den Plan |
|
||||
> |-----------------------|---------------|--------------------|
|
||||
> | Zwei Kamera-Klassen (`stream:true` via go2rtc, `stream:false` via eigenem FFmpeg) | **Eine** Klasse: jede Kamera ist eine `CameraSwitch` mit On-Demand | **Phase 2 (separater one-shot-Pfad) entfällt** — `getFrame()`/`grabHires()` gelten für alle |
|
||||
> | go2rtc-Config parallel pflegen (Redeploy je Kamera) | nur `cameras.json` → erzeugt `CameraSwitch`-Instanzen | Phase 1 wird einfacher, **keine Doppelpflege** |
|
||||
> | „~25 % CPU pro Live-Stream, dauerhaft" | **On-Demand: 0 % idle**, ~35 %/Kamera **nur während aktiv beobachtet** | „2–3 live" kostet nur was, wenn wirklich jemand zuschaut |
|
||||
> | HD-Grab = „Phase-2-Dance" (Consumer release → cam_hires) | `grabHires()` (Live stoppen → 1280 → zurück), **fertig** | Phase-2-Dance-Beschreibungen sind überholt |
|
||||
> | ffmpeg im Node-Container „noch zu ergänzen" | **bereits drin** (+ Geräte durchgereicht) | Phase-2-Voraussetzung schon erfüllt |
|
||||
> | `stream: false` = „kein Live möglich" | jede Kamera *kann* live (On-Demand); `stream:false` = **nur Viewer zeigt keine Live-Box** | reine Anzeige-Entscheidung, kein anderer Grab-Pfad |
|
||||
>
|
||||
> **Netto:** Phasen 1, 3, 4 bleiben sinnvoll (cameras.json/Metadaten, „Snapshot alle", WebService).
|
||||
> **Phase 2 ist großteils erledigt/obsolet.** USB-Bandbreite (unten) gilt unverändert.
|
||||
> Details der Architektur: `05_screenShot_roadmap.md` (Abschnitt „Node-MJPEG-Schalter").
|
||||
> Die folgenden Abschnitte sind als Konzept erhalten; go2rtc-spezifische Stellen sind
|
||||
> inline mit ⚠ markiert.
|
||||
|
||||
---
|
||||
|
||||
# AppRobotWebcam – Multiple Kameras
|
||||
|
||||
> Status: **Konzept**. Aktuelle Implementierung: 2 Streaming-Kameras + HD-Grab via Phase 2
|
||||
> (`05_screenShot_roadmap.md`). Diese Datei beschreibt den Ausbau auf bis zu 10 Kameras.
|
||||
> Status: **Konzept** (teils überholt, s. Banner oben). Aktuelle Implementierung: Node-MJPEG-
|
||||
> Schalter, alle Kameras On-Demand, HD-Grab via `grabHires()` (`05_screenShot_roadmap.md`).
|
||||
> Diese Datei beschreibt den Ausbau auf bis zu 10 Kameras.
|
||||
|
||||
---
|
||||
|
||||
@@ -18,22 +42,29 @@
|
||||
|
||||
---
|
||||
|
||||
## Grundproblem: Zwei Kamera-Klassen
|
||||
## Grundproblem: ~~Zwei Kamera-Klassen~~ → EINE Klasse (aktualisiert 2026-06-05)
|
||||
|
||||
Eine USB-Kamera kann gleichzeitig nur **einmal** geöffnet werden. go2rtc hält
|
||||
Streaming-Kameras offen. Für Nicht-Streaming-Kameras steht das Gerät dagegen
|
||||
jederzeit frei. Daraus ergeben sich zwei Klassen mit unterschiedlichem Grab-Pfad:
|
||||
Eine USB-Kamera kann gleichzeitig nur **einmal** geöffnet werden — das bleibt der
|
||||
harte Constraint. ⚠ **Die frühere Zwei-Klassen-Aufteilung ist mit dem Node-Schalter
|
||||
hinfällig:** Jede Kamera ist eine `CameraSwitch`, die das Gerät **on-demand** öffnet.
|
||||
Es gibt **einen** Grab-Pfad für alle:
|
||||
|
||||
| Klasse | `stream: true` | `stream: false` |
|
||||
| | `stream: true` | `stream: false` |
|
||||
|---|---|---|
|
||||
| In go2rtc-Config | ja (`cam_front`, `cam_front_hires`) | nein |
|
||||
| Live-View im Viewer | ja | nein (nur Snapshot-Symbol) |
|
||||
| Hires-Grab | Phase-2-Dance (release → cam_hires) | FFmpeg one-shot direkt |
|
||||
| CPU im Idle | ~25 % (solange Client verbunden) | 0 % |
|
||||
| Grab-Komplexität | hoch | niedrig |
|
||||
| Was ist es | `CameraSwitch` (wie alle) | `CameraSwitch` (wie alle) |
|
||||
| Live-View im Viewer | ja (Live-Box) | nein (nur Snapshot-Symbol) — **reine Anzeige-Wahl** |
|
||||
| Snapshot 640 | `getFrame()` (startet Gerät on-demand) | identisch `getFrame()` |
|
||||
| Hires 1280 | `grabHires()` | identisch `grabHires()` |
|
||||
| CPU im Idle | **0 %** (On-Demand, niemand schaut) | **0 %** |
|
||||
| CPU aktiv | ~35 %/Kamera nur solange beobachtet/gegrabbt | nur während eines Grabs (~2 s) |
|
||||
|
||||
**Faustregel:** Kameras, die permanent beobachtet werden müssen → `stream: true`.
|
||||
Kameras, die nur beim Homing / Trigger relevant sind → `stream: false`.
|
||||
**`stream` steuert jetzt nur, ob der Viewer eine Live-Box aufmacht** — nicht mehr den
|
||||
Grab-Mechanismus. Das alte „Phase-2-Dance" und der separate FFmpeg-one-shot-Pfad
|
||||
entfallen; der Schalter macht das einheitlich.
|
||||
|
||||
**Faustregel (unverändert sinnvoll):** dauernd zu beobachtende Kameras → `stream: true`
|
||||
(Live-Box); nur beim Homing/Trigger relevante → `stream: false` (spart Viewer-Last und
|
||||
Bandbreite, da kein Dauer-`<img>`).
|
||||
|
||||
---
|
||||
|
||||
@@ -83,7 +114,7 @@ Statt hardcodierter Gerätenamen eine maschinenlesbare Liste:
|
||||
| `device` | string | `/dev/videoN` — oder besser persistenter Pfad (s.u.) |
|
||||
| `name` | string | Anzeigename im Viewer |
|
||||
| `position` | string | frei; hilfreich für Homing-Auswertung |
|
||||
| `stream` | bool | `true` → Live-Stream in go2rtc; `false` → nur Snapshot |
|
||||
| `stream` | bool | `true` → Viewer zeigt Live-Box; `false` → nur Snapshot-Symbol (Grab-Pfad identisch, On-Demand) |
|
||||
| `hires` | bool | `false` → nur 640er-Snapshot verfügbar (z.B. bei alter Kamera) |
|
||||
| `note` | string | Freitext, erscheint nicht im Viewer |
|
||||
|
||||
@@ -100,26 +131,23 @@ Symlinks auf `/dev/videoN` — einmal prüfen, dann in `cameras.json` eintragen.
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Überblick
|
||||
## Architektur-Überblick (aktualisiert 2026-06-05)
|
||||
|
||||
```
|
||||
cameras.json
|
||||
│
|
||||
├── stream: true → go2rtc-Config (cam_front, cam_front_hires, …)
|
||||
│ │
|
||||
│ └── Live-View (Browser WS → go2rtc :1984)
|
||||
│ Hires-Grab (Phase-2-Dance)
|
||||
│
|
||||
└── stream: false → kein go2rtc-Eintrag
|
||||
│
|
||||
└── Hires-Grab (FFmpeg one-shot direkt, Node.js)
|
||||
Kein Live-View
|
||||
└── server.js erzeugt je Eintrag EINE CameraSwitch (besitzt /dev/videoN, On-Demand)
|
||||
│
|
||||
├── stream:true → Viewer zeigt Live-Box → GET /api/stream/:id (MJPEG multipart)
|
||||
└── stream:false → Viewer zeigt nur Snapshot-Symbol (kein Dauer-<img>)
|
||||
(Grab-Pfad für beide identisch — getFrame / grabHires)
|
||||
|
||||
Node.js / Express :8444
|
||||
├── GET /api/cameras → Liste aus cameras.json (mit Metadaten)
|
||||
├── GET /api/snapshot/:id → 640er JPEG (streaming: via go2rtc; non-streaming: one-shot)
|
||||
├── GET /api/snapshot/:id/hires → 1280er JPEG (streaming: Phase-2; non-streaming: one-shot)
|
||||
└── POST /api/snapshot/all → alle Kameras grabben, JSON-Antwort mit Metadaten [Phase 4]
|
||||
Node.js / Express :8444 (go2rtc ENTFERNT)
|
||||
├── GET /api/cameras → Liste aus cameras.json (mit Metadaten) [Phase 4A]
|
||||
├── GET /api/stream/:id → MJPEG multipart/x-mixed-replace (Live, On-Demand)
|
||||
├── GET /api/snapshot/:id → 640er JPEG (getFrame – startet Gerät bei Bedarf)
|
||||
├── GET /api/snapshot/:id/hires → 1280er JPEG (grabHires – Live kurz pausieren)
|
||||
└── POST /api/snapshot/all → alle Kameras grabben, JSON mit Metadaten [Phase 4B]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -155,12 +183,13 @@ Kein funktionaler Unterschied, aber Grundlage für alles Folgende.
|
||||
|
||||
**`server.js`**
|
||||
- `cameras.json` beim Start laden, validieren
|
||||
- go2rtc-Config-Block in `docker-compose.yaml` weiterhin manuell pflegen
|
||||
(Redeploy nötig bei Kamera-Änderung — akzeptiert, da Infrastruktur)
|
||||
- Je Eintrag eine `CameraSwitch` erzeugen (ersetzt das heutige `detectDevices()`-Mapping).
|
||||
⚠ **Keine go2rtc-Config mehr** — die Kamera-Definition lebt nur noch in `cameras.json`.
|
||||
Geräte müssen weiterhin in `docker-compose.yaml` durchgereicht werden (`devices:`).
|
||||
|
||||
**`src/snapshotService.js`**
|
||||
- `GET /api/snapshot` → liest Kamera-Metadaten aus `cameras.json` statt aus go2rtc
|
||||
- Gefilterte Liste (kein `_hires`) bleibt; Felder `name`, `position`, `stream` mitliefern
|
||||
- `GET /api/snapshot` → liest Kamera-Metadaten aus `cameras.json` (Felder `name`,
|
||||
`position`, `stream` mitliefern). Die Switch-Map kommt aus `server.js`.
|
||||
|
||||
**`public/viewer.js`**
|
||||
- Kamera-Box zeigt `name` + `position` statt rohem `id`
|
||||
@@ -173,9 +202,15 @@ Kein funktionaler Unterschied, aber Grundlage für alles Folgende.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 — Snapshot-only-Kameras (FFmpeg one-shot)
|
||||
### Phase 2 — Snapshot-only-Kameras (FFmpeg one-shot) — ⚠ GRÖSSTENTEILS OBSOLET
|
||||
|
||||
**Ziel:** Kameras mit `stream: false` können Snapshots liefern, ohne go2rtc zu berühren.
|
||||
> **Hinweis (2026-06-05):** Mit dem Node-Schalter ist dieser separate Pfad **nicht mehr
|
||||
> nötig.** Jede `CameraSwitch` macht Snapshots on-demand (`getFrame`/`grabHires`) — egal ob
|
||||
> `stream:true` oder `false`. ffmpeg im Container + Geräte-Durchreichung sind bereits erledigt.
|
||||
> Der einzige offene Teil aus dieser Phase: bei `stream:false` im Viewer **keine** Live-Box
|
||||
> rendern. Der untenstehende one-shot-Plan ist nur noch historischer Kontext.
|
||||
|
||||
**Ziel (alt):** Kameras mit `stream: false` können Snapshots liefern, ohne go2rtc zu berühren.
|
||||
|
||||
**Voraussetzung:** `ffmpeg` im Node-Container verfügbar.
|
||||
|
||||
@@ -223,10 +258,10 @@ nicht kollidieren.
|
||||
|
||||
**Ziel:** Ein Button-Klick erzeugt Bilder **aller** Kameras gleichzeitig.
|
||||
|
||||
**Streaming-Kameras** (`stream: true`): bisheriger paralleler Phase-2-Dance (bereits implementiert).
|
||||
|
||||
**Snapshot-only-Kameras** (`stream: false`): direkter FFmpeg one-shot, kein „Release" nötig
|
||||
→ können parallel zu den Streaming-Grabs laufen (verschiedene Geräte).
|
||||
⚠ **Vereinfacht (2026-06-05):** kein Unterschied mehr zwischen den Klassen. **Alle** Kameras
|
||||
nutzen `grabHires()` (Live kurz pausieren → 1280 → zurück). Da jede `CameraSwitch` ihr
|
||||
eigenes Gerät besitzt, laufen die Grabs gefahrlos parallel — genau das macht
|
||||
`snapshotAllHires()` im Viewer heute schon.
|
||||
|
||||
**`public/viewer.js` — `snapshotAllHires()` erweitern:**
|
||||
```
|
||||
@@ -334,8 +369,8 @@ synchron warten kann und keine grossen Bilder überträgt.
|
||||
| Gerätenamen `/dev/videoN` wechseln nach Reboot | mittel | persistente by-id-Pfade in `cameras.json` |
|
||||
| USB-Bandbreite bei >4 Kameras gleichzeitig | mittel | separate USB-Controller; `lsusb -t` prüfen |
|
||||
| ffmpeg im Node-Container (Phase 2) | niedrig | einmalige Dockerfile-Änderung; bewährt in `04_*` |
|
||||
| go2rtc-Config bei >3 Streaming-Kameras | CPU | max. 2–3 `stream: true`; Rest `stream: false` |
|
||||
| Warmup-Schwarzbild bei Snapshot-only (Phase 2) | bekannt | `select=gte(n,15)` bewährt aus `04_*` |
|
||||
| CPU bei vielen Kameras | niedriger als gedacht | On-Demand: nur **gleichzeitig beobachtete** Streams kosten CPU (~35 %/Kam). Idle = 0 %. Grenze ist die Zahl der parallel offenen Live-Views + USB-Bandbreite, nicht die Gesamtzahl der Kameras |
|
||||
| Warmup-Schwarzbild bei Hi-Res | bekannt, gelöst | `CameraSwitch.grabHires` verwirft die ersten Frames (`settleFrames`/`minSize`) |
|
||||
| Parallele Grabs auf gleichem Gerät | beherrschbar | Mutex pro Device (nicht pro ID) |
|
||||
| Job-Queue Phase 4B bei mehreren Clients | gering | für Single-Operator akzeptiert; sonst persistente Queue |
|
||||
|
||||
@@ -344,15 +379,16 @@ synchron warten kann und keine grossen Bilder überträgt.
|
||||
## Empfohlene Reihenfolge
|
||||
|
||||
```
|
||||
Phase 1 (cameras.json) ~2 h Grundlage, kein Risiko
|
||||
Phase 1 (cameras.json + Switch-Erzeugung) ~2 h Grundlage, kein Risiko
|
||||
↓
|
||||
Phase 2 (Snapshot-only, ffmpeg) ~3 h ffmpeg-Abhängigkeit klären
|
||||
Phase 2 (Snapshot-only, ffmpeg) ~0 h ⚠ erledigt durch Schalter; nur noch:
|
||||
Viewer rendert bei stream:false keine Live-Box
|
||||
↓
|
||||
Phase 3 (Snapshot alle erweitert) ~1 h baut auf Phase 1+2
|
||||
Phase 3 (Snapshot alle erweitert) ~1 h Logik existiert (snapshotAllHires), nur Liste erweitern
|
||||
↓
|
||||
Phase 4A (GET /api/cameras) ~1 h sofort nützlich für andere Container
|
||||
Phase 4A (GET /api/cameras) ~1 h sofort nützlich für andere Container
|
||||
↓
|
||||
Phase 4B oder 4C ~3–4 h nur wenn Push-Trigger gebraucht wird
|
||||
Phase 4B oder 4C ~3–4 h nur wenn Push-Trigger gebraucht wird
|
||||
```
|
||||
|
||||
Phase 4B/C **erst wenn** ein konkreter aufrufender Container existiert —
|
||||
|
||||
Reference in New Issue
Block a user