Die API des smart-home-control Servers wird von allen Gerätesteuerungen und Interfaces/Apps verwendet, die im Smart Home angebunden sind. Sie basiert auf Socket.IO, das selbst wiederum auf WebSockets aufbaut. Damit können, anders als bei HTTP, asynchron in beide Richtungen jederzeit Nachrichten geschickt werden.
Alle Nachrichten in Socket.IO werden mit socket.emit()
verschickt. Das erste Argument ist die Art von Event/Nachricht, die geschickt werden soll. Danach folgen alle zugehörigen Argumente. Als letztes kann optional ein Callback gegeben werden, über den der Empfänger direkt auf den Sender antworten kann. Zum Beispiel:
socket.emit("cache:update", fname, url, callback) // JS
socket.emit("cache:update", (fname, url), callback=callback) # Python
Nachrichten gehen immer vom Client zum smart-home-control Server oder umgekehrt, nie direkt zwischen den Clients. Nachrichten vom Client zum Server können vom Server aber an einen oder mehrere Clients weitergeleitet werden.
Kanäle
Zur Trennung der Funktionen im Smart Home, ist ein wesentlicher Teil des smart-home-control Servers die Bereitstellung von Kanälen. Es gibt spezielle Kanäle des Server (z.B. status
oder rollen
), gerätespezifische Kanäle (z.B. mikrofon
) und Plugin-Kanäle (z.B. sturzalarm
).
An sich kann jeder Client Daten in einem Kanal lesen und schreiben, oder Event darin senden und empfangen. In der Praxis hat aber jeder Kanal einen (bzw. in Ausnahmefällen mehrere, z.B. beim Kühlschrank) Verantwortlichen. Das kann der Server selbst, eine Gerätesteuerung, oder ein Plugin sein. Der Verantwortliche schreibt bzw. updatet Daten im Kanal und stellt Events bereit. Alle anderen Clients lesen die Daten aus einem Kanal und lösen Events aus.
Konkret schaut das zum Beispiel so aus (als exemplarisches Beispiel):
Client 1:
|
Client 2:
|
Dokumentation
Die Dokumentation der API und Kanäle ist in zwei Teile aufgeteilt:
- smart-home-control: Server API und spezielle Kanäle
- smart-home-control APIs: APIs der Gerätesteuerungen und Plugins
Beispiele
Steckdosen mit Sprachsteuerung umschalten
Ein häufiger Vorhang im Smart Home ist das Ein-/Ausschalten von Steckdosen. Gesteuert werden die Steckdosen von der Gerätesteuerung für alle Shelly Geräte. Um aber Priorierung zu ermöglichen (z.B.: Die Sprachsteuerung kann das automatische Ausschalten des Küchenlichts überschreiben, wenn man die Küche verlässt), gibt es ein eigenes Plugin, das die Steckdosen nochmal zusätzlich abstrahiert.
In diesem Beispiel geht es erstmal nur darum, wie die Sprachsteuerung das Küchenlicht über die zugehörige Steckdose einschalten kann. Der relevante Kanal zum Mikrofon ist mikrofon
. Spricht man ins Mikrofon, wird der aktuell erkannte Text in Echtzeit in diesem Kanal geupdated:
{
"text": "Schalte das", // Text wird kontinierlich während des Sprechens geupdated
"ausgefuehrt": false,
// ... Weitere Felder, die uns hier nicht interessieren
}
Updates erfolgen mithilfe der API Funktion put
, also:
socket.emit("put", "mikrofon", {"text": "Schalte das", ...})
Bei jedem Update werden alle Clients benachrichtigt, die diesen Kanal abboniert haben. Zum Abbonieren gibt es die API Funktion sub
:
socket.emit("sub", "mikrofon");
socket.on("mikrofon", (data) => { /* Handler für geupdatete Mikrofondaten */ });
Im konrekten Beispiel ist ein Handler in der Konfiguration (config.js
) gegeben. Einfache Wenn-Dann Verhalten können darin etwas einfacher deklariert werden. Zum Beispiel muss man sich dort nicht um das Abbonieren kümmern. Der Handler wird also bei jeder Änderung der Daten im Kanal mikrofon
ausgeführt. Jedes mal überprüft er den Text auf Stichwörter. Kommen die Wörter "Küche" und "an" oder "ein" vor, wird das Küchenlicht angeschalten.
Dazu wird über die API das Event steckdosen:anschalten
ausgelöst. Das Event ist wie eine Art Funktion des Steckdosen Plugins. Als Argumente werden die genaue Steckdose, sowie die Priorität angegeben (hier: Sprachsteuerung):
socket.emit("steckdosen:anschalten", "kueche-s1", "sprachsteuerung");
Der Server empfängt das Event, und leitet es an alle Clients, die den Kanal steckdosen
abboniert haben, weiter. Dazu gehört das Steckdosen Plugin, das wie ein Client an das Smart Home angebunden ist. Es reagiert (als einziges) auf das Event, und schaltet über die Gerätesteuerung der Shellies die Steckdose an.
Gleichzeitig zum steckdosen:anschalten
Event wird im obigen Handler aus der Konfigurationsdatei das Event mikrofon:ausgefuehrtSetzen
ausgelöst, das zurück an die Gerätesteuerung des Mikrofons geht:
socket.emit("mikrofon:ausgefuehrtSetzen", data.satz_id) // Die Satz-ID ist in den Daten gegeben
Daraufhin bleibt das Feld ausgefuehrt
im Kanal mikrofon
solange auf true, bis von der Sprachsteuerung der nächste Satz erkannt wurde. So wird sichergestellt, dass kein Sprachbefehl zweimal ausgeführt wird.
Sturzalarm auslösen
Als etwas umfangreicheres Beispiel, wie ein Ablauf im Smart Home aussieht und wie die Kommunikation über die API abläuft: Das Auslösen eines Sturzalarms. Das Beispiel soll verschiedene Arten von Vorgängen zeigen, die im Smart Home möglich sind. Es hat deshalb so viele Schritte, da mehrere Komponenten beteiligt sind, die miteinander nur über die API kommunizieren. Auf diese Art können einzelne Komponenten (wie z.B. die Push-API) auch in anderen Vorgängen wiederverwendet werden.
Verwendet werden drei Kanäle mit Daten: sensfloor
und sturzkamera
von den jeweiligen Gerätesteuerungen, und sturzalarm
vom Sturzalarm Plugin. Zu Beginn sind folgende Daten für die Kanäle hinterlegt:
-
Kanal
sensfloor
:{ "an": true, "aktivitaet": false, "sturzalarm": false, // Dieses Feld interessiert uns, der Rest ist hier nicht relevant "bereiche": { "irgendwo": false, "kueche": false }, "verbundenCareApi": true, "verbundenRoomApi": true }
-
Kanal
sturzkamera
:{ "bild": "/cache/sturzkamera.jpg?1664182443968", // Dieses Feld interessiert uns hier "letzter_timestamp": 1664182443847, "modus": "standby", "fehler": false }
-
Kanal
sturzalarm
:{ "alarm": false, "bild": null }
Ablauf:
- Der SensFloor erkennt einen Sturzalarm. Über seine eigene API schickt er ein Event an die Gerätesteuerung des SensFloors, die als Node.JS Skript auf dem zentralen Raspberry Pi läuft.
- Die Gerätesteuerung setzt das Feld
sturzalarm
im Kanalsensfloor
auf true:socket.emit("put", "sensfloor", {"sturzalarm": true})
- In der Smart Home Konfiguration (
config.js
) ist ein sehr einfacher Ereignishandler für den Fall, dass sich Daten im Kanalsensfloor
verändern. Wird dortsturzalarm
auf true gesetzt, wird das Eventsturzalarm:ausloesen
an den Server geschickt:
socket.emit("sturzalarm:ausloesen")
- Der Server schickt das Event weiter an alle Clients, die im Kanal
sturzalarm
sind (anhand des Prefixsturzalarm:
), unter anderem das Sturzalarm Plugin und die Sturzkamera. - (A) Das Sturzalarm Plugin macht zwei Sachen:
- Es updated seine Daten im
sturzalarm
Kanal ¹:socket.emit("put", "sturzalarm", {"alarm": true})
- Es löst eine Push-Benachrichtigung aus, indem es das Event
push-api:senden
mit den Details als Argumenten auslöst.
socket.emit("push-api:senden", msg)
Dieses Event wird vom PushAPI Plugin abgefangen, das dann die Benachrichtigungen sendet.
- Es updated seine Daten im
- (B) Gleichzeitig macht die Sturzkamera ein Bild, was einen kurzen Moment dauert.
- Sobald das Bild gemacht wurde, wird es mit einer speziellen Funktion des Servers auf diesen übertragen. Der Schritt ist notwendig, damit die App von extern auch darauf zugreifen kann, ohne Zugriff auf das interne Smart Home Netzwerk zu haben. Außerdem könnte die direkte Verbindung zur Sturzkamera manchmal zu langsam sein:
socket.emit("cache:update", fname, url, callback)
- Über den Callback benachrichtigt der Server die Sturzkamera, dass die Übertragung abgeschlossen wurde. Die Sturzkamera updated dann ihre Daten im Kanal mit einem Pfad, wo das Bild abgerufen werden kann:
socket.emit("put", "sturzkamera", {"bild": "<pfad>", ...})
- Sobald das Bild gemacht wurde, wird es mit einer speziellen Funktion des Servers auf diesen übertragen. Der Schritt ist notwendig, damit die App von extern auch darauf zugreifen kann, ohne Zugriff auf das interne Smart Home Netzwerk zu haben. Außerdem könnte die direkte Verbindung zur Sturzkamera manchmal zu langsam sein:
- Das Sturzalarm Plugin bekommt das neue Bild mit, weil es den Kanal von der Sturzkamera abboniert hat (
socket.emit("sub", "sturzkamera"); socket.on("sturzkamera", () => {...});
). Es updated das Feld bild in seinem eigenen Kanal, sodass auch die App das Bild erhalten kann.
¹ Der Grund warum die Information zum Sturzalarm doppelt im sensfloor
und sturzalarm
Kanal ist, ist dass man so die Zugriffsrechte besser einschränken kann. Die App braucht so nur Zugriff auf den sturzalarm
Kanal. Das gleiche gilt auch für den sturzkamera
Kanal.
Spezielle Abläufe
Sessions
Ein wesentliches Element, um die Komplexität etwas zu reduzieren, sind Sessions/Sitzungen. Damit können Client erkennen, wenn der smart-home-control Server neustartet, und sich dann selbst auch neustarten. Zum einen macht es das einfacher, das Smart Home zurückzusetzen. Zum anderen vereinfacht sich so das Synchronisierungsproblem beim Neustart eines Clients oder des Servers. So kann der Client davon ausgehen, dass nur zu seinem allerersten Verbindungsaufbau alle Daten zum Server synchronisiert werden müssen.
In der API ist das über session
Nachrichten des Servers umgesetzt.
Gerätesteuerungen
Einen zusätzlichen Schritt gegenüber anderen Clients beim Verbindungsaufbau haben die Gerätesteuerungen. Sie melden sich mit ihrer Geräte-ID beim Server an. Sie erhalten auf diese Art ihre Konfiguration (die zentral beim Server in einer Datei liegt), und außerdem weiß der Server so, dass sie verbunden sind (was im Gerätestatus angezeigt wird). Pro Geräte-ID kann nur eine Gerätesteuerung verbunden sein.