@pierre-gilles Ein Vorschlag von claude zur Änderung des SDK und des Kerns für die Verwaltung von ONVIF-, PTZ-Kameras…
Vorschlag: PTZ-Steuerung von Kameras in Gladys
Dieses Dokument schlägt die Hinzufügung der PTZ-Steuerung (pan / tilt / zoom) von Kameras zu Gladys vor:
Konstanten, die zum SDK hinzugefügt werden müssen, wie eine Anweisung die Integration erreicht,
und ein Steuerungs-Widget auf dem Dashboard.
Es wurde aus einer bestehenden externen Integration — gladys-tapo —
verfasst, die bereits ONVIF mit TP-Link Tapo-Kameras spricht und für die PTZ als
verfügbar auf Seiten der Kamera gemessen wird, aber nicht ausdrückbar auf Seiten von Gladys.
1. Der Bedarf
Eine motorisierte Kamera (Tapo C210, C500, TC70 und die meisten ONVIF-Kameras auf dem Markt)
kann drei Dinge tun, die keine Kategorie von Gladys heute abdeckt:
- sich in eine Richtung bewegen, mehr oder weniger schnell und mehr oder weniger weit;
- eine gespeicherte Position („Eingang“, „Garten“) erreichen;
- anhalten.
Die entsprechenden Smart-Home-Anwendungen sind klassisch:
- „Wenn jemand an der Tür klingelt, schaut die Wohnzimmerkamera zum Eingang“;
- „Nachts dreht sich die Kamera zum Tor; morgens kehrt sie zurück“;
- Steuerung der Kamera per Hand vom Dashboard aus, ohne die App des Herstellers zu öffnen.
Was heute blockiert
DEVICE_FEATURE_TYPES.CAMERA enthält nur einen Eintrag:
CAMERA: {
IMAGE: 'image',
},
Eine Gladys-Kamera ist daher konstruktionsbedingt eine nur lesbare Bildquelle.
Keine bestehende Kategorie eignet sich, um dies zu umgehen:
| In Betracht gezogene Option |
Warum sie verworfen wird |
CURTAIN.POSITION für Pan |
Zeigt einen Rollladen an einer Kamera an; semantisch falsch und blockiert die spätere Hinzufügung eines echten PTZ |
SWITCH.BINARY pro Richtung |
Vier Schalter für ein Richtungskreuz; trägt weder Geschwindigkeit noch Entfernung |
| Aktionen des Manifests |
Funktioniert, aber Aktionen sind nicht in einer Szene verwendbar — und das ist der Hauptanwendungsfall |
Der Zweck dieses Vorschlags ist daher, die fehlende Kategorie hinzuzufügen, anstatt eine zu missbrauchen.
2. Strukturelle Einschränkung: setValue transportiert nur einen Skalar
Dies ist der Punkt, der das gesamte Design bestimmt, und er sollte vor den Konstanten gestellt werden.
Ein Befehl startet an der Schnittstelle, durchquert den Kern und erreicht die Integration über
device.setValue:
// server/lib/device/device.setValue.js
async function setValue(device, deviceFeature, value, options = {}) {
const service = this.serviceManager.getService(device.service.name);
await service.device.setValue(device, deviceFeature, value, options);
// ...
}
value ist ein Skalar — eine Zahl oder eine Zeichenkette. Die angeforderte PTZ-Steuerung
beinhaltet jedoch acht Parameter:
| Parameter |
Werte |
| Pan |
LEFT, RIGHT |
| Tilt |
UP, DOWN |
| Zoom |
ZOOM_IN, ZOOM_OUT |
| Distance |
Bewegungsfaktor, von 0 bis 1 |
| Speed |
Geschwindigkeitsfaktor, von 0 bis 1 |
| Move Mode |
ContinuousMove, RelativeMove, AbsoluteMove, GotoPreset, Stop |
| Continuous duration |
Für ContinuousMove, die Dauer in Sekunden vor dem Anhalten |
| Preset |
Der Token des Presets, das mit GotoPreset erreicht werden soll |
Diese acht Parameter passen nicht in einen Skalar. Drei Lösungsmöglichkeiten:
Option A — Ein Feature pro Befehl, die Einstellungen als Geräteparameter
Jede Richtung wird zu einem Feature vom Typ push, und distance / speed /
continuous duration werden zu Geräteparametern (device.params),
die einmal bei der Konfiguration eingestellt werden.
camera/ptz-left push → bewegt sich nach links, mit den Geräteeinstellungen
camera/ptz-right push
camera/ptz-up push
camera/ptz-down push
camera/ptz-zoom-in push
camera/ptz-zoom-out push
camera/ptz-stop push
camera/ptz-preset string → der Token des Presets, das erreicht werden soll
Pro: Erfordert keine Änderungen am Kern. PushDeviceFeature existiert bereits und rendert
einen Button; Szenen können bereits ein push-Feature auslösen. Sofort implementierbar.
Contra: Geschwindigkeit und Entfernung sind nicht mehr per Befehl einstellbar — eine Szene kann nicht sagen „drehe langsam“. Sieben Features für ein einziges Gerät belasten die Liste.
Option B — Ein einziges Feature, das einen serialisierten Befehl trägt
Ein einziges Feature camera/ptz, vom Typ string, dessen Wert ein JSON ist:
{ "mode": "ContinuousMove", "pan": "LEFT", "speed": 0.5, "duration": 2 }
Pro: Abdeckt die acht Parameter, ohne den Kern zu ändern — setValue akzeptiert bereits
Zeichenketten (und persistiert sie nicht, was gut ist: ein Befehl ist kein Zustand).
Contra: Undurchsichtig. Die Schnittstelle kann kein Formular aus einer freien Zeichenkette erstellen, und der Szeneneditor würde ein Textfeld anzeigen, in das der Benutzer JSON eingeben müsste. Es ist eine API für Entwickler, nicht für den Endbenutzer.
Option C — setValue mit benannten Parametern erweitern (empfohlen)
setValue erhält bereits ein Objekt options, das es unverändert an den Dienst weitergibt. Es reicht aus,
dies für die sekundären Parameter zu verwenden, wobei value den Hauptbefehl trägt.
// Die Integration erhält:
setValue(device, feature, 'LEFT', { speed: 0.5, distance: 0.3, duration: 2 });
Pro: Abdeckt die acht Parameter, behält einen lesbaren Hauptwert (also anzeigbar
und scriptbar) und führt keine Brüche ein — options existiert und wird bereits weitergegeben.
Die Schnittstelle kann ein echtes Formular erstellen, da jeder Parameter benannt und typisiert ist.
Contra: Erfordert die Definition, welche options für jeden Feature-Typ gültig sind, und dass
der Szeneneditor sie anzeigen kann.
Empfehlung: Streben Sie C an, indem Sie A als ersten Schritt liefern. A ist
sofort implementierbar und deckt bereits „gehe zur Position X, wenn Y eintritt“ ab,
was der häufigste Anwendungsfall ist; C fügt dann die Feinabstimmung hinzu, ohne A zu ungültig zu machen.
3. Konstanten, die zum SDK hinzuzufügen sind
Zu ergänzen in server/utils/constants.js von Gladys, dann in
lib/device-constants.js des SDK — letzteres ist, gemäß der dokumentierten Konvention am
Anfang der Datei, ein strenger Spiegel des ersten.
3.1 Feature-Typen
CAMERA: {
IMAGE: 'image',
// --- PTZ: Steuerung einer motorisierten Kamera ---
// Richtungen. Der Name trägt die Achse, nicht die vom
// Protokoll erwartete Bewegungsrichtung: Eine an der Decke montierte Kamera kann eine
// invertierte Achse haben, was in der Integration und nicht in der Semantik des Features
// eingestellt wird.
PTZ_LEFT: 'ptz-left',
PTZ_RIGHT: 'ptz-right',
PTZ_UP: 'ptz-up',
PTZ_DOWN: 'ptz-down',
PTZ_ZOOM_IN: 'ptz-zoom-in',
PTZ_ZOOM_OUT: 'ptz-zoom-out',
// Stoppen einer kontinuierlichen Bewegung. Unverzichtbar und nicht nur praktisch:
// Ein ContinuousMove ohne Stop lässt die Kamera bis zu ihrem Anschlag drehen.
PTZ_STOP: 'ptz-stop',
// Gespeicherte Position, die erreicht werden soll. Der Wert ist der Token des Presets, wie
// die Kamera ihn nennt, niemals ein Index: ONVIF-Kameras geben undurchsichtige Token
// zurück und keine geordnete Liste.
PTZ_PRESET: 'ptz-preset',
// Absolute Position, für Kameras, die sie melden können. Getrennt von den
// Richtungen, weil sie lesbar UND beschreibbar ist, während eine Richtung nur ein Befehl ist.
PTZ_POSITION_PAN: 'ptz-position-pan',
PTZ_POSITION_TILT: 'ptz-position-tilt',
},
3.2 Vorhandener Code
Die Ergänzung folgt einem bereits vorhandenen Muster: TELEVISION trägt LEFT, RIGHT, UP, DOWN,
STOP als separate Feature-Typen, genau um ein Kreuz zu ausdrücken
direktional.
TELEVISION: {
// ...
LEFT: 'left',
RIGHT: 'right',
UP: 'up',
DOWN: 'down',
// ...
},
Der Vorschlag schafft also keinen Präzedenzfall: Er wendet diesen auf Kameras an und ergänzt ihn
mit dem, was PTZ zusätzlich erfordert (Presets, Stopp, absolute Position).
3.3 Parameterwerte (Option C)
Wenn Option C gewählt wird, benötigen die Koeffizienten einen expliziten Bereich:
const PTZ_MOVE_MODES = {
CONTINUOUS: 'ContinuousMove',
RELATIVE: 'RelativeMove',
ABSOLUTE: 'AbsoluteMove',
GOTO_PRESET: 'GotoPreset',
STOP: 'Stop',
};
// `speed` und `distance` sind Koeffizienten von 0 bis 1, absichtlich ohne Einheit:
// eine Kamera drückt ihre Geschwindigkeit in Grad pro Sekunde aus, eine andere in Motorschritten, und
// keine dokumentiert dies. Der Koeffizient ist die einzige portable Größe, und
// die Integration übersetzt ihn in das, was ihr Protokoll erwartet.
const PTZ_COEFFICIENT_MIN = 0;
const PTZ_COEFFICIENT_MAX = 1;
Die Modi nach der ONVIF-Terminologie zu benennen, ist absichtlich: Es ist der Standardvokabular, das die
mehrzahl der Kameras implementieren, und es zu übersetzen würde nur eine
Übersetzungsschicht einführen, die gepflegt werden müsste.
4. Kamerasteuerungs-Widget
4.1 Bestehendes Widget erweitern, statt ein zweites zu erstellen
Gladys hat bereits ein Kamerawidget (front/src/components/boxs/camera/Camera.jsx), das
Bild und, für kompatible Kameras, den Live-Stream anzeigt.
Ein separates PTZ-Widget würde den Benutzer zwingen, zwei Boxen nebeneinander für eine
Kamera zu platzieren und sie ausgerichtet zu halten. Der Vorschlag ist daher, die
Steuerungen zum bestehenden Widget hinzuzufügen, die nur angezeigt werden, wenn das Gerät PTZ-Features hat.
4.2 Vorgeschlagene Anordnung
┌─────────────────────────────────┐
│ │
│ Bild / Live │
│ │
│ ┌───┐ │ ← Überlagerung, untere rechte Ecke
│ │ ▲ │ │
│ ┌───┼───┼───┐ │
│ │ ◄ │ ■ │ ► │ │ ■ = Stopp
│ └───┼───┼───┘ │
│ │ ▼ │ │
│ └───┘ │
│ [Eingang ▾ ] [-] [+] │ ← Presets Zoom
└─────────────────────────────────┘
Designpunkte, jeder motiviert:
- Überlagert, nicht darunter. Das Kamerawidget wird oft in kleinem Format platziert; eine
zusätzliche Zeile mit Schaltflächen unter dem Bild würde die Höhe verbrauchen, die gerade
dazu dient, das Bild zu sehen.
- Steuerungen standardmäßig ausgeblendet, beim Überfahren sichtbar (und immer sichtbar bei Berührung, wo es kein Überfahren gibt). Ein Dashboard, das auf einen Blick betrachtet wird, benötigt nicht
acht Schaltflächen dauerhaft.
- Gedrückte Taste = kontinuierliche Bewegung.
mousedown löst die Richtung aus, mouseup
löst PTZ_STOP aus. Das ist die Geste, die jeder von Kameraschnittstellen kennt,
und es entspricht genau dem Paar ContinuousMove / Stop.
Ein einfacher Klick fällt auf ein RelativeMove eines Schrittes zurück.
- Presets in einer Dropdown-Liste, nicht als Schaltflächen: Ihre Anzahl variiert von Kamera zu Kamera und ihre Namen sind frei.
- Zoom getrennt vom Richtungskreuz, da nicht alle motorisierten Kameras zoomen
— die Schaltflächen erscheinen nur, wenn die entsprechenden Features vorhanden sind.
4.3 Darstellung der Features außerhalb des Widgets
Unabhängig vom Widget erscheinen die PTZ-Features in der Ansicht „Gerät in einem Raum“. Die
Weiterleitung erfolgt in front/src/components/boxs/device-in-room/DeviceRow.jsx:
const ROW_TYPE_BY_FEATURE_TYPE = {
// ...
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_LEFT]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_RIGHT]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_UP]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_DOWN]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_ZOOM_IN]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_ZOOM_OUT]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_STOP]: PushDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_POSITION_PAN]: MultiLevelDeviceFeature,
[DEVICE_FEATURE_TYPES.CAMERA.PTZ_POSITION_TILT]: MultiLevelDeviceFeature,
};
PushDeviceFeature und MultiLevelDeviceFeature existieren bereits: sieben der neun Typen
benötigen also keine neuen Komponenten. Nur PTZ_PRESET erfordert eine — eine Dropdown-Liste, die mit den Presets der Kamera gefüllt wird — und eine Komponente PtzControl, die das Richtungskreuz gruppiert, wäre wünschenswert, um zu vermeiden, sieben Zeilen mit Schaltflächen
überschichtet anzuzeigen.
Hinweis: Ein Feature, dessen Typ nicht in dieser Tabelle steht, scheitert nicht, es wird als Sensor dargestellt. Die PTZ-Features wären also sichtbar, aber nicht steuerbar, solange die Weiterleitung nicht hinzugefügt wird — was es ermöglicht, das SDK und die Schnittstelle separat zu liefern.
5. Verwendung in Szenen
Das ist der Hauptvorteil gegenüber den Integrationsaktionen, die nicht
skriptfähig sind.
Mit Option A schreibt sich eine Szene „jemand klingelt → die Kamera schaut zur Tür“ mit
der bestehenden Aktion „Ändere den Zustand eines Geräts“, indem camera/ptz-preset auf den Token des Presets eingestellt wird. Keine neue Szenenaktion ist notwendig.
Mit Option C gewinnt der Szeneneditor, die benannten Parameter (Geschwindigkeit,
Distanz, Dauer) im Formular dieser Aktion vorzuschlagen, anstatt sie den Geräteeinstellungen zu überlassen.
6. Vorgeschlagene Aufteilung
Jeder Schritt hat einen eigenen Wert und kann allein geliefert werden:
- Konstanten im SDK und im Kern — die Typen
CAMERA.PTZ_*. Ohne sichtbare Wirkung,
aber sofortige Freigabe der Integrationen: Eine Integration kann die Features veröffentlichen und über die API gesteuert werden, bevor die Schnittstelle weiß, wie sie sie anzuzeigen hat.
- Weiterleitung in
DeviceRow.jsx — wiederverwendet PushDeviceFeature und
MultiLevelDeviceFeature. Macht die Features von der Geräteansicht aus steuerbar für einen sehr geringen Aufwand.
- Komponente
PtzControl — das Richtungskreuz gruppiert und die Preset-Liste.
- Integration in das Kamerawidget — die in 4.2 beschriebene Überlagerung.
- Benannte Parameter (Option C) — Geschwindigkeit, Distanz, Dauer pro Befehl und ihre Darstellung im Szeneneditor.
Die Schritte 1 und 2 reichen aus, um PTZ von vorne bis hinten nutzbar zu machen, Szenen eingeschlossen.