In Teil 2 habe ich erste Skripte erörtert, die bereits einfache Anwendungen ermöglichen. Das letzte, in Auftrag gegebene, Skript stellt bereits eine Anwendung dar. Dieses Skript möchte ich nun zu einer interessanteren Anwendung ausbauen, die vermutlich nicht per Action oder Cloud Szene implementierbar ist.

Das zu Grunde gelegte Skript

Hier erst einmal das Skript, welches ich zur Erweiterung anbiete und welches du hoffentlich in ähnlicher Weise zusammengestellt hast.

// Definition der Funktion zur Verarbeitung von Ereignissen
function checkEvent(event) {
  // print(event);
  if(event.component!==undefined && event.component==="input:0") {
    Shelly.call("Switch.Toggle", {id:0});
    print("Ausgang 0 war " + (event.info.state ? "ein" : "aus") + "geschaltet. Er wurde soeben umgeschaltet.");
  }

}

Shelly.addEventHandler(checkEvent); // registriert checkEvent als EventHandler

Die Bedingung in checkEvent() habe ich zwecks Funktionssicherheit vorsichtshalber etwas erweitert. Sie lautet nun
event.component!==undefined && event.component==="input:0"

Den zweiten Teil kennst du bereits oder verstehst diesen hoffentlich. Der erste Teil event.component!==undefined prüft, ob das Objekt event eine Komponente component enthält, diese Komponente also definiert ist. Da es den Wert "defined" nicht gibt, aber den Wert "undefined" muss hier statt mit ===defined (gelingt nicht) mit !==undefined gearbeitet werden. Der Operator !== bedeutet ungleich oder nicht gleich. Dieser Operator ist in JavaScript festgelegt und leitet sich in seiner Schreibweise ab aus dem Operator ! für nicht und dem Vergleichsoperator ===, wobei ein = Zeichen entfällt. Du kannst dies auch etwas komplizierter schreiben.

!(a===b) bedeutet nicht a gleich b und ist gleich wirkend wie a!==b (a ungleich b).

Zwischen dem ersten und dem zweiten Teil steht der Operator &&. Dieser verknüpft den Wahrheitswert des davorstehenden Operanden mit dem Wahrheitswert des nachfolgenden Operanden und liefert einen Wahrheitswert als Ergebnis. Man nennt diesen Operator einen logischen Operator, weil er sog. logische Ausdrücke, auch Aussagen genannt - ein Begriff der Mathematik und der Informationstechnik - miteinander verknüpft. (Für strenge Mathematiker: Hier handelt es sich selbstverständlich um Aussageformen.)

a && b ist dann und nur dann wahr, wenn a wahr ist und wenn b wahr ist. Hiermit werden also ausschließlich Wahrheitswerte so miteinander verknüpft, dass wieder ein Wahrheitswert herauskommt. && ist wie folgt definiert.

falsch && falsch ergibt falsch, wahr && falsch ergibt falsch, falsch && wahr ergibt falsch, wahr && wahr ergibt wahr

Das Resultat ist also nur dann wahr, wenn der erste Operand wahr ist UND der zweite Operand wahr ist.

Anmerkung:
Es gibt auch & als Operator. Dieser bezieht sich auf einzelne Bits in den Operanden und verknüpft diese, weshalb er auch als "bitweises UND" bezeichnet wird. Diesen Operator brauchen wir in dieser Einführung nicht.

Warum ist die zusammengesetzte (verknüpfte) Bedingung sicherer als die vorherige und einfache?

Logische Operatoren werden per Kurzschluss verarbeitet. D.h. sobald der Wahrheitswert der Verknüpfung feststeht, wird die Verknüpfung nicht weiter verarbeitet. Ausdrücke werden immer von links nach rechts verarbeitet, wenn keine runden Klammern andere Prioritäten vorgeben. In der obigen Bedingung in checkEvent() wird also event.component==="input:0" nicht mehr geprüft, wenn event.component!==undefined falsch ist, wenn also event.component undefiniert ist, was nicht existent bedeutet. Das liegt daran, dass die && Verknüpfung falsch ergibt, wenn bereits ein Operand (hier der erste) falsch ist. Somit findet der zweite Vergleich nicht mehr statt, was zum Fehler führen täte, falls event.component nicht existiert.

Anmerkung:
Die zu && entsprechende logische ODER-Verknüpfung lautet ||.
falsch || falsch ergibt falsch, wahr || falsch ergibt wahr, falsch || wahr ergibt wahr, wahr || wahr ergibt wahr

Die neue, zu implementierende Anwendung

Ziel

Das Schalten des Ausgangs soll nur erfolgen, wenn die aktuelle Zeit zwischen 22:00 Uhr und 06:00 Uhr liegt.

Anders ausgedrückt

Wenn der Taster/Schalter betätigt wird und es ist zwischen 06:00 Uhr und 22:00 Uhr (je einschließlich), dann soll der Ausgang nicht geschaltet werden.

An dieser Stelle verrate ich, dass es möglich ist, im Skript die aktuelle Uhrzeit zu ermitteln. Also kann diese aktuelle Uhrzeit mit den beiden o.a. Uhrzeiten verglichen werden. Dabei kann eine Schwierigkeit auftreten, weil diese Uhrzeiten Strings sind. Strings werden lexikografisch vergleichen, d.h. sie werden so verglichen, wie sie alphabetisch geordnet in einem Lexikon stünden. Hier wirkt nicht die alphabetische Reihenfolge sondern die Werte der Zeichencodes, d.h. wie die für uns lesbare Zeichen intern als digitale Werte codiert sind. Dies ist in einem Lexikon anders, weil die Groß- und Kleinschreibung darin, anders als hier, keine Rolle spielt. Dies muss uns in dieser Anwendung allerdings nicht sorgen, weil in den Uhrzeiten ausschließlich Ziffern und der Doppelpunkt enthalten sind. Für die Vergleiche kompletter Uhrzeiten sind sowohl die Stunden als auch die Minuten zweistellig zu verwenden. Andernfalls wären die Vergleichsergebnisse mitunter nicht so, wie erwartet.

Beispiele: "06:59" < "07:00", "09:18" < "10:05", "00:30" < "22:00",
aber "8:30" > "15:00" (der Code von "8" ist größer als der Code von "1")

Du kannst solche Vergleiche bei laufendem Skript testen, indem du in der Eingabezeile bspw. eingibst
print("06:59" < "07:00");

Als Ausgabe erhältst du true, was bedeutet, dass "06:59" kleiner ist als "07:00". Wäre es nicht so, würde statt true der Wahrheitswert false ausgegeben werden.

Anmerkung:
Bei einem lexikografischen Vergleich ist dieser beendet, sobald feststeht, ob er true oder false ergibt, ähnlich der Kurzschlussauswertung (s.o.). Konkret zu obigem Beispiel:
Der Code der ersten Ziffer "0" in "06:59" ist gleich dem Code der ersten Ziffer "0" in "07:00".
=> Es muss weiter verglichen werden.
Der Code der zweiten Ziffer "6" in "06:59" ist kleiner als der Code der zweiten Ziffer "7" in "07:00".
=> Der Vergleich wird beendet, weil nun feststeht, dass "06:59" < "07:00" ist.

Mit einer kleinen Funktion kann man die Zweistelligkeit der Stunden und Minuten sicherstellen. Die folgende Funktion wird mit der Stundenzahl bzw. der Minutenzahl aufgerufen. Sie liefert eine Zeichenkette, die immer zweistellig ist, solange der Parameterwert zwischen 0 und 99 einschließlich liegt.

function twoDigits(d) { // d ist als Zahl zu importieren mit 0 <= d <= 99
  return d<10 ? "0" + d : "" + d;
// liefert einen String
}

Das Skript wird weitere Funktionen erhalten. Es ist zumindest guter Stil, eine Funktionsdefinition oberhalb ihrer ersten Aufrufstelle im Skript zu platzieren. Es mag JavaScript Interpreter geben, die eine solche Reihenfolge erwarten - dann ist diese Reihenfolge sogar zwingend.
Guter Stil Regel: Definiere jede Funktion vor ihrem ersten Aufruf - von oben nach unten gelesen!
Kurz: Erst die Definition, dann der Aufruf.

  1. Finde heraus, wie du die aktuelle Uhrzeit (ohne Datum) im Skript ermitteln lassen kannst!
    Hierfür kannst du die folgenden Links verwenden - oder andere Quellen, die dir weiterhelfen.
  2. Definiere im Skript eine Funktion namens getTime(), die die aktuelle Uhrzeit als String im Format "hh:mm" zusammenstellt und als Resultat liefert!
    hh bedeutet zweistelliger Stundenwert, mm entsprechend zweistelliger Minutenwert.
    Das Resultat wird per return ... geliefert.
    Teste deine Funktion getTime(), indem du diese in der Eingabezeile aufrufst.
    print(getTime());
    Dies kannst du jederzeit tun, auch bereits dann, wenn du bspw. erst die Stunden ermitteln lässt.
    Die Verkettung von Strings gelingt mit dem Operator +. "aha"+"!"+"soso" ergibt "aha!soso".

    Eine Lösung gibt es hier.

  3. Verwende den Aufruf von getTime() im EventHandler, um festzustellen, ob die aktuelle Zeit das Schalten des Ausgangs zulässt!
    Hierfür ist in einer if-Anweisung eine geeignete logische Verknüpfung erforderlich, deren Operanden Vergleiche sind.
    Nur wenn diese logische Verknüpfung true ergibt, sollte der Ausgang geschaltet werden.
    Mögliche Formen:
    if(a>b || a<c) ...
    if(a<b && a>c) ...
    oder etwas ähnliches, aber anders 🙃

    Du solltest zunächst zum Testen deines Entwurfs andere Zeiten verwenden, die in der Nähe deiner aktuellen Zeit liegen. Hierbei ist es am einfachsten, wenn die Schaltfreigabe mit einer kleineren Zeit beginnt und mit einer größeren Zeit endet - gemäß der lexikografischen Ordnung beim vergleichen von Strings. Dann sind die zielführenden Vergleiche in der Bedingung relativ einfach.
    Für den allgemeingültigen Fall ist obige Voraussetzung aber nicht gegeben. Dann sind zwei Fälle der beiden festgelegten Tageszeiten zu unterscheiden.
    Beginn liegt vor Ende, bspw. von 12:00 bis 15:00 -> Es greift die zuvor genutzte Bedingung.
    Beginn liegt hinter Ende, bspw. von 22:00 bis 06:00 -> Hier muss die Bedingung anders lauten.

    Eine Lösung mit Ausgabe statt schalten gibt es hier.

  4. Sicheres Erfassen des Schaltens - als erfolgreich oder als misslungen
    Wenn du das Schalten per Anweisung und Methode in Auftrag gibst, dann ist keineswegs sichergestellt, dass auch wirklich geschaltet wurde. Du kannst dies nur vermuten und in den allermeisten Fällen wird es wohl auch der Fall sein. Da Software aber möglichst sicher arbeiten und keinesfalls irritieren sollte, sollte die Reaktion auf den erteilten Auftrag verarbeitet werden. Solche Reaktionen (responses) kannst du mit RPC Aufrufen per Browser kennenlernen, wobei der URL den Auftrag fernsteuernd (remote) erteilt. Auch per Skript kann der Auftrag per Methode "HTTP.Get" einem anderen Shelly erteilt werden. Dies habe ich anfänglich beschrieben unter Methodenaufrufe in einem Skript.
    An dieser Stelle will ich dies zu obiger Aufgabenstellung und an Hand meiner Lösung genauer beleuchten. Die vorläufige Funktionsdefinition zum Umschalten gibt bisher nur eine vorsichtige Meldung aus. Nun soll sie um einen Umschalteauftrag erweitert werden.
    function switchToggle(id) { // bisherige Funktion zum Umschalten
      print("trying switch " + id + " to toggle");
    }

    Der Umschalteauftrag gelingt per Methode "Switch.Toggle" und der Id des Ausgangs als Parameter.
    Shelly.call("Switch.Toggle", {id:0});
    Shelly.call() erwartet immer an erster Stelle die Methode und als zweiten Parameter ein Objekt oder einen JSON String. Ich bevorzuge, das Objekt direkt zu notieren. In beiden Fällen sind die beiden geschweiften Klammern erforderlich. Der Inhalt dazwischen sollte leer sein ({}), wenn die Methode keinen Parameter importiert.
    Dieser Auftrag hat als Resultat eine Antwort zur folge, die mit einem dritten Parameter, der empfangenden Funktion verarbeitet werden kann. Eine solche Funktion als Parameter wird eine "callback" Funktion genannt. Sie soll hier als benannte Funktion eingesetzt werden. Diese callback Funktion importiert per se drei Parameter, den optionalen vierten brauchen wir nicht. Die Parameternamen dürfen gerne auch kürzer sein. Man kann auch die beiden error Parameter weglassen, hier sollen aber alle Antwort-Informationen genutzt werden.
    function toggleReceive(result, errorcode, errormessage) {
      if(errorcode!==0) print("toggle error ", errorcode, ", message: ", errormessage);
      else {
        print(JSON.stringify(result));
        // sonstige Auswertungen
      }

    }

    Diese Funktion ist als callback von Shelly.call() in die Definition von switchToggle() einzubauen.
    function switchToggle(id) { // komplette Funktion zum Umschalten

      print("trying switch " + id + " to toggle");
      Shelly.call("Switch.Toggle", {id:0}, toggleReceive);

    }

    Um das in Auftrag gegebene Skript in seiner Vollständigkeit zu testen, solltest du diese beiden Funktionsdefinitionen darin einbauen. Wenn du unbenannte Funktionen magst, kannst du gerne an Stelle des Parameters toggleReceive eine unbenannte Funktion gemäß obiger Definition von toggleReceive() einbauen.

    Auch solltest du gelegentlich, print() Ausgaben, die du nicht brauchst, einfach entfernen! Wenn die Arbeitsweise verstanden ist, ist so manche Ausgabe überflüssig, wie bspw. print("trying switch " + id + " to toggle");
    Dann wird auch die Funktion switchToggle() nicht mehr gebraucht, weil der Shelly.call() Aufruf unmittelbar im EventHandler platziert werden kann. Versuche also nun, dein Skript von Überflüssigem zu befreien, damit es kürzer und übersichtlicher wird! Tue dies an Hand deines eigenen Verständnisses und deines Maßstabes!

    Mehr zur Component Switch und deren Methoden ist in der Dokumentation zu finden.

 

zu Teil 2

2024-03-21