Mit Shelly Skripten lässt sich der Fundus an möglichen Anwendungen erheblich erweitern. Skripte reagieren ausschließlich auf Ereignisse. Solche Ereignisse können das Eintreffen von Messwerten, die Änderung eines Ausgangs, das Betätigen eines Schalters bzw. Tasters oder das Auftreten eines bestimmten Zeitpunktes sein. Letzteres unterliegt einer Zeitsteuerung, wozu Schedule Jobs (frei übersetzt: Zeitplan Aufträge) prädestiniert sind.

Die von der Web UI (Web basiertes User Interface, Mensch-Maschinen-Schnittstelle auf der Grundlage einer Webseite) bereitgestellten Möglichkeiten, einen Schedule Job anzulegen, sind jedoch (noch) stark eingeschränkt.

Die Möglichkeiten für solche Schedule Jobs erschließen sich erst mit dem Prinzip der Methoden, die per unterschiedlicher Kommunikationsschnittstellen genutzt werden können, zumeist kurz als API bezeichnet (Application Programming Interface). Die letztlich wirkenden Methoden sind aus unterschiedlichen Umgebungen heraus aktivierbar. Solche Umgebungen sind bspw. ein Skript, ein URL (Adresszeile in einem Web Browser) oder, besonders interessant, ein Schedule Job.

Das Anlegen eines Zeitplans kann per Web UI sehr einfach gelingen, was dann aber sehr eingeschränkte Funktionalitäten bietet. Bspw. per URL lassen sich erheblich mehr Möglichkeiten für Schedule Jobs nutzen, was ich hier beispielhaft erläutern will.

Anlass war ein Thread in einem Forum. Ein Anwender möchte eine Außenbeleuchtung um 22:00 Uhr ausschalten lassen und den angeschlossenen Schalter oder Bewegungsmelder sperren. Das genaue Anliegen mag etwas komplizierter sein, was hier unerheblich ist.

Hierfür kann folgender Schedule Job (in JSON Syntax) genutzt werden:

{
"enable": true,
"timespec": "0 0 22 * * *",
"calls": [
{
"method": "Input.SetConfig",
  "params": {
    "id": 0,
    "config":{"enable": false}
  }
},
{
"method": "Switch.Set",
  "params": {
    "id": 0,
    "on": false
    }
}
]
}

Dieser Job nutzt zwei Methoden - das Ändern der Konfiguration eines Eingangs ("Input.SetConfig") und gleichzeitig das Setzen eines Ausgangszustandes ("Switch.Set"). Er sperrt um 22:00 jeden Tages die Wirkung des Signals an Eingang 0 und schaltet zugleich den Ausgang auf aus. Einen solchen Job anzulegen, gelingt nicht mit der Web UI, aber per URL.

Der dafür geeignete komplette URL lautet so:

http://<IP Adresse Shelly>/rpc/Schedule.Create?timespec="0 0 22 * * *"&calls=[{"method":"Input.SetConfig","params":{"id":0,"config":{"enable":false}}},{"method":"Switch.Set","params":{"id":0,"on":false}}]

Da die Zusammenstellung eines Jobs per URL und das Testen des Jobs nicht trivial sind, empfehle ich hierfür folgende Vorgehensweise.

  1. Man lege den Schedule Job mit enable=false und einem simplen timspec="0 * * * * *" (0 und 5 Asterisk, je ein Leerzeichen dazwischen) an. Wesentlich sind hier die Einträge hinter calls=, darauf muss man sich konzentrieren.
    Z.B. <IP Adresse>/rpc/Schedule.Create?enable=false&timespec="0 * * * * *"&calls=[{"method":"Input.SetConfig","params":{"id":0,"config":{"enable":false}}},{"method":"Switch.Set","params":{"id":0,"on":false}}]
    Zeigt der Browser eine Fehlermeldung, stimmt etwas mit der Syntax zum anlegen des Jobs nicht und muss korrigiert wiederholt werden. War die Eintragung erfolgreich, wird etwas ausgegeben wie { "id": 4, "rev": 6 }. Hier hat der neue Job die Id 4 erhalten und die sechste Änderung wurde vorgenommen.
    Die führende 0 in timespec dient dazu, dass jede volle Minute der Job ausgeführt wird, nachdem dieser bspw. per Web UI enabled wurde.

  2. Evaluationsschritt: Zur Prüfung nutze man im Browser <IP Adresse>/rpc/Schedule.List . Nun zeigt der Browser alle eingetragenen Schedule Jobs an (Edge und Firefox tun dies übersichtlich).
    Nach sorgfältiger Prüfung aktiviert man den neuen Job in der Web UI - Schiebeschaltersymbol rechts.
    Wirkung: Jede volle Minute (Sekunde 0) wird der Job abgearbeitet. Nun kann man zwischendurch per Web UI oder Schalter/Taster manuell Änderungen vornehmen wie Einschalten, Ausschalten, Eingang sperren/freigeben ... und beobachten, ob der Job das tut, was er tun soll.

  3. Wenn alles so läuft, wie es soll, den Job deaktivieren - weiter mit 4. Wenn nicht, die Ursache suchen, den Job löschen und ihn verbessert neu anlegen - weiter mit 2.
    Statt löschen und neu anlegen kann man einen existierenden Job per .../rpc/Schedule.Update?<Job Id>&enable=false&calls=[...] abändern. Dabei darf man außer der Job Id Teile weglassen, wie bspw. timespec. Dann bleiben solche Teile im Job unverändert.

  4. Nun die gewünschten Zeiten per Web UI einstellen, den Job aktivieren ... und sich hoffentlich freuen. ;)

Ist die Zeitspanne von einer Minute für die Evaluation zu kurz, verwende man folgenden timespec: "0 */5 * * * *" zum auslösen des Jobs alle 5 Minuten, für alle 3 Minuten entsprechend die 5 durch 3 ersetzen ... Diese kleine Änderung kann auch per Web UI vorgenommen werden, zumindest in der Version 1.2.0-beta1 und höher.

Nach einem Firmware Update auf Version 1.2.0 stable zeigt die Seite mit den Schedules alles richtig an. Es gibt nun keinen Hinweis mehr auf irgendwelche Ungültigkeiten - vermutlich nur dann, wenn der entsprechende Job syntaktisch richtig eingetragen wurde. Dies wird meiner Erfahrung nach per RPC Methoden Schedule.Create und Schedule.Update geprüft, weshalb alle solche Jobs syntaktisch fehlerfrei vorliegen müssten. Nach wie vor lassen sich die Jobs per Web UI nicht frei formulieren/editieren, weshalb meine Webseite https://tools.eichelsdoerfer.net/schedjob.html noch immer eine nützliche Ergänzung ist.

Mögliche Unterstützung per Skript

Wem das Eintragen solcher Schedule Jobs zu fehlerträchtig, zu mühsam oder zu aufwändig im testen erscheint, der kann Unterstützung per Skript nutzen. Siehe hierzu auch meine Skripteinführung.

Zum obigen Beispiel:

Man lege ein Skript an oder ergänze ein vorhandenes, genutztes entsprechend. Darin wird die folgende Funktionsdefinition gebraucht.

function input0Disable() {
  Shelly.call("Input.SetConfig", {id:0, config:{enable:false}});
  Shelly.call("Switch.Set", {id:0, on:false});
  // something else, whatever you want
}

Nachdem man das Skript gestartet, per Konsoleneingabe input0Disable() getestet und als geeignet befunden hat - evtl. sind weitere Tests nach Änderungen erforderlich, kann man entweder den Schedule Job per URL anlegen oder dies mit der bereits erwähnten Webseite https://tools.eichelsdoerfer.net/schedjob.html bewerkstelligen.

Der URL: http://<IP Adresse Shelly>/rpc/schedule.create?enable=false&timespec="0 0 22 * * *"&calls=[{"method":"script.eval","params":{"id":<Skript Id>,"code":"input0Disable()"}}]

Nach einer Prüfung per Methode Schedule.List kann der Schedule Job auf der Web UI freigegeben werden. Zwecks Funktionstüchtigkeit muss selbstverständlich das Skript gestartet sein, da der Schedule Job nur die Funktion input0Disable() aufruft. Folgende andere Varianten sind hierbei möglich und durchaus empfehlenswert.

  1. Name der Funktion passend anders wählen, bspw. sunrise().
  2. Parameter verwenden, sowohl in der Funktionsdefinition (formal) als auch im Schedule Job (aktual), bspw. sunrise(0) - der Parameter legt sowohl die Input  Id als auch die Output Id als 0 fest. Dazu muss die Funktionsdefinition von sunrise() wie folgt lauten.
    function sunrise(id) {
      Shelly.call("Input.SetConfig", {id:id, config:{enable:false}});
      Shelly.call("Switch.Set", {id:id, on:false});
      // something else, whatever you want
    }

Eine solche Implementation braucht zwar ein Skript, bietet aber interessante Vorteile gegenüber der skriptlosen Implementation.

  1. Sowohl im Skript als auch selbstverständlich im Schedule Job kann ein vielsagender Funktionsname verwendet werden.
  2. Im Fall einer gewünschten Änderung (Softwarepflege), kann diese relativ leicht implementiert werden (hohe Servicefreundlichkeit). Dazu bleibt der Schedule Job wie er ist bzw. die Triggerzeiten können leicht per Web UI angepasst werden, ohne am "calls" Eintrag komplizierte Änderungen vorzunehmen.
  3. Die Funktion kann leicht getestet werden, indem man in der Konsole die Funktion aufruft, was jederzeit gelingt.

Es gibt aber auch einen Nachteil.
Falls einer von evtl. mehreren Schedule Jobs einen Fehler im Funktionsnamen, bspw. sunise(...) statt sunrise(...). hat, wird das Skript mit einer Fehlermeldung abgebrochen. Dies führt aber auch dazu, den Fehler zu suchen, kann somit auch als Vorteil genutzt werden.

Dazu ein kurz gehaltener Hinweis:
Wenn man keinen solchen Abbruch riskieren will, kann man in der calls=[...] Liste statt der Methode Script.Eval die Methode HTTP.Get verwenden, welcher in der "params" Komponente ein URL Eintrag folgen muss. Dann muss im Skript ein sog. HTTP Endpoint definiert werden. Ist der Job Eintrag fehlerhaft und die Endpoint callback Funktion robust erstellt, dann erfolgt kein Skriptabbruch. Dann wird schlicht der Auftrag nicht oder anders als gewünscht ausgeführt. Diesen Hinweis gestalte ich hier nicht weiter aus. Vielleicht werde ich dies in einem weiteren Teil meiner Skripteinführung unterbringen.

2024-03-14