10 KiB
Entscheidungsgrundlage – Fokus-Steuerung (C920/C922)
Status: Nicht umgesetzt. Reine Entscheidungsgrundlage, um abzuwägen ob/wie sich das lohnt, bevor Aufwand investiert wird.
Betrifft nur cam2 (laut cameras.json aktuell als C920 dokumentiert, laut Nutzer real
eine C922 – beide haben denselben UVC-Autofokus-Motor, der Ansatz unten gilt für beide
identisch). cam0/cam1 (C270) haben keinen Fokusmotor – Fixed-Focus-Linse, keine
UVC-Focus-Controls vorhanden. Jeder UI-/API-Entwurf muss das abbilden (Capability-Check,
nicht Kamera-ID-Whitelist – falls mal eine C270 gegen eine weitere C920/C922 ausgetauscht
wird, soll es automatisch funktionieren).
Kontext: Was steuert man da eigentlich?
Fokus ist ein UVC-Geräte-Control, kein FFmpeg-Stream-Parameter. Er liegt auf derselben
Ebene wie Belichtung/Weißabgleich – gesetzt per ioctl auf /dev/videoN, unabhängig vom
Capture-Pfad. Zwei relevante Controls (Linux-Namen, via v4l2-ctl --list-ctrls abfragbar):
| Control | Bedeutung |
|---|---|
focus_automatic_continuous |
Autofokus an (1) / aus (0) |
focus_absolute |
manueller Fokuswert, Bereich/Step kameraabhängig (C922 typ. 0–255, Step 5) |
focus_absolute greift nur, wenn focus_automatic_continuous=0 ist – sonst überschreibt
der Autofokus-Regler den Wert laufend wieder.
Wichtiger Unterschied zu allem bisher im Projekt: Bisher hat ausschließlich
CameraSwitch das Gerät geöffnet (FFmpeg, exklusiv gedacht laut Architektur-Doku). Eine
Fokus-Steuerung braucht einen zweiten, kurzlebigen Öffner desselben Devices, während
FFmpeg ggf. gerade live streamt. Ob das auf dieser Hardware klappt, ohne den Live-Stream zu
stören, ist unklar und zwingend zuerst auf dem Host zu prüfen (Memory-Regel: auf Host
messen statt vorhersagen).
Werkzeug-Wahl: v4l2-ctl per spawn, kein neues npm-Package
Konsistent mit dem Rest des Projekts (FFmpeg wird ebenfalls per child_process.spawn
aufgerufen, package.json hat bewusst nur express als Dependency):
v4l2-ctl -d /dev/video4 --list-ctrls # Capability-Check + Wertebereich
v4l2-ctl -d /dev/video4 --set-ctrl=focus_automatic_continuous=0
v4l2-ctl -d /dev/video4 --set-ctrl=focus_absolute=30
v4l2-ctl ist im Image bereits vorhanden (laut Architektur-Doku, dort bisher nur
diagnostisch genutzt). Keine zusätzliche Library, kein zusätzliches Docker-Image-Gewicht.
Alternative wäre eine native Node-V4L2-Bindung (z. B. v4l2-ctrls-artige Pakete) – unnötig,
da v4l2-ctl exakt das tut und schon im Container ist.
Geplanter Aufbau
Neues Modul src/focusControl.js
Trennung reine Logik (Jest-testbar, kein Hardware-Zugriff) von Ausführung, exakt
das Muster aus src/configService.js:
// Reine Logik – kein spawn, kein fs:
function parseListCtrls(stdout) { /* → { focus_automatic_continuous: {min,max,default}, focus_absolute: {min,max,step,default} } | {} wenn keine Focus-Controls */ }
function buildSetArgs(device, { auto, value }) { /* → Array von v4l2-ctl-Aufrufen/Args, auto IMMER vor value gesetzt */ }
function clampFocus(value, caps) { /* auf min/max/step runden */ }
// Ausführung – spawn, Promise-basiert (analog _captureAt-Stil in cameraSwitch.js):
async function probeFocusCaps(device) { /* spawn v4l2-ctl --list-ctrls, parseListCtrls */ }
async function applyFocus(device, { auto, value }) { /* spawn v4l2-ctl --set-ctrl=..., sequenziell */ }
probeFocusCaps liefert {} (kein Fehler) für Geräte ohne Focus-Control → das ist der
Capability-Check, der C270 automatisch ausblendet, ohne Kamera-Modell-Strings zu vergleichen.
Wann proben?
Beim Server-Start, einmal pro Kamera, parallel zu den CameraSwitch-Instanzen (analog
loadCalibrations() in server.js). Ergebnis in camsMeta[i].focusCaps ablegen. Kein
Re-Probe zur Laufzeit nötig (Hardware-Capabilities ändern sich nicht).
Offene Frage, auf dem Host zu klären: Funktioniert --list-ctrls/--set-ctrl
zuverlässig, während CameraSwitch für dieselbe Kamera gerade per FFmpeg streamt (state === 'live')? Falls nicht (z. B. VIDIOC_S_CTRL: Device or resource busy):
- Fallback A: Fokus-Änderungen kurz den Live-Stream pausieren (
_killCurrentAndWait+_spawnLive, exakt der bestehende HD-Grab-Mechanismus, nur ohne Auflösungswechsel). Kostet ein kurzes Stream-Einfrieren (wie bei jedemreconfigure()/HD-Grab schon heute). - Fallback B: Falls selbst das nicht reicht (Gerät wirklich exklusiv), müsste Fokus-Setzen
als neue
CameraSwitch-Methode laufen, die den Lock nutzt statt eines externen Prozesses.
→ Das ist der einzige echte Unsicherheitsfaktor in diesem Plan. Vor Implementierung mit
einem 5-Minuten-Test auf dem Host klären (v4l2-ctl --set-ctrl=focus_absolute=30 während
der Viewer offen ist und cam2 streamt).
Persistenz: cameras.json erweitern
Analog zu liveSize/stream (siehe 12_cameraConfig_roadmap.md) – neue optionale Felder,
nur bei Kameras mit Fokus-Support gesetzt:
{
"id": "cam2",
"...": "...",
"focusAuto": false,
"focusAbsolute": 30
}
Fehlen die Felder (wie aktuell bei allen drei Kameras) → Kamera bleibt im
Werks-/Treiber-Default (meist focus_automatic_continuous=1). Beim Server-Start wird ein
persistierter Wert einmalig angewendet (applyFocus() direkt nach probeFocusCaps(),
bevor CameraSwitch.start() o. On-Demand-Live anläuft) – dann ist der Fokus von Anfang an
korrekt, kein Nachjustieren beim ersten Frame nötig.
API: src/focusService.js (neuer Router, Muster wie configService.js)
GET /api/focus
→ { cameras: [
{ id:"cam0", supported:false },
{ id:"cam1", supported:false },
{ id:"cam2", supported:true, auto:false, value:30, min:0, max:255, step:5 }
] }
POST /api/focus/:id Body: { auto: boolean, value?: number }
→ validiert (id bekannt, supported, value im Bereich) → v4l2-ctl ausführen
(auto zuerst, dann value falls auto:false) → persistiert in cameras.json
(gleiches atomares tmp+rename-Muster wie configService) → 200 mit Ist-Zustand
→ 400 bei unbekannter id / nicht unterstützt / Wert außerhalb Bereich
→ 409 falls Gerät kurzfristig busy (siehe offene Frage oben) – kein Server-500,
Client kann anzeigen „bitte erneut versuchen"
UI: Erweiterung von config.html (kein neues Formular)
Pro Kamera-Zeile, nur wenn supported:true, zusätzliche Spalte:
┌──────────┬──────────────┬────────────────┬───────────────────────────┐
│ cam2 │ Kamera 2 │ [320×240 ▼] │ Fokus: ☑ Auto [────●────] │
└──────────┴──────────────┴────────────────┴───────────────────────────┘
- Checkbox „Auto" (
focus_automatic_continuous) – an = Slider deaktiviert/ausgegraut. - Slider (
focus_absolute,min/max/stepausGET /api/focus) – nur aktiv wenn Auto aus. Live-Vorschau (Slider-Drag → Debounce ~300 ms →POST /api/focus/:id), kein separater „Anwenden"-Button nötig, da Fokus anders als Auflösung keinen Stream-Restart auslösen sollte (siehe offene Frage – falls Fallback A nötig wird, kurzes Einfrieren pro Drag-Schritt einplanen, dann doch debounce auf ~800 ms hochsetzen). - Für cam0/cam1: Spalte entweder ausgeblendet oder Platzhaltertext „kein Autofokus (C270)".
Test-Strategie
Jest (ohne Hardware), analog configValidate.test.js/configMerge.test.js:
parseListCtrls: echtev4l2-ctl --list-ctrls-Textbeispiele (mit und ohne Focus-Controls) → korrektes Capability-Objekt bzw.{}.clampFocus: Werte außerhalb Bereich, Werte nicht aufstep-Raster.buildSetArgs: Reihenfolge garantiert auto-vor-value;auto:truesetzt KEINfocus_absolutemit.- Validierung im Router: unbekannte id,
supported:false, Wert außerhalb Bereich.
Host-Test (zwingend vor jeder UI-Arbeit):
v4l2-ctl -d /dev/video4 --list-ctrls→ Bestätigen, dass C922 (cam2) Focus-Controls meldet und C270s (cam0/cam1) keine.- Die offene Frage von oben:
--set-ctrlwährend cam2 live streamt testen. - Sichtprüfung: Schärfentest-Chart oder Textseite vor die Kamera halten,
focus_absoluteüber den gesamten Bereich durchfahren, im Viewer beobachten ob sich die Schärfe ändert (bestätigt, dass die Werte überhaupt etwas bewirken – manche UVC-Geräte melden Controls, die der Treiber dann ignoriert).
Abgrenzung (bewusst NICHT Teil dieses Plans)
- C270: keine Fokus-Funktion – Hardware kann es nicht, kein Workaround sinnvoll (digitales Nachschärfen ist kein echter Fokus, nur ein Filter auf bereits unscharfem Bild).
- Spot-/Touch-Autofokus (auf einen Bildbereich tippen): UVC kennt das nicht, das ist proprietäre Logitech-Software-Logik (Logi Capture u. ä.), nicht über v4l2 erreichbar.
- Automatisches Nachfokussieren bei Auflösungswechsel: Fokus ist ein reiner
Geräte-/Linsenzustand, unabhängig von
liveSize/hiresSize– ändert sich durchreconfigure()oder HD-Grab nicht, daher keine Interaktion mit dem bestehenden Live/Grab-State-Machine-Code nötig (außer ggf. Fallback A oben). - Authentifizierung auf dem neuen Endpoint: gleiche Linie wie
/api/config– erst bei geplantem Internet-Zugang.
Aufwandsschätzung (grob, zur Priorisierung)
| Teil | Aufwand |
|---|---|
| Host-Test der offenen Frage (busy-Verhalten) | 15 Min |
src/focusControl.js (Logik + Jest) |
klein (~1–2h) |
src/focusService.js (Router + Jest) |
klein (~1h) |
server.js-Integration (Probe beim Start, Apply persistierter Werte) |
klein |
config.html/config.js-UI-Erweiterung |
klein–mittel (~1–2h) |
| Gesamt | ein halber bis ein Tag, abhängig vom Ergebnis des Host-Tests |
Empfehlung: Lohnt sich – kleiner, klar abgegrenzter Scope, nutzt bestehende Muster 1:1 (configService als Vorlage). Einziges Risiko ist der Busy-Test; der ist in 15 Minuten geklärt und entscheidet nur zwischen Plan A (einfach) und Fallback A (geringfügig komplexer, aber bereits vorhandener Mechanismus).