Kamera: Steuerung von ONVIF-kompatiblen Kameras

Die Kamera-Integration ermöglicht es heute, das Bild einer Kamera, die einen RTSP-Stream veröffentlicht, auf dem Dashboard anzuzeigen und den Videostream zu aktivieren. Man kann auch von einer Szene aus das Bild einer Kamera an Telegram senden. Aber man könnte noch mehr tun :wink:

Wenn eine Kamera ONVIF-kompatibel ist (das ist zum Beispiel bei den Kameras TP-Link Tapo der Fall), ist es technisch möglich, über dieses standardisierte Protokoll die Kamera zu steuern. Siehe Erklärungen hier.

Ich denke, es wäre nützlich in Gladys, auf die folgenden Funktionen von einer Szene oder dem Dashboard aus zugreifen zu können:

  • Kamera aktivieren/deaktivieren
  • Änderung der « horizontalen » Ausrichtung der Kamera (Pan)
  • Änderung der « vertikalen » Ausrichtung der Kamera (Tilt)
  • Änderung des Zooms der Kamera (Zoom)
  • (falls möglich) Positionierung der Kamera in einer ihrer Voreinstellungen (Pan+Tilt+Zoom)
  • Verwaltung der Bewegungserkennung der Kamera (zur Verwendung als Szenenauslöser)
  • Übertragung eines Textes in ‹ Text-to-Speech › auf einer Kamera (indem eine Kamera in der Aktion « Sprechen auf einem Lautsprecher » auswählbar gemacht wird).

Einige Anwendungsbeispiele:

  • Wenn ich das Haus verlasse, aktiviere die Kameras (und umgekehrt, wenn ich nach Hause komme)
  • Jeden Abend, wenn ich abwesend bin, sende mir auf Telegram das Foto meiner Kamera, indem ich nacheinander verschiedene Winkel des Raumes betrachte
  • Im Falle eines Eindringens, sende eine abschreckende Nachricht auf meiner Kamera
  • …

Soweit ich die ONVIF-Norm verstehe, ist es das « Profil S », das zu beachten ist. Die ONVIF-Profile sind hier beschrieben.

Ich weiß nicht, ob dies die Entwicklung unterstützen kann, aber es gibt ein Home Assistant-Plug-in, das auf Github verfügbar ist und das ONVIF-Protokoll für Tapo-Kameras verwaltet: hier

Wer macht uns mal eine kleine externe ONVIF-Integration? :fire:

Ich kann schon mal für TAPO schauen, da ich eine externe Integration für TAPO gemacht habe

Ich habe Claude darauf losgelassen, er hat es schon erledigt. Ich teste heute Abend und sage Bescheid, ob es funktioniert! Bis dahin ist es mir zu heiß, also Pause am Pool für mich.

Gewinne :smiley: Das heißt aber nicht, dass die beiden nicht kompatibel sind, mit Claude Code auf dem Handy :joy:

Das Handy im Wasser etwas weniger :joy:

@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:

  1. 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.
  2. Weiterleitung in DeviceRow.jsx — wiederverwendet PushDeviceFeature und
    MultiLevelDeviceFeature. Macht die Features von der Geräteansicht aus steuerbar für einen sehr geringen Aufwand.
  3. Komponente PtzControl — das Richtungskreuz gruppiert und die Preset-Liste.
  4. Integration in das Kamerawidget — die in 4.2 beschriebene Überlagerung.
  5. 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.

Danke für das Feedback, es ist sehr hilfreich. Ich habe es an Fable zur Analyse und für einen Vorschlag zur Spezifikation und Implementierung weitergeleitet.

Ich halte dich auf dem Laufenden!

Ich habe mit Fable für etwas Präziseres iteriert und die supported_options berücksichtigt, die große Neuheit von Gladys :slight_smile:

Die Spezifikation:

Sag mir, was du davon hältst

Das funktioniert, ich schau mir das heute Abend an.

Das scheint mir nicht schlecht und sogar besser als das, was vorgeschlagen wurde.

Guten Abend @Will_71

Ich habe 4 ONVIF-Kameras (genau dafür gekauft). Zögern Sie nicht, mich bei Bedarf für Tests dieser Integration zu kontaktieren.

Vielen Dank für Ihr Engagement und für die Informationen, die Sie den Gladys-Nutzern zur Verfügung stellen :heart:

Jean

@Will_71 Der PR ist bereit zum Testen!

https://github.com/GladysAssistant/Gladys/pull/2762

Docker-Image:

ghcr.io/gladysassistant/gladys-preview:claude-onvif-camera-spec-foh27o

Ok :+1:.
Ich mache am Wochenende einen Test. Ich halte dich auf dem Laufenden.

@Will_71 Hast du es am Ende testen können? :slight_smile:

Ich sollte es hier machen, aber ich hatte eine unerwartete Sache, ich habe meinen PC gestern nicht angerührt.
Und am ersten Tag frei, fange ich damit an, an meiner Moto zu schrauben, also werde ich etwas mehr Zeit brauchen. Ich mache den Test so schnell wie möglich.

@pierre-gilles Ich habe also die Funktion auf den TAPO-Kameras implementiert.
Hier ist ein erster Rückmeldung:

Wir kommen voran, ich habe zusätzliche Funktionen auf meiner Kamera, darunter die PTZ-Bewegungssteuerung

Allerdings funktioniert es bisher nicht. Ich muss herausfinden, warum, ob es meine Integration oder etwas anderes ist. Das Problem ist, dass ich keine Logs habe, wenn ich auf die Schaltflächen drücke.


Die Steuerung auf dem Kamerabild ist gut, aber die Aktionen darunter sind nicht mehr möglich!

Trop cool, danke, dass du es getestet hast!

Bei den Schaltflächen auf dem Bild sieht es tatsächlich nicht gut aus, was die Steuerung angeht. Bestätigst du das?

Ist es in der Nutzung nicht gut?

Soll ich die Korrekturen im PR vornehmen? :slight_smile:

Ja, dadurch sind die Schaltflächen unter den Pfeilen nicht mehr nutzbar.
Sie sollten nur angezeigt werden, wenn man live ist, denn im Snapshot gibt es keinen Grund, das Kreuz zum Steuern der Kamera zu haben.

@Will_71 Das ist korrigiert :slight_smile: Sag mir, ob es besser ist!