logoFHEM CONTROL 2.0



Schnelleinstieg mit praktischen Beispielen

Nachfolgend finden sich einige λscript-Beispiele die den Einstieg erleichtern sollen. Zunächst werden kurz die verwendeten Grundbegriffe definiert und danach kleine Scripte entwickelt. Die in den Scripten verwendeten Devices müssen selbstverständlich an die der aktuellen Installation angepasst werden. Die verwendeten Kommandos werden kurz erklärt. Eine genauere Dokumention findet sich in der Sprachbeschreibung.

1 Definition der Grundbegriffe

Stand: 30.05.22 07:15

2 Beispielscripts

2.1 Hallo Welt

Stand: 05.08.20 14:30

Die Eingabe von "define test lambda" in der Kommandozeile der fhem-Website erzeugt ein Device mit dem Namen test. Der Eintrag in Feld DEF enthält das Script.

log "Hallo Welt";

Die Ausführung des set-Befehls "set test start" führt das Script aus. Es schreibt "Hallo FHEM" in die aktuelle log-Datei.

Mit einem Klick auf DEF kann das Script editiert werden.

2.2 Lampe einschalten

Stand: 05.08.20 14:30

Gibt es in der aktuellen Installation eine Lampe WZ_Lampe, und enthält das Script diese Zeile

set WZ_Lampe on;

wird sie durch das Starten des Scripts eingeschaltet.

2.3 Lampe ein- und ausschalten

Stand: 05.08.20 14:30
set WZ_Lampe on;
wait 0,3;
set WZ_Lampe off;

Der dem Symbol wait folgende Wert ist vom Typ timespan und bezeichnet eine Zeitdauer von 3 Sekunden. Allgemein wird eine Zeichenfolge m/d/h:min,sec als eine Zeitspanne von m Monaten, d Tagen, h Stunden, min Minuten und sec Sekunden interpretiert. Der Doppelpunkt oder das Komma oder die beiden Schrägstriche sind zwingend, damit λscript ein Zeichenfolge der Gestalt m/d/h:min,sec als eine Zeitspanne interpretiert. Die Bestandteile m, d, h, min müssen ganze Zahlen sein. sec kann eine Dezimalzahl (mit einem Punkt als Dezimaltrennzeichen) sein. 0,0.2 steht für 200 Millisekunden.

2.4 Lampe mehrfach ein- und ausschalten

Stand: 05.08.20 14:30

Das Kommando zum wiederholten Ausführen von Anweisungem wird vom Schlüsselwort repeat eingeleitet. Danach folgen die Zahl der Wiederholungen (Eine Zahl oder eine Variable vom Typ number) und ein Block mit den Kommandos die wiederholt werden sollen. Wird die Zahl 3 nach dem Symbol repeat weggelassen, wird der nachfolgende Block unendlich oft ausgeführt.

repeat 3 {
   set WZ_Lampe on;
   wait 0,10;
   set WZ_Lampe off;
   wait 1,0;
}

Dieses Script schaltet das Device WZ_Lampe für 10 Sekunden an. Der Vorgang wird nach einer Minute wiederholt. Insgesamt drei mal.

2.5 Lampe zufällig schalten

Stand: 05.08.20 14:30

Es soll ein Script erstellt werden, dass zwischen 23:00 Uhr und 4:00 Uhr in einen zufälligen Abstand von 20 bis 40 Minuten ein von außen sichbares Licht (hier das Device TreppenhausLicht) einschaltet, um so Anwesenheit und wach sein zu simulieren.

repeat {
   while '([23:00 4:00]) {
      wait (random 20 40)'minutes;
      set TreppenhausLicht on;
      wait 2,0;
      set TreppenhausLicht off;
   };
   wait 23:00'today;
}

Dieses Script ist nach dem bisher gesagten leicht verständlich. Neu ist das while-Kommando. Dem Schlüsselwort while folgt ein Funktional mit einem boolschen Wert und ein Block. Der Block wir solange ausgeführt wie der boolsche Wert true ist. Der boolsche Wert ist hier [23:00 4:00]. Der wird zu true ausgewertet, wenn die aktuelle Uhrzeit in dem angegebenen Intervall liegt. Ansonsten ist er false und die while-Schleife wird verlassen. Danach wird bis 23:00 Uhr gewartet, um von neuem zu beginnen.

Das Kommando (random 20 40) liefert eine zufällige ganze Zahl zwischen 20 und 40. Die Funktion number minutes wandelt diese Zahl in einen Wert vom Typ timespan um. Dieser Wert liegt dann zwischen 20 und 40 Minuten. Diese Zeit wird gewartet und dann das Licht für 2 Minuten eingeschaltet.

2.6 Erweiterte Lampensteuerung

2.6.1 Lampe mit variablem Ausschaltzeitpunkt

Stand: 14.11.21 14:42

Ein Lampe (Device LampeEingang) wird von mehreren Szenarien geschaltet.

Diese Funktionalität soll mit einer neu zu definierenden Klasse clsLamp realisiert werden:

clsLamp := fhemDevice
   onTill moment'empty
;

Die neue Klasse clsLamp ist von fhemDevice abgeleitet und hat eine neue Eigenschaft onTill. Diese Eigenschaft soll den Zeitpunkt aufnehmen, an dem die Lampe automatisch ausgeschaltet werden soll. Das Kommando zum Einschalten bis zu einem gewissen Zeitpunkt (z.B. heute 22:00 Uhr) soll sein: set LampeEingang on 22:00'today. Dazu wird folgende Funktion definiert:

'(set clsLamp'lampe on moment't) := {
   lampe'onTill := max lampe'onTill t;
   if ([lampe] ~ /off/) {set lampe on};
   thread {
      wait lampe'onTill;
      if (lampe'onTill <= now) {set lampe off};
   };
};

Die erste Zeile der Funktion ist die Signatur: Symbol set gefolgt vom Device (als Instanz der Klasse clsLamp), dem Symbol on und dann die Angabe des Ausschaltzeitpunktes als Wert vom Typ moment.

Die nächste Zeile berechnet die neue Ausschaltzeit (lampe'onTill) als dem Maximum aus der alten Ausschaltzeit und der Neuen.

Danach wird die Lampe, wenn sie aus ist, eigeschaltet: if ([lampe] ~ /off/) {set lampe on};

Jetzt wird ein Thread gestartet, ein Programm, das parallel zum dem laufenden Script läuft. Dieser Thread wartet bis zum Ausschaltzeitpunkt und schaltet die Lampe aus. Die if-Abfrage vor dem Auschalten in der Zeile if (lampe'onTill <= now) {set lampe off}; sichert ab, dass die Lampe, wenn während der Wartezeit eine Verlängerung der Einschaltdauer erfolgt ist, die Lampe nicht vorzeitig auschaltet wird.

Ein Testscript könnte nun so aussehen:

clsLamp := fhemDevice
   onTill moment
;

'(set clsLamp'lampe on moment't) := {
   lampe'onTill := max lampe'onTill t;
   if ([lampe] ~ /off/) {set lampe on};
   thread {
      wait lampe'onTill;
      if (lampe'onTill <= now) {set lampe off};
   };
};

new LampeEingang1 clsLamp;
new LampeEingang2 clsLamp;


set LampeEingang1 on 14:00'today;
wait 13:55;
set LampeEingang1 on 15:00'today;
set LampeEingang1 on 14:30'today;

Nach den Definitionen der Klasse und der Funktion werden die Lampen, die mit dieser Funktionalität ausgestattet werden sollen, auf die neue Klasse clsLamp "upgegradet". Probeweise wird nun LampeEingang1 bis 14:00 Uhr eingschaltet. Kurz vor Ablauf der Zeit wird die Dauer noch einmal bis 15:00 Uhr verlängert. Das Kommando set LampeEingang1 on 14:30'today; bleibt ohne Wirkung.

2.6.2 Lampe mit variabler Einschaltdauer

Stand: 05.08.20 14:30

Die Funktionalität der Klasse clsLamp soll nun so erweitert werden, dass eine Lampe nicht nur bis zu einem definierten Zeitpunkt, sondern auch für eine gewisse Zeitdauer eingeschaltet werden kann.

Das Kommando zum Einschalten für z.B. 5 Minuten soll sein: set LampeEingang on 5,0. Dazu wird folgende Funktion definiert:

'(set clsLamp'lampe on timespan't) := {set lampe on (now t)};

Hier wird einfach aus der Zeitdauer die geplante Ausschaltzeit berechnet (jetzt + Dauer) und die vorher definierte Funktion aufgerufen. Die beiden Funktionen sehen von Ihren Aufruf gesehen zwar ähnlich aus, besitzen aber eine unterschiedliche Signatur. Der letzte Parameter ist einmal vom Typ moment und hier vom Typ timespan. Damit sind sie wohlunterschieden.

Das komplette Testscript sieht nun so aus:

clsLamp := fhemDevice
   onTill moment
;

'(set clsLamp'lampe on moment't) := {
   lampe'onTill := max lampe'onTill t;
   if ([lampe] ~ /off/) {set lampe on};
   thread {
      wait lampe'onTill;
      if (lampe'onTill <= now) {set lampe off};
   };
};

'(set clsLamp'lampe on timespan't) := {set lampe on (now t)};

new LampeEingang1 clsLamp;
new LampeEingang2 clsLamp;


set LampeEingang1 on 1,0;
wait 0,10;
set LampeEingang1 on 14:00'today;
wait 13:55;
set LampeEingang1 on 3,0;
set LampeEingang1 on 14:30'today;

Auch hier ändert der Einschaltbefehl set LampeEingang1 on 3,0; (Der in dieser Form von einem Bewegungsmelder kommen kann.) nichts daran, das die Lampe bis 14:00 Uhr an.

2.7 Lampen synchron schalten

Stand: 05.08.20 14:30

Angenommen es gibt in der fhem-Installation noch ein Device Flur_Lampe. Ziel soll sein, ein Script zu schreiben, dass WZ_Lampe immer ein- bzw. ausgeschaltet wird, wenn Flur_Lampe ein- bzw. ausgeschaltet wird. Ein Script dafür wäre:

repeat {
   wait [Flur_Lampe];
   if ([Flur_Lampe] ~ /on/) {set WZ_Lampe on} ([Flur_Lampe] ~ /off/) {set WZ_Lampe off};
}

Hier passiert Folgendes: Zunächst wird auf eine Änderung des Readings state des Devices WZ_Lampe gewartet. Allgemein liefert der Ausdruck [deviceName readingName] den aktuellen Wert des Readings mit dem angegebenen Namen. Der Rückgabewert ist immer ein Object der Klasse string. Für das Reading state kann readingName weggelassen werden. Ist der wait folgende Parameter keine Zeitangabe, so wird mit der Fortführung des Scripts gewartet bis sich der Wert des Parameters geändert hat. Folgen dem Symbol wait mehrere Parameter, wird das Warten beendet wenn sich mindestens ein der Parameter geändert hat. wait [Flur_Lampe]; wartet also darauf, dass sich der Wert des Readings state von Flur_Lampe ändert. Danach wird mit dem Script fortgefahren.

Nachdem sich also das Reading state von Flur_Lampe geändert hat, wird geprüft, ob der string "on" in state enthalten ist. Hier wäre auch der Vergleich [Flur_Lampe] = "on" möglich. Die erste Variante schließt jedoch den Fall, dass state den Wert "set_on" hat, ein. Ist der Vergleich erfolgreich, wird der nachfolgende Block {set WZ_Lampe on} ausgeführt. Danach wird die Kommandofolge mit dem wait-Befehl wiederholt. Es wird wieder auf eine Veränderung des Readings state von Flur_Lampe gewartet.

Um sicher zu stellen, das WZ_Lampe nur ein- bzw. ausgeschaltet wird, wenn sie nicht schon an bzw. aus ist, wird das if-Kommandos erweitert.

repeat {
   wait [Flur_Lampe];
   if ([Flur_Lampe] ~ /on/ & [WZ_Lampe] !~ /on/) {set WZ_Lampe on}
      ([Flur_Lampe] ~ /off/ & [WZ_Lampe] !~ /off/) {set WZ_Lampe off};
}

Diese Funktionalität soll nun in einem neu zu definierenden Kommando gekapselt werden. Der Aufruf des Kommandos soll so aussehen:

Flur_Lampe -> WZ_Lampe;
Terasse_Licht -> Teich_Lampe;

Einmal definiert, lässt sich diese Funktion für jede Kombination von Geräten die sychron geschalten werden sollen aufrufen. Z.B. auch für die Kombination Terasse_Licht und Teich_Lampe. Die Funktion besteht im wesentlichen aus den Codezeilen von oben:

'(fhemDevice'Lampe1 -> fhemDevice'Lampe2) := {
   always {
      wait [Lampe1];
      if ([Lampe1] ~ /on/ & [Lampe2] !~ /on/) {set Lampe2 on}
         ([Lampe1] ~ /off/ & [Lampe2] !~ /off/) {set Lampe2 off};
   };
};

Die erste Zeile definiert die sogenannte Signatur der Funktion. Sie besteht aus:

Die gesamte Signatur wird in runde Klammern gesetzt und ein ' vorangestellt. Danach schließt sich ein Kommandoblock an. Der enthält die Kommandos die beim Aufruf der Funktion abgearbeitet werden.

Wichtig ist die Änderung des Schlüsselwortes repeat in always. Das hat folgenden Hintergrund: Wird die Funktion aufgerufen bleibt sie in der repeat->Schleife hängen. Von den Aufrufen Flur_Lampe -> WZ_Lampe; Terasse_Licht -> Teich_Lampe; würde nur ersterer gestartet. Mit der Verwendung des Schlüsselwortes always an Stelle von repeat wird die Warteschleife als eigener Thread gestartet. D.h.: Die Wiederholung wird angestoßen und danach mit dem auf die Schleife folgenden Programmcode fortgesetzt. In unserem Beispiel läuft dann die Überwachung von Flur_Lampe und Terasse_Licht quasi parallel nebeneinander und unabhängig von "Hauptscript".

Die eben entwickelte Lösung für Synchronisation läuft nur in einer Richtung. Lampe1 schaltet Lampe2. Der Vollständigkeit soll noch eine Funktion entwickelt werden, bei der jede Lampe die andere mitschaltet. Die Aufrufsyntax soll Lampe1 <-> Lampe2; sein. Folgende Funktion löst diese Aufgabe:

'(fhemDevice'Lampe1 <-> fhemDevice'Lampe2) := {
   Lampe1 -> Lampe2;
   Lampe2 -> Lampe1;
};

Die Funktionsweise ist einfach: Die zuerst entwickelte Funktion wird zweimal aufgerufen. Einmal mit Lampe1 als Master und Lampe 2 als Slave und ein zweites Mal umgekehrt. Das komplette Script sieht dann so aus:

'(fhemDevice'Lampe1 -> fhemDevice'Lampe2) := {
   always {
      wait [Lampe1];
      if ([Lampe1] ~ /on/ & [Lampe2] !~ /on/) {set Lampe2 on}
         ([Lampe1] ~ /off/ & [Lampe2] !~ /off/) {set Lampe2 off};
   };
};

'(fhemDevice'Lampe1 <-> fhemDevice'Lampe2) := {
   Lampe1 -> Lampe2;
   Lampe2 -> Lampe1;
};

Flur_Lampe     -> WZ_Lampe;
Terasse_Licht <-> Teich_Lampe;

Hier ist noch wichtig anzumerken, das neue Funktionen immer vor ihrer ersten Verwendung definiert werden müssen.