logoFHEM CONTROL 2.0

Sprachbeschreibung Version: 1.20



λscript wurde geschrieben um große komplexe Systeme mit vielen Sensoren und Aktoren zu steuern. Übliche "SMART HOME"-Systeme sind häufig vergleichsweise klein: Ein paar Rolläden, einige Thermostate und Ventile, ein paar Lampen – viel mehr ist es oft nicht. Trotzdem können auch solche Systeme sehr komplex werden. Die Anzahl der möglichen Verküpfungen wächst sehr viel schneller als die Anzahl der Komponenten. Mit den klassischen Möglichkeiten die FHEM bietet, wird die Programmierung der Steuerung schnell unübersichtlich und fehleranfällig. λscript schafft hier eine Alternative. Moderne Programmiertechniken (insbesondere objektorientierte und funktionale Programmierung) erlauben es sehr komplexe Aufgaben mit wenig Code zu lösen. Komfortables Multithreading rundet die Sprache ab.

λscript ist sehr umfangreich und bietet mehr Möglichkeiten als der durchschnittliche FHEM-Benutzer wahrscheinlich benötigt. Nach der Fertigstellung dieser Seite soll sie den gesamten Sprachumfang dokumentieren. Es ist kaum möglich, etwas vollständig zu erklären ohne auf später Beschriebenes vorzugreifen. Das macht das Verstehen einiger Textpassagen für den Einsteiger, der noch nie programmiert hat, etwas schwieriger. Für erfahrene Programmierer trifft das sicher weniger zu.

Deshalb ist diese Dokumentation eher zum Nachschlagen als zum Erlernen der Sprache gedacht. Für den Anfänger ist es einfacher, mit den kleinen Scripts, wie sie hier z.B. unter dem Menupunkt "Schnellstart" zu finden sind, zu beginnen. Diese können fast spielerisch abgewandelt und ergänzt werden. Das sorgt für ein steile Lernkurve und macht das Verstehen der Sprachbeschreibung leichter.

1 Klassifizierung von λscript

Stand: 06.04.22 16:32

Wenn man λscript klassifizieren möchte, lässt sich das anhand der in ihr umgesetzten Programmierparadigmen tun. λscript ist:

imperativ
Bestimmende Merkmale für diese Art des Programmierens sind:
  • Im Quellcode wird festgelegt, was in welcher Reihenfolge und wie zu tun ist. Das Grundprinzip ist das schrittweise Fortschreiten innerhalb der Kommandofolge.
  • Zur Steuerung der Befehlsausführung werden Kontrollstrukturen (z.B. Schleifen, Verzweigungen) benutzt.
strukturiert
Strukturierte Programmierung beinhaltet zum einen die baumartige Zerlegung eines Programms in Teilprogramme (Prozeduren) und enthält somit das Paradigma der prozeduralen Programmierung. Zudem verlangt die strukturierte Programmierung auf der untersten Ebene die Beschränkung auf lediglich drei Kontrollstrukturen:
  • Sequenz (hintereinander auszuführende Programmanweisungen)
  • Auswahl/Selektion (Verzweigung)
  • Wiederholung/Iteration (Schleifen)
Das bedeutet, das sich auf der Ebene, auf der sich die meisten "normalen" User bewegen, die Scripte maximal einfach sind.
objektorientiert
Unter Objektorientierung (kurz OO) versteht man in der Entwicklung von Software eine Sichtweise auf komplexe Systeme, bei der ein System durch das Zusammenspiel kooperierender Objekte beschrieben wird. Einem Objekt werden bestimmte Attribute (Eigenschaften) und Methoden zugeordnet. Dabei muss ein Objekt nicht gegenständlich sein. Entscheidend ist, dass bei dem jeweiligen Objektbegriff eine sinnvolle und allgemein übliche Zuordnung möglich ist. Ergänzt wird dies durch das Konzept der Klasse, in der Objekte aufgrund ähnlicher Eigenschaften zusammengefasst werden. Ein Objekt wird im Programmcode als Instanz Klasse definiert.
statisch typsicher
Während der Kompilierung wird festgestellt, ob im Script eine Typverletzung auftritt. D.h. Wertzuweisungen werden auf Verträglichkeit geprüft und bei Operationen wird sicher getestet ob diese Operation mit den Operanden verträglich ist. Ist das nicht der Fall, kann das Script nicht gestartet werden. Dieses Konzept sorgt für schneller ablaufenden Programmcode, da die Überprüfung nicht zur Laufzeit erfolgen muss, und gleichzeitig für eine höhere Betriebssicherheit.
funktional
Funktionale Programmierung ist ein Programmierparadigma, innerhalb dessen Funktionen nicht nur definiert und angewendet werden können, sondern auch wie Daten miteinander verknüpft, als Parameter verwendet und als Funktionsergebnisse auftreten können. Dadurch kann ein funktionales Programm sehr weitreichend neue Berechnungsvorschriften zur Laufzeit zusammensetzen und anwenden.
Eine Konsequenz daraus ist, dass es in funktionaler Programmierung besonders einfach ist, Algorithmen ohne Berücksichtigung der Beschaffenheit der bearbeiteten Datenobjekte zu beschreiben und dadurch generischen Programmcode zu erstellen.
nebenläufig
Die Nebenläufigkeit ist die Eigenschaft eines Systems, mehrere Berechnungen, Anweisungen oder Befehle gleichzeitig ausführen zu können. Es kann sich dabei um völlig unabhängige Anweisungen handeln, bis hin zur gemeinsamen Bearbeitung einer Aufgabe. Sie können dabei auch miteinander interagieren (z. B. um Zwischenergebnisse auszutauschen).

2 Grundlegende Begriffe

2.1 Symbol

Stand: 06.04.22 16:32

Ein Symbol ist eine Zeichenfolge, die folgende Zeichen nicht enthält: Leerzeichen, Tabulatoren, Zeilenumbrüche und keines der Zeichen: [ ] ( ) { } ' " ;

Es gibt Symbole, die in λscript für die Definition von Funktionen verwendet werden. Einige dieser reservierten Symbole sind:

:= ==
+ - * / ^
= !=
< <= <> <=> => >
~ !~
<< >>
& and | or ! not
my new
if wait repeat while forEach loop quit
thread always terminate stop
set setReading userReading
today tomorrow yesterday
year easter month week day hour minute now
years months weeks days hours minutes seconds
random shuffle
class of dimension default
toString toBoolean toNumber alternatively

Wird einem der oben aufgelisteten Symbole ein Wert zugeweisen, wird dieses Symbol zu einer Variablen. Dann sind die mit diesem Symbol definierten Funktionen in ihrer ursprünglichen Form nicht mehr aufrufbar. (Der gleiche Effekt tritt auf, wenn diese Symbole als Namen für λscripts verwendet werden.) Das stellt kein prinzipielles Problem dar; der Aufruf dieser Funktionen ist immer noch möglich. Einfacher ist es, die oben genannten Symbole nicht als Variable, Klassennamen oder Devicenamen zu verwenden. Sollte man dies doch tun wollen, ist lediglich zu verhindern, dass beim Aufruf von Funktionen, in denen diese Symbole verwendet werden, der Compiler an die Stelle des Symbols, die Variable einsetzt. Das wird durch das Voranstellen eines Apostrophs ' erreicht.

Wenn z.B. wait ein Wert zugewiesen wurde, ist wait damit zu einer Variablen geworden. Dann muss der Befehl für "Warte eine Stunde" als 'wait 1:00; geschrieben werden.

2.2 Klasse

Stand: 06.04.22 16:32

Eine Klasse ist, wie in allen objektorientiert arbeitenden Programmiersprachen, auch hier ein abstraktes Modell für eine Reihe von ähnlichen Objekten. Es gibt in λscript vordefinierte Klassen (number, string, timespan, stack, fhemDevice, ...). Alle Klassen sind eine Instanz der Klasse class. In λscript können den vordefinierten Klassen Neue hinzugefügt werden. Einfache Vererbung wird unterstützt. Eine Klassendefinition wird durch ihren Namen repräsentiert. Mit der Definition einer neuen Klasse entsteht eine neue Variable die mit dem Namen der Klasse identisch ist.

2.3 Werte

Stand: 06.04.22 16:32

λscript ist streng objektorientiert. Alle Werte sind von Klassen abgeleitete Instanzen. Der Wert 123 ist zum Beispiel eine Instanz der Klasse number; oder 123 ist vom Typ number. Der Wert "Hallo Welt" ist Instanz der Klasse string und 12:30 eine Instanz der Klasse timespan.

Für jede Klasse gibt es eine besonderen Wert. empty. Dieser Wert hat, außer seiner Zugehörigkeit zu einer bestimmten Klasse, keine Eigenschaft. Das Kommando class'empty liefert als Ergebnis den zur Klasse class gehörenden Wert empty.

2.4 Variable

Stand: 06.04.22 16:32

Einem Symbol kann ein Wert zugewiesen werden. Dann wird aus diesem Symbol eine Variable. Die Zuweisung geschieht durch den Zuweisungsoperator :=. Beispiel:

zahl := 123;

Alles was rechts vom Symbol := steht, wird als Kommando interpretiert, ausgeführt und das Ergebnis dem links stehenden Symbol zugewiesen. Steht links bereits ein Variable, wird deren Wert durch den neu Berechneten ersetzt.

Einer Variablen (z.B. v) kann dier Wert empty durch v := number'empty zugewiesen werden. Nach der Ausführung diese Kommandos hat die Variable v den Wert empty der Klasse number. Mit v'isEmpty kann getestet werden, ob die Variable v den Wert empty hat. Der Rückgabewert diese Kommandos ist true oder false.

Alle verwendeten Variablen sind statisch typisiert. Das heißt, jede Variable besitzt einen Wert, der Instanz einer Klasse ist. Dieser Variablen kann als neuer Wert nur ein Objekt vom Typ der Klasse zugewiesen werden, mit der die Variable erstmalig definiert wurde.

Es gibt mit dem Start von λscript vordefinierte Variable. Das sind:

2.5 Devices

Stand: 06.04.22 16:32

Unter diesem Begriff wird die zu der Installation gehörende Hardwareumgebung zusammengefasst. In erster Linie sind das bei der Integration in eine fhem-Umgebung die fhem-Devices. Automatisch wird beim Start von λscript zu jedem fhem-Device eine Variable vom Typ fhemDevice mit dem Namen des Devices angelegt. Während der Zugriff auf Variable in λscript eingeschränkt sein kann, sind Devices systemweit verfügbar.

2.6 Kommando

Stand: 06.04.22 16:32

Ein Kommando ist eine Folge von Symbolen und Variablen, und führt eine definierte Funktion aus. Diese Folge ist entweder in runde Klammern eingeschlossen, oder sie endet mit einem Semikolon. Kommandos können ineinander verschachtelt werden. Beispiel:

z := 3 * (4 + 5);

Hier ist das Kommando (4 + 5) in z := 3 * (4 + 5); eingebettet.

Ein Kommando kann einen Rückgabewert haben. Dieser wird nach der Abarbeitung des Kommandos im übergeordneten Kommando an dessen Stelle eingesetzt. Im obigen Beispiel wird das Kommando (4 + 5) ausgeführt und das Ergebnis 9 an dessen Stelle eingesetzt. Dann wird mit der Abarbeitung des Scripts fortgefahren.

2.7 Block

Stand: 06.04.22 16:32

Ein Block ist eine in geschweifte Klammern eingeschlossene Folge von Kommandos, die nacheinander ausgeführt werden. Beispiel:

{
   z := 3 * (4 + 5);
   wait z'seconds;
   set Lampe on;
}

Das letzte Semikolon als Abschluss des letzten Kommandos vor der schließenden geschweiften Klammer ist optional, kann also weggelassen werden. Blöcke können ineinander verschachtelt werden. D.h.: Blöcke können wiederum Blöcke enthalten.

Innerhalb eines Blocks kann auf die in übergeordneten Blöcken definierten Klassen und Funktionen und auf alle Devices zugegriffen werden.

Es werden zwei Arten von Blöcken unterschieden: Kommandoblöcke und Definitionsblöcke. Beides sind in geschweifte Klammern Folgen von Kommandos. Der Unterschied ist folgender:

2.8 Script bzw. Programm

Stand: 06.04.22 16:32

Ein Programm, ein λscript, ist ein Kommandoblock ohne die öffnende und schließende geschweifte Klammer. Diese Klammern werden vom Precompiler eigenständig zugefügt.

Jedes λscript ist in einen Superblock eingebettet. In diesem Superblock werden beim Systemstart alle systemimmanenten Klassen, Funktionen und Devices definiert.

2.9 Zugriffsoperator

Stand: 06.04.22 16:32

Der Zugriffsoperator in λscript ist das Apostroph: '. Er dient dazu, auf Elemente von Klassen oder Datenstrukturen zuzugreifen. Solche Zugriffe geschehen durch Aufrufe der Form Object'Eigenschaft. In anderen Programmiersprachen wird dafür auch häufig der Punkt . oder -> verwendet. Da der Punkt aber Bestandteil eines Devicenamens in FHEM sein kann, wurde hier auf das Apostroph ausgewichen.

Die Verwendung des Zugriffsoperators ist lediglich eine andere Schreibweise für bestimmte Kommandos: Kommandos die aus zwei Elemeten bestehen: Aus einem Wert und einem nachfolgenden Symbol. Z.B. das Kommando 12 hours;. Es besteht aus einer Zahl (number) und dem Symbol hours. Es liefert als Ergebis eine Wert vom Typ timespan. Das Kommando, das das laufende Script stoppt und nach 12 Stunden fortsetzt, lautet: wait 12'hours;. Das ist so leichter lesbar als: wait (12 hours);

Das Apostroph als Zugriffsoperator wurde lediglich aus Gründen der kürzeren Schreibweise und der besseren Lesbarkeit eingeführt. Es stellt keine neue Funktionalität bereit. λscript wandelt beim Einlesen des Scripts jeden Ausdruck der Form Wert'Symbol; in (Wert 'Symbol) um – macht daraus also ein "normales" Kommando. Dem entsprechend wird aus

Wert'Symbol1'Symbol2'Symbol3;

beim Einlesen

(((Wert 'Symbol1) 'Symbol2 ) 'Symbol3)

Wie dieses Beispiel zeigt, ist, bei einem mehrstufigen Zugriff auf die Eigenschaften eines Objektes, oder bei der Verwendung bestimmter Funktionen, die Schreibweise mit der Verwendung des Apostrophs besser lesbar.

2.10 Kommentierung

Stand: 06.04.22 16:32

Skriptzeilen, die mit einem # (es können Leerzeichen vorangehen) beginnen, werden beim Einlesen des Scripts übergangen.

# Das ist eine Kommentarzeile;

Für eine ausführliche Kommentierung des Scripts kann ein Kommentarblock definiert werden. Die erste Zeile eines Kommentarblocks muss mit /# beginnen. (Leerzeichen können vorangehen.) Der Block endet mit einer Zeile, die mit #/ abschließt. Auch hier können Leerzeichen vorangehen denen beliebig viele # folgen.

   /###############
   Das ist eine Kommentarblock.
      1. Zeile
      ...
      ...
   ###############/

3 λscript und FHEM

Die hier beschriebene Version ist eine in Perl für FHEM adaptierte Version der Programmiersprache λscript. Die Integration in eine bestehende FHEM-Installation erfolgt durch das Einbinden des Moduls 98_lambda.pm. Das nachfolgende Schema visualisiert das Prinzip dieser Einbindung:

S
u
p
e
r
b
l
o
c
k
{

Bereitstellung der Devices, Definition von globalen Klassen und Funktionen

1.

S
c
r
i
p
t
{
# Code für das 1. Script
# Inhalt des Internals DEF des 1. Devices vom Typ lambda

Codezeile 1;

thread {

};
}
2.

S
c
r
i
p
t
{
# Code für das 2. Script
# Inhalt des Internals DEF des 2. Devices vom Typ lambda


repeat {
   wait 1:00;
   …
   set WZ_Lampe on;
   …
   set WZ_Lampe off;
};
}
 
}

Ein λscript ist eine Folge von Kommandos. Der Compiler fasst diese Kommandos mit einem Paar geschweifter Klammern zu einem Block zusammen. Alle Scripte werden in einen Superblock eingebettet. In diesem Superblock werden beim Start von FHEM die vorhandenen Devices, die vordefinierten Klassen und Funktionen bereitgestellt. Devices, Klassennamen, Funktionen und Variable werden im Allgemeinen von einem übergeordneten Block in alle untergeordneten Blöcke durchgereicht. (Näheres im Punkt Gültigkeitsbereich. Daher sind die Devices in allen λscripts verfügbar. Das trifft auch auf die vordefinierten Klassen und Funktionen zu.

Ein λscript wird mit dem Kommando "define scriptname lambda" erstellt. Danach kann es mit dem DEF-Editor bearbeitet werden.

In den Devices vom Typ lambda ist ein Attribut mode definiert. Wenn das auf autostart gesetzt wurde, wird das entsprechende λscript mit dem Start von FHEM automatisch gestartet. Steht das Attribut mode auf disabled (Standardwert) kann das Script nur manuell oder durch ein anderes steuerndes Device mit "set scriptname start", gestartet werden. Für das Anhalten des Scripts existiert das Kommando "set scriptname stop". Der Neustart eines laufenden Scripts kann mit dem Kommando "set scriptname restart" ausgelöst werden.

Jedes Script läuft als eigener Thread. In jedem Script können beliebig viele weitere untergeordnete Threads gestartet werden.

Mit dem Start eines ersten λscripts wird ein Device lambdaDevices vom Typ lambdaDevices angelegt. Es bleibt die einzige Instanz des Typs. lambdaDevices hat die Aufgabe, die Verbindung von λscripts mit den anderen Devices der FHEM-Installation herzustellen.

4 Klassen

Auf eine definierte Klasse kann in dem Block, in dem sie definiert wurde, und in allen untergeordneten Blöcken zugegriffen werden. Da die nun aufgelisteten Klassen auf höchster Ebene definiert wurden, sind sie global verfügbar – wenn sie nicht durch den Nutzer neu definiert wurden.

Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

4.1 Vordefinierte Klassen

4.1.1 value

Stand: 06.04.22 16:32

Die Klasse value ist eine Klasse ohne Eigenschaften und gleichzeitig die Basisklasse aller Werte. value ist die Urahnin jedes Werts in λscript. Alle anderen Werteklassen sind von value abgeleitet.

Die Klasse value kann nicht instanziiert werden. D.h., es gibt keine Werte oder Objekte von Typ value.

4.1.2 number

Stand: 06.04.22 16:32

Zahlen in λscript sind sämtlich von der Klasse number abgeleitet. Die in einem Script auftauchenden Zeichenfolgen 123, 3.14, -12e-5 werden in der üblichen Art und Weise als Zahlen interpretiert.
Besonders bei λscript ist, das auch gemeine Brüche z.B. -2/7 als Zahlen erkannt werden. Mit solchen Brüchen wird entsprechend den Regeln der Bruchrechnung exakt und ohne Rundungsfehler gerechnet.

4.1.3 string

Eine Instanz der Klasse string ist eine beliebig lange Zeichenkette. In einem Script werden Zeichenketten in Anführungszeichen " eingeschlossen. So ist "Hallo Welt" eine Zeichenkette.

Soll ein Anführungszeichen in einer Zeichenkette enthalten sein, ist ihm das Zeichen \ voranzustellen. Das Kommando zur Ausgabe von Goethe schrieb "Faust"! in die Log-Datei lautet demnach:

log "Goethe schrieb \"Faust\"!";
Goethe schrieb "Faust"!

4.1.3.1 word

Stand: 06.04.22 16:32

Die Klasse word ist eine von der Klasse string abgeleitete Klasse. Ein Objekt von Typ word ist eine nicht leere Zeichenkette die kein Leerzeichen (auch keinen Tabulator und keinen Zeilenumbruch) enthält. Analog string werden sie in einem Script in Anführungszeichen eingeschlossen. So wird "Hallo" automatisch als Zeichenkette vom Typ word erkannt.

4.1.4 regExpr

Stand: 06.04.22 16:32

Diese Klasse dient zur Aufnahme von regulären Ausdrücken. Ein regulärer Ausdruck ist eine Zeichenkette, die der Beschreibung von Mengen von Zeichenketten mit Hilfe bestimmter syntaktischer Regeln dient. Reguläre Ausdrücke finden vor allem in der Softwareentwicklung Verwendung.

Beispiele für regulären Ausdrücke in λscript sind:

m/on/
m/^set_/

Das vorangestellte m ist in beiden Fällen optional. Mit Hilfe diese Werte kann getestet werden, ob ein Zeichenkette die Zeichenfolge on enthält oder mit der Zeichenfolge set beginnt. Enthält die Variable x eine Zeichenkette erfolgt der Test unter Verwendung des Symbols ~:

isOn := x ~ m/on/;

oder

isOn := x ~ /on/;

Beginnt der reguläre Ausdruck mit einem s werden die untersuchten Zeichenketten, entsprechend der im regulären Ausdruck codierten Vorschrift, modifiziert.

s/on/off/g
s/^set_//

Mit Hilfe diese Werte wird in der Zeichenkette die Zeichfolge on durch off ersetzt bzw. die führende Zeichenfolge set_ entfernt.

Über die Arbeitsweise von regulären Ausdrücken gibt es eine Vielzahl von Dokumentationen. (z.B: https://wiki.selfhtml.org/wiki/Regulärer_Ausdruck ) Deshalb wird sie an dieser Stelle nicht vertieft.

4.1.5 measurement

Stand: 24.07.22 15:21

Diese Klasse modelliert die Messwerte physikalischer Größen. Ein Messwert besteht immer aus einer Maßzahl und einer Maßeinheit. Neben dem Zahlenwert, der vomTyp number ist, hat measurement eine weitere Eigenschaft: die Maßeinheit. Die Initialisierung einer Variablen l vom Typ measurement, die den physikalischen Wert "10 m" enthalten soll, erfolgt deshalb nach folgendem Schema:

l := [10 m];

Ein Kommando bestehend aus eckiger Klammer auf, der Maßzahl von Typ number, einem Symbol für die Maßeinheit und eckiger Klammern zu, liefert einen Wert vom Typ measurement. Als Maßeinheiten sind alle SI-Einheiten (inkl. der gesetzlich zulässigen Vorsätze) erlaubt. Daneben sind auch einige nicht-SI-Einheiten zulässig: °C, °F, ... Eventuell notwendige Exponenten werden (wenn nötig mit Vorzeichen) hinter der Maßeinheit notiert.

v := [10 m3];

Damit hat die Variable v den Wert 10 m3. Das Kommando

g := [9.81 m1s-2];

definierte eine Variable g mit dem Wert 9.81 m/s2 Hier ist der Exponent 1 nach m notwendig, da die Einheit sonst als Millisekunde interpretiert werden würde. Eine zweite alternative Schreibweise wäre: [9.81 s-2m];

Einer Variable vom Typ measurement kann nach ihrer Initialisierung nur wieder ein Wert mit der gleichen physikalischen Dimension zugewiesen werden. Soll der oben definierten Variablen l, die die Dimension Länge repräsentiert, der Wert "12 Sekunden" (Dimension Zeit) zugewiesen werden, führt die Anweisung l := [12 s]; zu einer Fehlermeldung.

Mit Werten vom Typ measurement kann wie mit Zahlen gerechnet werden. Bei Multiplikation und Division werden außer neben den Maßzahlen auch die Maßeinheiten miteinander verknüpft. Addition und Subtraktion sind nur möglich, wenn beide Operanden von der selben physikalischen Dimension sind. In dem folgenden Beispiel werden 1 m und 1 mm addiert.

l1 := [1 m];
l2 := [1 mm];
l3 := l1 + l2;
log l3;
1.001 m

Wie man an diesem Beispiel sieht, wird die Operation unter Berücksichtigung der konkreten physikalischen Einheit durchgeführt. Möchte man die Ausgabe in einer anderen Einheit (z.B. in cm) haben, wird dem Ergebnis die gewünschte Einheit zugewiesen.

l1 := [1 m];
l2 := [1 mm];
l3 := l1 + l2;
log l3;
1.001 m
l3 unit [cm];
log l3;
100.1 cm

Der Versuch der Zuweisung einer nicht passenden Einheit wird von Compiler erkannt und führt zu einer Fehlermeldung.

4.1.6 boolean

Stand: 06.04.22 16:32

Von der Klasse boolean sind genau zwei Werte instanziierbar: true und false.
Will man z.B. eine Variable t mit dem Wert true, und eine Variable mit dem Wert false belegen, notiert man im Script:

t := true;
f := false;

4.1.7 moment

Stand: 06.04.22 16:32

Die Klasse moment repräsentiert Objekte, die einen bestimmten Zeitpunkt definieren. Intern wird dieser Zeitpunkt durch die entsprechende Unix-Zeit festgelegt. (Die Zahl der vergangenen Sekunden seit Donnerstag, dem 1. Januar 1970, 00:00 Uhr UTC.)

Konkrete Zeitpunkte sind: Der Beginn und das Ende eines Fußballspiels, oder der Moment in dem der erste Mensch den Mond betreten hat.

Ein fester Zeitpunkt kann z.B. durch die Angabe von Tag, Monat und Jahr definiert werden:

m := 31.1.2019
log m;
28.2.2019 00:00,00

Oder mit Uhrzeit:

m := 31.1.2019 12:34,56
log m;
28.2.2019 12:34,56

Zu letzten Beispiel ist anzumerken, dass es sich hier im Grunde um die Addition eines Objekts moment 31.1.2019 und eines Objekts timespan 12:34,56 handelt.

In λscript gibt es eine Reihe von Funktionen mit denen ein solcher bestimmter Zeitpunkt erzeugt werden kann. Dazu zählt z.B. die Einwort-Funktionen now. Das Kommando t := now; weist der Variablen t die aktuelle Zeit zu.

Analog arbeiten z.B. die Funktionen today und hour. Nach der Abarbeitung des Kommandos

t := today;
h := hour;

hat t den Wert "heute 0:00 Uhr" – den Zeitpunkt des Beginns des aktuellen Tages und h den Wert des Beginns der laufenden Stunde.

4.1.8 timespan

Stand: 06.04.22 16:32

Die Klasse timespan repräsentiert Objekte, die eine bestimmte Zeitspanne beschreiben. Eine Zeitspanne ist die Differenz zwischen zwei Objekten von Typ moment. So ist die aktuelle Uhrzeit ein timespan und genau die Zeitspanne vom Beginn des heutigen Tages bis jetzt. Konkrete Zeitspannen sind auch: Die 90 Minuten Spieldauer eines Fußballspiels, oder die Zeit die man braucht, um von der Erde bis zum Mond zu fliegen.

Im Script auftauchende Zeichenketten der Gestalt m/d/h:min,sec werden als eine Zeitspanne von m Monaten, d Tagen, h Stunden, min Minuten und sec Sekunden interpretiert. sec kann ein mit Punkt geschriebener Dezimalbruch sein. Es ist möglich mit Bruchteilen von Sekunden zu operieren.

Dabei ist die Verwendung des Doppelpunktes, des Kommas oder die Verwendung von zwei Schrägstrichen zwingend notwendig, damit λscript die Zeichenfolge als timespan interpretiert. Beispiele:

4.1.9 stack

Stand: 06.04.22 16:32

Ein Objekt von Typ stack ist eine geordnete Menge von Objekten. Objekte können oben auf den Stapel gelegt werden oder unter den Anfang des Stapels geschoben werden. Der lesende und der löschende Zugriff ist nur auf das oberste oder das unterste Objekt möglich.

stack hat eine Eigenschaft of. Diese Eigenschaft definiert die Klasse, der alle Objekte des Stapel angehören müssen. (Instanzen von davon abgeleiteten Klassen sind hier eingeschlossen.) Der Standardwert von of ist value. Damit kann ein mit s := new stack definierter Stapel sämtliche Werte aufnehmen, da alle Objekte in λscript Abkömmlinge von value sind.

Eine Stapel s der nur Zahlen enthalten kann, wird mit

s := new stack of number;

definiert.

Auf diesen leeren Stapel können mit dem Kommando

s << 3 4;

die Zahlen 3 und 4 obendrauf gepackt werden. Das Kommando

1 2 >> s;

schiebt die Zahlen 2 und 1 unter den Stapel.

log s;

würde jetzt 1 2 3 4 ausgeben.

Das Auslesen eines Stacks funktioniert ähnlich. Die Kommandos

top := s >;
bottom := < s;

weisen den Variablen top und bottom den obersten bzw. untersten Wert des Stapels zu.

Die Kommandos

top := s >>;
bottom := << s;

bewirken dasselbe, aber das unterste bzw. oberste Element wird zusätzlich vom Stapel gelöscht.

Um alle Elemente eines Stacks in einer Schleife zu durchlaufen, dienen Kommandos mit dem Schlüsselwort forEach (siehe auch hier):

s := new stack of word;
s << "Montag" "Dienstag" "Mittwoch" "Donnerstag" "Freitag";
n := 1;
forEach s d {
   log "Der " n ". Tag ist " d;
   n'+
}

4.1.10 collection

Stand: 23.06.22 16:15

Ein Objekt von Typ collection ist eine indizierte Menge von Objekten. Als Indizes dienen Objekte von Typ word oder Symbole.

collection hat wie stack eine Eigenschaft of. Sie definiert ebenfalls die Klasse, der alle Objekte des collection angehören müssen. Der Standardwert von of ist auch hier value.

Eine Collection c in der nur Zeichenketten gespeichert werden, wird mit

c := new collection of string;

definiert. Der Zugriff auf die Elemente einer collection passiert unter zuhilfenahme eckiger Klammern. Sowohl die collection als auch der Index (ein Symbol oder ein word) werden dazu in eckige Klammer geschrieben. Das Einspeichern von Werten geht unter dann so:

[c x] := "Hallo";
[c y] := " ";
[c z] := "FHEM";
log [c x] [c y] [c z];
Hallo FHEM

Das Kommando c keys; gibt die aktuell in der collection c enthaltenen Indizes in einem Stack vom Typ "stack of word" zurück. Dem entsprechend liefert

[c x] := "Hallo";
[c y] := " ";
[c z] := "FHEM";
log c'keys;
(word|x,z,y)

Dabei ist anzumerken, dass die Reihenfolge der Schlüsselwerte nicht determiniert ist.

4.1.11 vector

Stand: 06.04.22 16:32

Ein Objekt von Typ vector ist ebenfalls eine indizierte Menge von Objekten. Als Indizes dienen die natürliche Zahlen 1, 2, 3, ...

vector hat eine Eigenschaft dimension. Der Wert von dimension muss eine natürliche Zahl sein. Er definiert den höchsten erlaubten Index. Der muss zwingend bei der Definition eines Objekte vom Typ vector angegeben werden. Eine zweite Eigenschaft ist default. Das der Wert, den jedes Element des Vektors hat, solange ihm kein Anderer zugewiesen wurde. default legt auch den Typ der in dem Vektor gespeicherten Daten fest. Ist default z.B. vom Typ string dürfen nur Zeichenketten in dem Vektor gespeichert werden.

Ein Vektor v mit zehn Elementen, die zunächst alle den Wert 1 haben, wird mit

v := new vector dimension 10 default 1;

definiert. In diesen Vektor können dann auch nur Werte vom Typ number gespeichert werden.

4.1.12 fhemReading

Stand: 06.04.22 16:32

Ein Objekt von Typ fhemReading hat zwei Eigenschaften: val und time.

val ist vom Typ string und time vom Typ moment. Sie stehen für den Wert und den Zeitpunkt der letzten Änderung des Readings eines FHEM-Devices.

Ist R eine Variable die ein Objekt vom Typ fhemReading enthält, dann ist R'val der Inhalt des Readings und R'time der Zeitpunkt der letzten Änderung.

Grundsätzlich kann auf ein Reading kann nur lesend zugegriffen werden. Eine Ausnahme bilden mit dem Kommando userReading freigeschaltene Readings.

4.1.13 fhemDevice

Ein Objekt von Typ fhemDevice bildet ein Device der FHEM-Installation ab. Objekte dieses Typs können innerhalb eines λscript nicht instanziiert werden, da sie quasi die unveränderliche Hardwareumgebung abbilden. Beim Systemstart wird für jedes gefundene Device eine Variable vom Typ fhemDevice oder einer passenden definierten Unterklasse angelegt. Der Name der Variablen stimmt mit dem Namen des Devices überein. Existiert also ein Device WZ_Lampe, dann wird eine Variable WZ_Lampe vom Typ fhemDevice angelegt. Ist WZ_Lampe ein Homematic-Gerät ist WZ_Lampe vom Typ fhemHomematicDevice

WZ_Lampe'name liefert den Namen des Devices als ein word-Objekt.

Eine wichtige Eigenschaft eine Objektes dieses Typs ist: readings. Diese Eigenschaft enthält den aktuellen Zustand des Devices. Es handelt sich dabei um ein Objekt vom Typ collection of fhemReading.

Den Wert des Readings level von WZ_Lampe erhalte ich demzufolge durch:
l := WZ_Lampe'readings'level'val;

Da auf die Werte von Readings in der Praxis häufig zugegriffen wird, gibt es dafür unter Verwendung eckiger Klammern die alternative Schreibweise:

level := [WZ_Lampe level];

Ist der Name des Readings state, reicht die Schreibweise:

state := [WZ_Lampe];

Danach stehen in level der Wert des Readings level und in state der Wert des Readings state von WZ_Lampe. Beides sind Objekte vom Typ string.

4.1.13.1 fhemLambdaDevice

Stand: 06.04.22 16:32

Ein fhemDevice, das ein λscript enthält, gehört automatisch zu dieser Klasse. Es kann seine eigenen Readings setzen, so wie jedes Device seinen internen Zustand verwaltet. Das Kommando zum Setzen eines Readings lautet:

setReading [name] [wert] ([name] [wert]);

Dabei steht [name] für den Namen des Readings und [wert] für den zu setzenden Wert. [name] kann ein Symbol oder ein Objekt von Typ word sein. Die dem ersten Parameterpaar folgende Klammer symbolisiert, das beliebig viele weitere Parameterpaare folgen können. Beispiel für das Setzen der Readings temperature und door:

setReading
   temperature 12
   door "closed"
;

Da der Wert eines Readings immer ein string sein muss, wandelt das Kommando setReading alle Werte (hier die Zahl 12) automatisch unter Verwendung der Funktion toString in eine Zeichenkette um. (Siehe: Typumwandlungen)

Es gibt auch die Möglichkeit, Readings eines Devices dieses Typs von Außen zu setzen. Ein solches Reading wird mit dem Kommando:

userReading name;

oder

userReading name widgetOverride;

definiert. Dabei ist name der Name des Readings und der optionale Parameter widgetOverride ein definiertes Element der grafischen Benutzeroberfläche von FHEM. (Siehe hier.) Ein so spezifiziertes Reading kann per FHEM-Kommando

set fhemLambdaDevice reading name value;

gesetzt werden. Für fhemLambdaDevice ist hier der Name des Devices einzusetzen. Dieses Kommnado kann in der Kommandozeile von FHEM oder innerhalb eines anderen λscripts abgesetzt werden.

4.1.13.2 fhemHomematicDevice

Stand: 06.04.22 16:32

Ein fhemDevice das ein λscript Homematic-Gerät (TYPE: CUL_HM) verwaltet, gehört automatisch zu dieser Klasse.

Hier sind Kommandos möglich, die spezifische für diese Geräteklasse sind. Z.B. Kommandos der Form:

device'channel_??

Dieses Kommando gibt das Gerät dass dem angegebenen Channel von device entspricht zurück. Existiert der Kanal nicht, bricht der Compiler mit einer Fehlermeldung ab. Ist TH-Bad ein Thermostat vom Typ "HM-TC-IT-WM-W-EU" denn liefert das folgende Kommando die aktuelle Luftfeuchtigkeit aus dem Weather-Kanal des Thermostats als Zahl in die Variable humidity.

humidity := [TH-Bad'channel_01 humidity]'toNumber;

4.1.14 astronomy

Stand: 06.04.22 16:32

Ein Objekt von Typ astronomy dient dazu, astronomische Daten bereitzustellen. Mit

mySun := new astronomy;

wird eine Variable mySun angelegt. Dabei werden der Readings latitude und longitude aus dem Device global herangezogen. Die Ausdrücke

azimut := mySun'azimut;
elevation := mySun'elevation;

liefern dann denn aktuellen Sonnenstand in ° (Typ: number).

nextSunrise := mySun'sunrise;
nextSunset := mySun'sunset;

liefern die Zeitpunkte (Typ: moment) des nächsten Sonnenauf- bzw. untergangs.

Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

4.2 Typumwandlungen

In λscript werden Objekte in der Regel nicht automatisch in andere Objektypen transformiert. Es gibt eine einzige Ausnahme: Die besteht in der Konvertierung eines Wertes vom Typ word in den Typ string bei einer Wertzuweisung an ein Objekt das als string deklariert wurde. Soll heißen:

t := "Hallo Welt!";
   
t := "lambda";

In diesem Beispiel wird eine Variable t von Typ string initialisiert. Der soll später ein Wert von Typ word zugewiesen werden. In diesem, und nur in diesem, Fall wird der Compiler eine automatische Typumwandlung vornehmen und den Übersetzungsprozess nicht mit einer Fehlermeldung beenden.

Für die Umwandlung eines Wertes in eine Wert eines anderen Typs sind einige Funktionen eingebaut. Für die anderen Fälle muss der User weitestgehend selbst sorgen. Für die Durchführung der Umwandlung eignen sich Kommandos der Gestalt:

new := old toClassname;

oder gleichwertig

new := old'toClassname;

old ist hier ein Wert, ein Repräsentant einer bestimmten Klasse. Dieser Wert wird in eine Variable new vom Typ classname umgewandelt.

4.2.1 Umwandlung nach string

Stand: 06.04.22 16:32

Für alle Klassen, gibt es eine Funktion toString, die die Werte dieser Klassen in den Typ string umwandelt. Diese Funktion wird automatisch aufgerufen, wenn ein Wert bei Ausgabekommandos (log, setReading, ...) ausgegeben werden soll.

n := 123;
s := n toString;

Diese Kommandos weisen der Variablen s die Zeichenkette "123" zu.

Für neue Klassen, die der User selbst erstellt hat, wird automatisch eine entsprechende Funktion generiert. Diese vordefinierten Funktionen können durch eigene Funktionen ersetzt und so an die jeweiligen Bedürfnisse angepasst werden.

Für die Klasse number gibt es eine Besonderheit: Sie enthält eine Eigenschaft format von Typ string. Dieser Eigenschaft kann ein Formatstring zugewiesen werden, mit der die Umwandlung in einen Strings beeinflusst wird. Dieser Formatstring ist der, den man in der Programmiersprache Perl zur formatierten Ausgabe von Zahlen verwendet. Für eine ausführliche Darstellung der Möglichkeiten gibt es zahlreichen Quellen (z.B. https://perldoc.perl.org/functions/sprintf.html). Wird dieser Formatstring einer Zahl zugewiesen, wird die Zahl bei der Umwandlung in einen String entsprechend formatiert:

pi := 3.14159265359;
pi'format := "%.3f";
log "gerundet: " pi;
gerundet: 3.142
pi'format := "";
log "original: " pi;
original: 3.14159265359

In diesem Beispiel wurde die Variable pi zunächst auf 3 Dezimalstellen gerundet ausgegeben. Nach dem Löschen des Formatstrings erfolgt die Ausgabe des originalen Wertes. Die Angabe eines Formatstrings beeinflusst also nicht die interne Darstellung der Zahl, sondern wirkt sich nur bei der Umwandlung in einen String aus.

4.2.2 Umwandlung nach boolean

Stand: 06.04.22 16:32

Neben toString gibt es eine weitere für alle Klassen definierte Typumwandlungsfunktion: toBoolean. Der Aufruf

y := x toBoolean;

setzt y auf den Wert false wenn:

In allen anderen Fällen setzt das Kommando y auf den Wert true.

4.2.3 Umwandlung von string nach number

Stand: 23.06.22 16:30

In der Praxis kommt es häufig vor, dass man Zeichenketten, die Zahlen darstellen, in echte Zahlen (Werte vom Typ number) umwandeln möchte. Dafür gibt es das Kommando toNumber

n := "123";
log n'toNumber;
123

Eine leere Zeichenkette gibt den Wert 0 zurück. Das Script bricht mit einer entsprechenden Fehlermeldung ab, wenn s nicht die String-Darstellung einer Zahl ist. Ob das der Fall ist, lässt sich mit einer Funktion (Diese besteht aus dem string gefolgt vom Symbol isNumber.) prüfen:

n := "123";
log n'isNumber;
true

s := "12 m";
log s'isNumber;
false

Für den Fall, dass man nicht sicher ist, ob der String eine Zahl darstellt, möchte man vielleicht einen Standardwert zurückgeben. Das wird mit folgendem Kommando erreicht:

s := "xyz";
n := s toNumber alternatively 123;
log n;
123

4.2.4 Umwandlung von string nach timespan

Stand: 06.04.22 16:32

Die Umwandlung von Zeichenketten, die Werte vom Typ timespan darstellen, in Werte vom Typ timespan ist funktioniert analog der Umwandlung in Zahlen. Das Kommando hierfür ist toTimespan.

s := "12:30";
log s'toTimespan;
12:30,0

Eine leere Zeichenkette gibt den Wert 0,0 zurück. Das Script bricht mit einer entsprechenden Fehlermeldung ab, wenn s nicht die String-Darstellung einer Zeitspanne ist. Ob das der Fall ist, lässt sich mit der Funktion isTimespan prüfen:

s := "12:30";
log s'isTimespan;
true

s := "12 m";
log s'isTimespan;
false

Für den Fall, dass man nicht sicher ist, ob der String eine Zeitspanne darstellt, kann ein Standardwert zurückgeben werden. Das wird mit der Verwendung von alternatively erreicht:

s := "12 m";
log (s toTimespan alternatively 12:30);
12.30

4.3 Definition neuer Klassen

Stand: 06.04.22 16:32

Eine neue Klasse kann mit folgendem Kommando definiert werden:

newClass := oldClass
   Eigenschaft1 Wert1
   Eigenschaft2 Wert2
   …
   { Initialisierungsblock }
;

newClass ist der Name der neuen Klasse. oldClass ist der Name der Klasse von der die neue Klasse sämtliche Eigenschaften erbt. Für eine komplett neue Klasse, ist für oldClass value - die Mutter aller Werteklassen - einzusetzen.

Danach folgt eine Liste der Eigenschaften, die die neue Klasse, zusätzlich zu denen von oldClass, haben soll. Dem Namen der Eigenschaft (ein Symbol) folgt ein initialer Wert. Dieser Wert kann auch empty sein. Sowohl durch die Angabe eines Wertes, als auch durch die Definition mit empty ist die Klassenzugehörigkeit dieser Eigenschaft festgelegt. Damit ist z.B. Wert1 die Instanz eine bestimmten Klasse, und wie bei einer Variablen, der Typ dier Eigenschaft festgelegt. Bei der Instanziierung der neu definierten Klasse kann einer Eigenschaft nur ein Wert zugewiesen werden, der einen entsprechenden Typ besitzt.

Nach der der Angabe der Eigenschaften kann ein Block (hier als Initialisierungsblock bezeichnet) folgen. Die Angabe des Blocks ist optional. Ist er bei der Definition der Klasse angegeben worden, dann werden die Kommandos des Blocks bei jeder Instanzierung eines Objektes von newClass abgearbeitet. Innerhalb dieses Blocks existiert eine Variable my, die den Wert des aktuellen Objektes enthält. Wurde bei der der Definition der ElternKlasse oldClass ebenfalls ein Initialisierungsblock definiert, wird dieser vor jedem abgearbeitet.

Ein Beispiel:

clsShutter := fhemDevice
   direction number
   shading 50
;

Hier wird eine neue Klasse, mit denen Rollläden gesteuert werden sollen, definiert. Die neue Klasse clsShutter hat dann alle Eigenschaften von fhemDevice, plus zwei Weitere, mit denen eine Beschattung gesteuert werden soll: direction - eine Zahl die die Himmelsrichtung angibt in die das zugehörige Fenster zeigt und shading - das Level auf das der Rollladen abgesenkt wird, wenn alle Voraussetzungen für ein notwendige Beschattung erfüllt sind. Der Wert für shading wird auf 50 % vordefiniert der Wert für direction muss bei der Instanziierung für jedes Rollo mit angegeben werden. (siehe hier)

Innerhalb des Blocks, in dem eine neue Klasse definiert wurde, kann die Definition um weitere Eigenschaften ergänzt werden. Dazu werden hinter dem Klassennamen die Eigenschaftsnamen und die entsprechenden initialen Werte notiert - völlig anlog zu Definition der neuen Klasse. Auch die Angabe eines weiteren Initialisierungsblocks ist optional möglich.

newClass
   Eigenschaftx Wertx
   Eigenschafty Werty
   …
   { Initialisierungsblock }
;

Diese Möglichkeit ist wichtig, wenn zwei Klassen definiert werden sollen, und jede eine Eigenschaft vom Typ der anderen Klasse enthalten soll. Dazu ein Beispiel:

Es soll eine Klasse clsRoom definiert werden. Diese Klasse soll unter anderem eine Eigenschaft shutters enthalten. Diese Eigenschaft ist eine Auflistung aller Rollos in einem Raum. Ein Raum sollen Instanz einer Klasse clsRoom sein. Die Klasse clsShutter soll eine Eigenschaft room besitzen die auf den Raum verweist in dem sich das Rollo befindet. Beide Klassen "in einem Zug" definieren zu wollen, ist nicht möglich, da bei der Definition der ersten Klasse clsShutterauf die ZweiteclsRoom noch nicht zugegriffen werden kann. Deshalb legt man die erste Klasse zunächst ohne die noch nicht zu definierende Eigenschaft an, und ergänzt diese nach der Definition der zweiten Klasse. Für obiges Beispiel könnte das wie folgt aussehen:

clsRoom := value name string;
clsShutter := fhemDevice room clsRoom direction number shading 50;

clsRoom shutters (stack of clsShutter);

In der ersten wird die Klasse clsRaum, nur mit der Eigenschaft name definiert. Die zweite Zeile enthält die Definition der Klasse clsShutter analog oben und der zusätzlichen Eigenschaft room. Jetzt, da dem System die Klasse clsShutter bekannt ist, kann diese bei der Erweiterung der Klasse clsRoom verwendet werden. Dieser Klasse wird jetzt eine Eigenschaft shutters zugefügt, die für einen Stapel von Werten des Typs clsShutter steht.

Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

4.4 Instanziierung von Klassen

Werte sind Objekte die von Klassen abgeleitet werden. Dieser Prozess heißt Instanziierung. Charakteristisch dafür ist die Verwendung des Schlüsselwortes new. Hier gibt es mehrere Möglichkeiten:

4.4.1 Explizite Instanziierung

Stand: 06.04.22 16:32
new cls p1 v1 p2 v2 …;

cls ist der Name der Klasse von der eine Instanz gezogen werden soll. pn und vn sind ein Symbol und ein Wert. Beide definieren die entsprechende Eigenschaft des neu zu kreierenden Objektes. Wichtig ist, dass die Eigenschaften, die in der Klassendefinition nicht mit einem Standardwert belegt sind, in dem new-Kommando festgelegt werden.

Hier gibt es eine Ausnahme: Ist eine Eigenschaft p ein Array (stack, collection oder vector) und wird diese Eigenschaft nicht explizit durch ein entsprechendes v festgelegt, dann wird während der Instanziierung für p ein leeres Array des vorgesehenen Typs erzeugt. Beispiel:

cls := value names (stack of string);
var := new cls;

Die Klasse enthält eine Eigenschaft names das aus einem Stack mit Werten vom Typ string bestehen soll. Die Variable var enthält dann nach ihrer Erzeugung einen entsprechenden leeren Stack.

4.4.2 Erweiterte explizite Instanziierung

Stand: 06.04.22 16:32
new cls var p1 v1 p2 v2 …;

Auch hier ist cls der Name der Klasse von der das Objekt abgeleitet werden soll. Auch das eben zu den pn und vn Gesagte gilt analog. Die Variable var steht für eine Variable, die eine Instanz der Klasse cls oder einer ihrer Vorfahren ist. Dann werden zunächst alle Eigenschaften von var auf das neue Objekt übertragen und danach die Paare pn und vn an das Objekt übergeben.

4.4.3 Instanziierung von Klassen

Stand: 06.04.22 16:32

Werte sind Objekte die von Klassen abgeleitet werden. Dieser Prozess heißt Instanziierung. Charakteristisch dafür ist die Verwendung des Schlüsselwortes new. Hier gibt es mehrere Möglichkeiten:

5 Variable

Variable werden definiert, indem man einem Symbol einen Wert zuweist. (Z.B. durch das Kommando: zahl := 123;) Das Symbol ist dann, bildlich ausgedrückt, der Name dieses Wertes. In λscript sind Variable statisch typisiert. D.h.: Jeder Wert ist Instanz einer Klasse. Einer Variablen kann als neuer Wert grundsätzlich nur eine andere Instanz der gleichen Klasse zugewiesen werden, mit der sie definiert wurde.

Die Kommandofolge:

zahl := 123;

zahl := "Hallo";

würde vom Compiler mit einer Fehlermeldung quittiert und die Überprüfung des Scriptes abgebrochen werden.

5.1 Direkte Definition einer Variablen

Stand: 06.04.22 16:32

Für einige Klassen (number, boolean, string, word, timespan) ist die Definition einer Variablen durch ein bestimmtes Zeichenmuster im Script möglich:

zahl := 123;
text := "Hallo Welt";
wort := "Hi";
pause := 1:30,45;
offen? := false;

Das ist weitgehend selbsterklärend. Die Definition von Ausdrücken des Typs timespan ist hier beschrieben. Genau so einfach ist die Definition einer Variablen indem man ihr den Wert einer anderen Variablen oder eines Ausdrucks zuweist.

zahl := 3 + 12 ^ 2;
text := "Ergebnis: " zahl'toString;
wort := 5 * "Hi" ;
uhrzeit := now - today;
offen? := 3 > 5;
Warning: filemtime(): stat failed for 00 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

5.2 Variable sind Zeiger

Das Prinzip der Speicherung von Objekten in einer Variablen ist im Grunde simpel: Bei der Zuweisung eines Wertes an ein Symbol wird ein leere Speicherzelle gesucht. An dieser Stelle wird das Objekt gespeichert und die Position der Speicherzelle im Symbol abgelegt. Dadurch wird das Symbol zu einer Variablen.

Ein Beispiel: Bei der Initialisierung des Symbols x mit dem Wert "Hallo" wird dem Symbol x ein Speicherplatz zugewiesen. Hier zum Beispiel der Platz mit der Nummer 178.

x := "Hallo";

Schematisch stellt sich die Situation so dar:

Variable Position Inhalt
177
x 178 Hallo
179

Wird der Variablen danach ein neues Objekt zugewiesen wird dieses Objekt an die Stelle geschrieben auf die die Variable zeigt. Genauer betrachtet liegen in diesem Konzept einige bedenkenswerte Sonderfälle versteckt. Die treten auf, wenn die Objekte, die einer Variablen zugewiesen werden sollen, Eigenschaften von anderen Objekten oder Werte anderen Variablen sind.

5.2.1 Zuweisung einer Variablen an eine Andere

Stand: 06.04.22 16:32

Wird dem Symbol y der Wert von x mit y := x; zugewiesen muss y danach auf eine andere Speicherposition als x weisen, und auf der Speicherposition von y muss der gleiche Wert wie bei x stehen.

x := "Hallo";
y := x;
Variable Position Inhalt
177
x 178 Hallo
y 179 Hallo
180

Das bedeutet, dass bei der Zuweisung einer Variablen x an eine Variable y ein Kopie des Wertes von x erstellt und diese dann y zugeordnet werden muss. Anderenfalls würden sowohl x als auch y auf den Speicherplatz 178 verweisen. Das hätte zur Folge, das jede Änderung von x in gleichem Maß auch y betreffen würde.

Dieses Verhalten ist im Allgemeinen nicht erwünscht. Deshalb wird in λscript bei der Zuweisung einer Variablen an eine Variable eine Objektkopie erstellt und diese mit der Zielvariablen verknüpft. Um den Kopieraufwand zu minimieren, wird das Kopieren eines Objektes als sogenannte flache Kopie realisiert. Erst wenn ein Schreibzugriff auf eine kopierte Eigenschaft stattfindet, wird die Kopie dieser Eigenschaft vollzogen. Dadurch wird sichergestellt, dass diese Änderung nur die Kopie erreicht und gleichzeitig werden unnötige Kopiervorgänge vermieden.

Ohne diese Vorgehensweise würde die Kommandofolge:

x := "Hallo";
y := x;
x := "Welt";
log y;
Hallo
log x;
Welt

dazu führen, dass beide Variable den Wert Welt hätten.

5.2.2 Zuweisung der Eigenschaft eines Objeks an eine Variable

Stand: 06.04.22 16:32

Im Abschnit Umwandlung nach string wird erläutert, das Werte vom Typ number eine Eigenschaft format besitzen mit der die Formatierung bei der Ausgabe der Zahlen gesteuert wird. Standardmäßig ist dieser String leer. Im nachfolgenden Beispiel wird diese Eigenschaft (von Typ string) an eine Variable s übergeben, geändert und die Zahl ausgegeben. Ziel ist, die Zahl auf drei Nachkommastellen zu runden:

pi := 3.14159265359;
s := pi'format;
s := "%.3f";
log "gerundet: " pi;
3.14159265359

Das Script zeigt nicht das erwartete Verhalten: pi wird nicht gerundet auf 3 Nachkommastellen ausgegeben. Nach dem oben Ausgeführten ist das Ergebnis erklärbar. pi'format und s zeigen nicht auf die selbe Speicherzelle. Deshalb wirkt sich die Änderung von s im Kommando s := "%.3f" nicht auf die Eigenschaft format in pi aus. Die schematische Speicherkonfiguration sieht also so aus:

Variable Position Inhalt
212
pi 213 3.14159265359…
p'format 214
s 215 %.3f
216

Um zu Erreichen, dass das Script wie gewünscht funktioniert, muss erzwungen werden, dass s auf den selben Speicherplatz zeigt wie pi'format. In λscript gibt es dafür den Operator ==. Der bewirkt, das das links von ihm stehende Symbol zu einer Variablen wird, die auf den rechts stehenden Wert zeigt. Die beiden Terme sind praktisch, bis auf den Namen, identisch — mehr als nur gleich. Mit der Verwendung dieses Operators wird das erwartete Resultat erzielt:

pi := 3.14159265359;
s == pi'format;
s := "%.3f";
log "gerundet: " pi;
3.142

Jetzt sieht die schematische Speicherkonfiguration so aus:

Variable Position Inhalt
212
pi 213 3.14159265359…
s, p'format 214 %.3f
215

5.3 Neudefinition durch implizite Instanziierung

Stand: 06.04.22 16:32

Das Kommando dafür lautet:

new var cls p1 v1 p2 v2 …;

Diese Variante unterscheidet sich syntaxtisch leicht von der erweiterten expliziten Definition eines Objektes. Vor dem Klassennamen steht eine Variable. Hier ist es die Variable var selbst, die zu einer Instanz der Klasse cls angehoben, und um die in den pn und vn bezeichneten Eigenschaften erweitert wird. Zu beachten ist hier, dass die Variable var ihren Typ ändert. Das führt nur dann nicht zu einem Konflikt, wenn var ursprünglich in einem übergeordneten Block definiert wurde. Dazu ein Beispiel:

Weiter oben wurde ein Klasse clsShutter definiert. Existiert in unserer FHEM-Installation ein Device mit dem Namen WZ_Rollo so gibt es automatisch auch eine Variable WZ_Rollo vom Typ fhemDevice. Das Kommando

new WZ_Rollo clsShutter direction 45;

macht aus einem einfachen WZ_Rollo vom Typ fhemDevice ein Objekt von Typ clsShutter. Das führt nicht zu einem Widerspruch bezüglich der statischen Typisierung, weil die Variablen, die die Devices der FHEM-Installation abbilden, im übergeordneten Superblock, der Block in dem das gesamte Script eingebettet ist, definiert werden.

5.4 Definition durch Zuweisung eines Funktionsaufrufs

Stand: 06.04.22 16:32

Eine Besonderheit der funktionalen Programmierung ist, das Variable für einen Funktionsaufruf stehen können. Das geschieht in der Weise, dass einem Symbol ein Funktionsaufruf zugewiesen wird. Diese Variable kann, wie jede andere Variable, in Kommandos verwendet werden. Mit dieser Variablen kann gerechnet und sie kann mit anderen Variablen verglichen werden. Sie kann auch als Parameter in Funktionsaufrufen dienen. Von außen ist ihr nicht anzusehen, dass sie keinen festen Wert hat. Einen festen Wert bekommt sie erst dann, wenn er für eine Kommunikation mit der Hardwareperipherie des Systems benötigt wird. Z.B. wenn der Wert für die Ansteuerung eines Devices oder eine Ausgabe in das Protokoll benötigt wird.

Das folgende Beispiel soll das demonstrieren:

x := '(random 10 20);
log x;
log x;
log x;

Das Kommando random 10 20; ruft ein Funktion auf, die eine ganzzahlige Zufallszahl zwischen 10 und 20 liefert. Dieser Funktionsaufruf wird dem Symbol x zugewiesen. (Das Apostroph verhindert die sofortige Ausführung des Kommandos. Genau so, wie ein Symbol ein Symbol bleibt wenn ihm ein Apostroph vorangestellt ist, verhindert ein Apostroph vor der Liste die Abarbeitung des in der Liste stehenden Kommandos.) Damit wird x zu einer Variablen vom Typ number - allerdings ohne einen festen Wert. Der in x gespeicherte Funktionsaufruf wird bei jeder Verwendung der Variablen x ausgeführt. In unserem Beispiel ist die Funktion random 10 20; nicht deterministisch. Sie hat einen undefinierten Rückgabewert. Deshalb sind die Ausgaben von log x; in der Regel voneinander verschieden.

Die Auswertung der Funktion kann durch das Kommando

var evaluate;

erzwungen werden. Ist var eine Variable, der ein Funktionsaufruf zugewiesen wurde, oder ist var ein berechneter Wert, in dem mindestens ein unaufgelöster Funktionsaufruf steckt, wird die Berechnung jetzt durchgeführt.

Die Stärke dieses Konzeptes zeigt sich besonders beim Einsatz in einer Prozesssteuerung. Hier ist charakteristisch, das Variable von den Zuständen äußerer Parameter abhängen und der aktuelle Wert sich mit der Zeit ändert. Trotzdem kann mit diesen noch unbekannten Werten "normal" gerechnet und das Ergebnis zu dem Zeitpunkt wenn es benötigt wird, konkretisiert werden. Dass soll nachfolgend an einem Beispiel erklärt werden:

Angenommen es gibt im System ein Device mit dem Namen LightSensor. Das Reading state dieses Sensors hat einen Wert zwischen 0 und 255, der mit der Umgebungshelligkeit des Sensors korreliert. 0 steht für absolut dunkel und 255 für helles Sonnenlicht. Ziel soll es sein, eine Device lamp einzuschalten wenn der Wert kleiner als 40 und ansonsten auszuschalten.

Diese Funktionalität soll in einer Funktion gekapselt werden, da sie für eine Vielzahl von Kombinationen von Helligkeitssensoren, Lampen und Schwellwerten benötigt wird.

Die zu definierende Funktion soll mit einem Kommando der Gestalt

lamp on if brightness < threshold;

aufgerufen werden können. Dabei sind lamp, brightness und threshold die Parameter des Aufrufs. Die Definition einer solchen Funktion wäre z.B. (siehe selbstdefinierte Funktionen):

'(fhemDevice'lamp on if number'brightness < number'threshold) {
   repeat {
      state := if (brightness < threshold) "on" "off";
      set lamp state;
      wait brightness;
   };
};

Die erste Zeile enthält die Signatur aus den Symbolen on, if und < sowie den Parametern lamp von Typ fhemDevice und brightnes und threshold vom Typ number. Mit dem Symbol repeat wird eine Wiederholschleife gestartet. In dem Wiederholblock stehen zwei Kommandos. Das Erste state := if (brightness < threshold) "on" "off"; berechnet aus den Parameter brightness und threshold den gewünschten Schaltzustand der Lampe - "on" oder off. Dieser Zustand ist aber, wenn brightness oder threshold keine festen Werte darstellen, sondern Funktionsaufrufe sind, ebenfalls unbestimmt. Erst mit dem nächsten Kommando set lamp state;, mit dem die Hardware angesprochen wird, werden die hinterlegten Funktionen aufgerufen, die Berechnungen durchgeführt und das Ergebnis an das Device übergeben. Daran schließt sich ein wait-Kommando an, mit dem auf eine Veränderung des Parameters brightness gewartet wird. Steckt hinter brightness kein Funktionsaufruf sondern ein fester Wert ist der Wartebefehl natürlich wirkungslos. (Ein übergebener konstanter Wert ändert sich ja nicht.)

5.5 Gültigkeitsbereich

Stand: 06.04.22 16:32

Auf eine Variable kann in dem Block, in dem sie definiert wurde, und in allen untergeordneten Kommandoblöcken zugegriffen werden. Überall wo der Name der Variablen in Ausdrücken auftaucht, wird er durch den Wert der Variablen ersetzt.

Nach der Abarbeitung der Anweisungen:

a := 3;
b := 5;
c := a * (8 - b);

hat die Variable c den Wert 9.

Es gibt Situationen in denen dieses Verhalten nicht gewünscht ist. Dann soll das Symbol als ein solches in einem Kommando stehen bleiben. Das ist z. B. der Fall, wenn ich in einem untergeordneten Kommandoblock einer Variablen lokal einen anderen Wert zuweisen (auch eines anderen Typs) zuweisen möchte:

a := 3;
...
{
   a := "Hi!";
   ...
}

Die Zeile a := "Hi!"; ließt sich wie folgt: Weise der Variablen a, die jetzt den Wert 3 hat den Wert "Hi!" zu. Das steht zum einen im Widerspruch zur statischen Typisierung (und führt zu einer Fehlermeldung) und zum anderen hätte die Variable a nach dem Verlassen des inneren Blockes einen geänderten Wert. Der Weg um das zu Verhindern ist, ein Apostroph vor das Symbol zu setzen:

a := 3;
...
{
   'a := "Hi!";
   ...
}

Ein vorangestelltes Apostroph verhindert, das 'a als Variable a interpretiert wird, sondern es bleibt das Symbol a. Dann ließt sie die Zeile als: Definiere eine Variable a mit den Wert "Hi!". Diese Variable "verschwindet" mit dem Verlassen des inneren Blocks. Danach hat die Variablen a, wieder den Wert 3.

Um einen Block zu kapseln, d.h. um zu Verhindern, dass in einem Block auf Variable zugegriffen werden kann, die in übergeordneten Blöcken definiert wurden, dient das Kommando: encapsulate {... block ...};. Auf in übergeordneten Blöcken definierte Klassen, Funktionen und Devices kann weiterhin zugegriffen werden. Soll auf bestimmte Variable in {... block ...} verfügbar gemacht werden, müssen diese nach dem Symbol encapsulate notiert werden:

a := 3;
b := "Hallo Welt!";
...
encapsulate a {
   a := 3 * a;
   b := a * a;
   log b;
81
   ...
};
log b;
Hallo Welt!

Die Variable a wird nach "unten" durchgereicht und die Variable b im gekapselten Block ist unabhängig von der Variablen b in übergeordneten Block.

6 Kommandos und Funktionen

Er gibt keinen prinzipiellen Unterschied zwischen Funktionen die in λscript vordefiniert sind und selbst definierten Funktionen. Vordefinierte Funktionen können durch Eigene ersetzt werden. Selbst definierte Funktion fügen sich harmonisch in die Sprache ein. Das wichtigste Prinzip aber ist: Funktionen haben Inputs und Outputs, aber keine Nebenwirkungen! Sie bearbeiten keine Daten, die ihnen nicht explizit übergeben wurden. Die Kommandos aus denen eine Funktion besteht, bilden zwar einen Block, aber es ist nicht möglich auf Variable die außerhalb des Block definiert wurden zuzugreifen. (Ein Ausnahme bilden Devices. Auf die umgebende Hardware kann immer lesend und steuernd zugegriffen werden.) Sehr wohl können aber in einer Funktion alle in übergeordneten Blöcken definierten Klassen und Funktionen verwendet werden.

6.1 Signatur eines Kommandos

Stand: 06.04.22 16:32

Funktionen werden durch Kommandos aufgerufen. Jedes Kommando ist eine Folge von Werten und Symbolen. Werte sind Instanzen von Klassen und damit kann jedem Kommando eindeutig eine Folge von Klassen und Symbolen zugeordnet werden. Diese Folge nennt man die Signatur eines Kommandos. Ein Beispiel:

(12 + 18)

Diese Kommando hat die Signatur 'number + number'. Stößt λscript auf ein Kommando, sucht es im aktuellen Block, ob für diese Signatur eine Funktion gefunden wird. Wird keine gefunden, wird im übergeordneten usw. gesucht. Wenn eine Funktion gefunden wird, dann wird sie mit den aktuellen Werten, hier 12 und 18, aufgerufen. Wird keine Funktion gefunden, beendet der Compiler seine Arbeit mit einer Fehlermeldung. In diesem Fall aber, wird das Ergebnis 30 zurückgegeben.

6.2 Definition eigener Kommandos

Stand: 06.04.22 16:32

Eine neue Funktion wird in dem Block definiert, in dem sie gelten soll. (Ihr Gültigkeitsbereich umfasst auch alle untergeordneten Blöcke.) Dazu wird zum einen die sie beschreibende Signatur, und zum anderen der Code der sie am Ende ausmacht, in einem Definitionsblock notiert.

Ersteres, die Signatur, wird in runden Klammer eingefasst. Damit λscript diese Liste nicht als ein Kommando interpretiert, wird ihr ein Apostroph ' vorangestellt. Wie, wenn einem Symbol ein Apostroph vorangestellt wird, verhindert es die Abarbeitung und die Liste bleibt unverändert. Die Klassennamen werden noch um den symbolischen Parameternamen, ebenfalls unter Benutzung von ' erweitert. Daran schließt sich der Definitionsblock mit den Kommandos an. Der Rückgabewert einer Funktion ist das Ergebnis der letzten Operation im Definitionsblock. Zur Verdeutlichung ein simples Beispiel:

Es soll eine Funktion geschrieben werden, die eine Zahle quadriert, also mit sich selbst multipliziert. Wenn a die zu quadrierende Zahl ist, soll der Funktionsaufruf a'qrt oder was identisch ist (vgl. Anmerkung um Zugriffsoperator) (a qrt) sein. Die Signatur ist also die Klasse number und das Symbol qrt. Das folgende Kommando definiert die Funktion:

'(number'x qrt) := {x x};

Die gequotete Liste enthält die Signatur: Die Klasse number mit dem symbolischen Parameter x gefolgt von dem Symbol qrt. Der symbolische Parameter ist frei wählbar. Daran schließt sich der Deklarationsblock {x x} mit den Kommandos an. Hier ist es nur ein Kommando, und weil das Multiplikationszeichen zwischen zwei Zahlen ist optional ist, wurde es weggelassen.

Diese Funktion kann nun im nachfolgenden Script eingesetzt werden. Man kann das Ergebnis anderen Variablen zuweisen oder in anderen Ausdrücken verwenden.

'(number'x qrt) := {x x};

log 25'qrt;
625
a := 3;
b := 4;
c := 5;
log (a'qrt + b'qrt - c'qrt);
0
log 2'qrt'qrt'qrt;
256

Diese Funktion kann auch in Weiteren neu definierten Funktionen verwendet werden. Nachfolgend wird eine Funktion isRrightTriangle definiert, die überprüft, ob die drei übergebenen Parameter die Seitenlängen eines rechtwinkligen Dreiecks sind. Der Rückgabewert ist vom Typ boolean.

'(number'x qrt) := {x x};

'(isRrightTriangle number'a number'b number'c) := {
   (a'qrt + b'qrt) = c'qrt
};

log (isRrightTriangle 3 4 5) ;
true
log (isRrightTriangle 56 90 105);
false
log (isRrightTriangle 68 285 293);
true

Wird die Funktion '(number qrt) im Deklarationsblock der zweiten Funktion '(isRrightTriangle number number number) definiert, funktioniert sie nur dort und ist von außen nicht sichtbar.

'(isRrightTriangle number'a number'b number'c) := {
   '(number'x qrt) := {x x};
   (a'qrt + b'qrt) = c'qrt
};

log (isRrightTriangle 3 4 5);
true
log (isRrightTriangle 56 90 105);
false
log (isRrightTriangle 68 285 293);
true
Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

6.3 Vordefinierte Kommandos

6.3.1 Kommandos mit einem Element

Stand: 06.04.22 16:32

Besteht ein Kommando nur aus einer Variablen, dann ist Wert dieser Variablen der Rückgabewert des Kommandos.

In der folgenden Tabelle sind die Kommandos aufgelistet, die nur aus einem Symbol bestehen. Hat ein Kommando einen Ergebniswert, dann kann dieses Kommandosymbol, ohne es explizit in Klammern zu setzen, in Kommandos verwendet werden. Beispiel:

m := today 12:00;
log m;
23.1.2020 12:00

Das Symbol today in der ersten Zeile wird als Kommando interpretiert, ausgeführt und zu dem Ergebnis werden 12 Stunden addiert.

SymbolErgebnisBemerkung
nowmomentRückgabe der aktuellen Systemzeit
secondmomentRückgabe der aktuellen Systemzeit, auf volle Sekunden abgerundet
minutemomentZeitpunkt des Beginns der aktuell laufenden Minute
hourmomentZeitpunkt des Beginns der aktuell laufenden Stunde
daymomentZeitpunkt des Beginns des aktuell laufenden Tages
todaymomentanalog day
tomorrowmomentZeitpunkt des Beginns des kommenden Tages
yesterdaymomentDer Zeitpunkt des Beginns des vorigen Tages
weekmomentZeitpunkt des Beginns der aktuell laufenden Woche (letzter Montag 0:00 Uhr)
monthmomentZeitpunkt des Beginns des aktuell laufenden Monats
yearmomentZeitpunkt des Beginns des aktuell laufenden Monats
eastermomentDer Zeitpunkt des Beginns des Ostersonntags im laufenden Jahr
loop Der aktuell laufende Schleifenabarbeitung (forEach ...) wird beendet und mit dem nächsten Durchlauf begonnen.
quit Die aktuell laufende Schleife (forEach ...) wird verlassen und das Script mit dem der Schleife folgenden Kommando fortgesetzt.
terminate Der aktuell laufende Thread wird sofort beendet.
stop Das λscript wird beendet.

6.3.2 Kommandos mit zwei Elementen

Stand: 06.04.22 16:32

Besteht ein Kommando nur aus zwei Werten, dann handelt es sich um eine zweistellige Verknüpfungsrelation in der das Operationssymbol optional ist. Diese Operationen sind unter Zweistellige Verknüpfungsoperationen aufgelistet.

Die nachstehende Tabelle Listet eine Auswahl an Kommandos auf, die aus einem Wert (Instanz einer bestimmten Klasse) und einem Symbol bestehen. Manchmal ist die ausgeführte Operation unabhängig von der Reihenfolge der beiden Operanden. Dann bietet die Version mit dem hinten stehenden Symbol den Vorteil, dass sie unter Verwendung des Zugriffsoperators ' kürzer geschrieben werden kann.

KommandoErgebnisBemerkung
not boolean boolean Negation: Der Ergebnisausdruck ist genau dann true, wenn der boolsche Wert false ist.
boolean not
! boolean
boolean !
- number number Zahl mit umgekehrtem Vorzeichen.
number + number Zur Zahl wird 1 addiert.
number - number Von der Zahl wird 1 subtrahiert.
value toString string Umwandlung eines Wertes in einen Wert vom Typ string. Siehe Umwandlung nach string
value toBoolean boolean Umwandlung eines Wertes in einen Wert vom Typ boolean. Siehe Umwandlung nach boolean
count collection number Anzahl der Elemente des Arrays vom Typ collection, stack oder vector.
collection count
stack count
count stack
vector count
count vector
number seconds timespan Zeitspanne mit der Länge der angegebenen Anzahl von Sekunden.
number minutes timespan Zeitspanne mit der Länge der angegebenen Anzahl von Minuten.
number hours timespan Zeitspanne mit der Länge der angegebenen Anzahl von Stunden.
number days timespan Zeitspanne mit der Länge der angegebenen Anzahl von Tagen.
number weeks timespan Zeitspanne mit der Länge der angegebenen Anzahl von Wochen.
number months timespan Zeitspanne mit der Länge der angegebenen Anzahl von Monaten. Die Zahl muss eine ganze Zahl sein.
number years timespan Zeitspanne mit der Länge der angegebenen Anzahl von Jahren. Die Zahl muss eine ganze Zahl sein.

6.3.3 Zweistellige Verknüpfungsoperationen

Stand: 06.04.22 16:32

Mit Objekten vom Typ number kann in der üblichen Weise gerechnet werden. Verwendet werden können die Grundrechenarten + - * / ^. Das Multiplikationszeichen kann, wie in der Mathematik üblich, auch weggelassen werden. Das Symbol ^ zwischen zwei Zahlen potenziert die erste Zahl. Die zweite Zahl, der Exponent, muss eine ganze Zahl sein. Es gelten die üblichen Vorrangregeln. Diese Reihenfolge kann mit Hilfe von Paaren runder Klammern ( ) verändert werden. Eine Division durch 0 oder der Versuch der Potenzierung mit einer gebrochenen Zahl führen zu einem definierten Programmabbruch. Beispiele:

a := 3 + 5 * 8;
a := 3 + 5 8;
Ergebnis ist in beiden Fällen 43
c := 3 ^ 2 + 4 ^ 2;
Ergebnis: 25
a := (3 + 5) * 8;
a := (3 + 5) 8;
Ergebnis ist in beiden Fällen 64

Es gibt eine Vielzahl von Operationen bei denen zwei Operanden (Op1 und Op2) mit Hilfe eines dazwischen stehenden Symbols (Smbl) miteinander verknüpft werden. Diese so genannten binären Operationen werden häufig, wie bei den oben erwähnten arithmetischen Operationen, einfach hintereinander geschrieben. Die Reihenfolge mit der die Operanden verknüpft werden, hängt von der Priorität ab, die dem Verknüpfungssymbol zugeordnet ist. Zuerst werden die Operanden verknüpft die das Symbol mit der höchsten Priorität verbindet. Folgen Symbole mit der gleichen Priorität aufeinander (nur durch einen Operanden getrennt) werden die Operationen von links nach rechts ausgeführt.

Die nachfolgende Tabelle verschafft einen Überblick über die möglichen Verknüpfungsoperationen. Sie ist nach der Priorität der definierenden Symbol geordnet. Das Symbol mit der niedrigsten Priorität steht ganz oben. In den Spalten OP 1 und OP 2 sind die Klasse der der Operand angehören muss notiert. Dort kann auch einen von diesen Klasse abgeleitete Klasse stehen. (Wo string steht, könnte auch word stehen, und wo number steht könnte auch measurement stehen, da diese Klassen von jenen abgeleitet sind. Der Eintrag value bedeutet, da alle Werte Abkömmlinge der Klasse value sind, dass hier ein beliebiger Wert stehen kann.

SmblOp1Bemerkung
Op2
Ergebnis
or
|
booleanoder- Verknüpfung. Der Ergebnisausdruck ist genau dann true, wenn einer der beiden Operanden true ist.
and
&
booleanund - Verknüpfung. Der Ergebnisausdruck ist genau dann true, wenn beiden Operanden true sind.
=valueTest auf Gleichheit. Objekte sind gleich, wenn sie der selben Klasse angehören und in allen Eigenschaften übereinstimmen. Gehören die Operanden unterschiedlichen Klassen an, beendet der Compiler das Programm mit einer Fehlermeldung.
boolean
!=valueTest auf Ungleichheit. Objekte sind ungleich, wenn sie der selben Klasse angehören und in mindestens einem Merkmal nicht übereinstimmen. Gehören die Operanden unterschiedlichen Klassen an, beendet der Compiler das Programm mit einer Fehlermeldung.
boolean
<= <
> =>
<=> <>
valueVergleich von Objekten des Typs number, moment, string und aller Instanzen von Klassen, über denen die Operation <= definiert wurde. (Siehe auch hier: Vergleichsoperationen) Strings werden lexikografisch verglichen. Der erste Zeitpunkt ist kleiner als der Zweite, wenn er vor diesem liegt.
boolean
~stringDie Zeichenkette wird darauf getestet ob der Wert des regExpr auf sie passt.
regExpr
boolean
!~stringDie Zeichenkette wird darauf getestet ob der Wert des regExpr nicht auf sie passt.
regExpr
boolean
+numberZahlen werden addiert.
+stringZeichenketten zusammengefügt. Das Symbol + ist optional.
+timespanDie Zeitspannen werden addiert. Das Symbol + ist optional.
+timespanDas Ergebnis ist der Zeitpunkt der gegenüber moment um timespan versetzt ist. Das Symbol + ist optional.
moment
moment
+momentDas Ergebnis ist der Zeitpunkt der gegenüber moment um timespan versetzt ist. Das Symbol + ist optional.
timespan
moment
-numberZahlen werden subtrahiert.
-momentBerechnung der Zeitspanne zwischen zwei Zeitpunkten.
moment
timespan
-momentBerechnung eines Zeitpunktes der um den Wert von timespan vor moment liegt.
timespan
moment
*numberMultiplikation von Zahlen. Das Symbol * ist optional.
*numberVervielfachung eines Strings. number muss ganzzahlig und 0 oder größer sein.
string
/numberDivision von Zahlen. Der Versuch einer Division durch 0 führt zu einem Programmabbruch.
^numberPotenzierung mit einem ganzzahligen Exponenten. Ist der zweite Operand nicht ganzzahlig, oder sind beide Operanden gleich 0 wird das Programm mit einer Fehlermeldung abgebrochen.

6.3.4 Operationen über bestimmte Klassen

6.3.4.1 boolean

Hier werden einerseits die Kommandos aufgeführt, die logische Werte als Ergebnis haben. Andererseits die Kommandos, die logische Werte als Ausgangspunkt benutzen. Eine wichtige Gruppe von Funktionen mit einem boolschen Wert als Ergebnis sind Vergleichsoperationen.

Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

6.3.4.1.1 Vergleichsoperationen

6.3.4.1.1.1 Test auf Gleichheit von Objekten

Stand: 06.04.22 16:32

Zum Vergleich von Objekten werden die Symbole = und != verwendet. Das Ergebnis eines solchen Vergleiches ist immer von Typ boolean. Die Bedeutung der Symbole bei Zahlen liegt auf der Hand. Die Operationen für = und != sind für alle Klassen, auch für vom Nutzer angelegte, automatisch definiert.

Zwei Objekte sind gleich, wenn sie der selben Klasse entstammen und alle Eigenschaften, die die Objekte ausmachen, paarweise gleich sind. In diesem Fall ist das Ergebnis des Vergleichs true. Stammen zwei Objekte aus unterschiedlichen Klassen oder unterscheiden sie sich in mindestens einem Merkmal, sind sie ungleich, und das Ergebnis ist der Wert false.

6.3.4.1.1.2 Ordnen von Objekten

Stand: 06.04.22 16:32

Über den Objekten einiger Klassen ist die Operation <= definiert. Z.B. für Zahlen (number measurement), Zeichenketten (string word) und Zeitpunkten (moment). Hier ist die Bedeutung des Symbols <= unmittelbar einleuchtend. Diese Operation definiert eine Ordnung über die Objekte.

Es kann sehr hilfreich sein, eine solche Funktion auch über andere Klassen zu definieren.

Die Operation mit dem Symbol <= muss, in der Sprache der mathematischen Algebra gesprochen, eine "totale Quasiordnung" sein. Dazu muss bei der Definition sicher gestellt werden, dass für alle möglichen Objekte x, y und z einer Klasse, folgende Bedingungen gelten:

Totalität
Der Vergleich x <= y muss immer einen boolschen Wert (true oder false) liefern.
Reflexivität
Für Objekte x und y, die gleich im Sinne des vorhergehenden Abschnitts sind, muss x <= y den Wert true ergeben.
Transitivität
Für alle Objekt x, y und z der Klasse muss gelten: Liefern die Vergleiche x <= y und y <= z den Wert true, muss auch der Vergleich x <= z den Wert true ergeben.

Wurde die Operation <= definiert, existieren automatisch auch die Operationen mit den Symbolen <, => und > und müssen nicht explizit definiert werden. Diese sind dann wie folgt definiert:

Außerdem sind zwei weitere Operationen definiert. Dazu führt folgende Überlegung: Für zwei beliebige Werte x und y kann sowohl x <= y als auch y => x gelten. Das bedeutet nicht, dass x = y gilt. Sondern: Sie sind nur gleich bezüglich der definierten Ordnungsrelationelation <=.

Dieser Fall lässt sich mit dem Kommando x <=> y testen. Der Rückgabewert dieses Ausdrucks ist genau dann wahr, wenn sowohl x <= y als auch y <= x zutreffen. D.h., x und y sind im Sinne der Ordnungsrelation gleich.
Trifft genau nur eine der beiden zu Aussagen, x <= y oder y <= x, zu, liefert das Kommando x <> y den Wert true. D.h., x und y sind im Sinne der Ordnungsrelation ungleich.

Für die Klassen number, string, moment, measurement, moment ist jeweils ein Kommando x <= y definiert. Diese Definitionen sind mit dem üblichen Verständniss des Zeichens <= in Übereinstimmung.

Die Definition einer solchen Ordnungsrelation <= ist nicht in jeder Klasse sinnvoll möglich. Z.B. bei dem Datentyp timespan. Es lässt sich nämlich nicht allgemein sagen, ob ein Monat (1/0/0:00) mehr oder weniger als 30 Tage (0/30/0:00) hat. Deshalb führen die Kommandos:

a := 1/0/0:00;
b := 30/0:00;
v := a > b;

zu einer Fehlermeldung des Compilers und das λscript wird nicht gestartet.

Ist über einer Klasse (z.B. cls) eine Ordnungsrelation definiert, dann existieren neben den Operationen <, => , >, <=> und <> automatisch auch einige weitere Funktionen die auf der Existenz einer Ordnungsrelation beruhen:

a := min x1 x2 ... xn;
b := max x1 x2 ... xn;

Die erste Funktion liefert das kleinste Objekt der Werte x1 bis xn. Die zweite Funktion entsprechend das größte Objekt. Dabei müssen die Argumente sämtlich vom der Klasse cls abgeleitet worden sein.

Nachfolgend ein Beispiel für die Verwendung von selbst definierten Ordnungsrelationen:

Betrachtet werden soll die Menge der Schüler eine Schule. Jeder Schüler ist Repräsentant einer Klasse clsSchüler. Jeder Schüler hat zwei Merkmale: seinen Namen und die Klassenstufe. Der Name ist von Typ string und die Klassenstufe von Typ number. Der Standardwert ist hier 1. Die Definition der Klasse Schüler sieht wie folgt aus.

clsSchüler := value
   name string
   stufe 1
;

Die einzelnen Schüler (Max Meier in Klasse 1; Ina Schröder in Klasse 3; ...) werden nun wie folgt angelegt:

S1 := new clsSchüler name "Max Meier";
S2 := new clsSchüler name "Ina Schröder" stufe 3;
S3 := new clsSchüler name "Petra Krüger Dingelstedt";
...

Nun wird eine Vergleichsrelation definiert, die sich auf die Klassenstufe bezieht.

'(clsSchüler'x <= clsSchüler'y) := {x'stufe <= y'stufe};

Dabei wird auf die vordefinierte Vergleichsrelation bei Zahlen zurückgegriffen. Nun lassen sich unter Verwendung der zusätzlich zur Verfügung stehenden Kommandos einige Fragen beantworten:

# Sind Schüler 1 und Schüler 3 in der selben Klasse?
log (S1 <=> S3);
true
# Ist Schüler 2 in einer Klasse unter Schüler 1?
log (S2 < S1);
false
# Sind Schüler 1 und Schüler 2 in verschiedenen Klassen?
log (S1 <> S2);
true

6.3.4.1.2 Verknüpfung boolscher Werte

Stand: 06.04.22 16:32

Logische Werte können mit den Symbolen ! & | verknüpft bzw. verändert werden. Anstelle von & kann auch and, statt | das Symbol or und satt ! auch not verwendet werden. Wie allgemein üblich führt das Symbol & zu einer und-Verknüpfung und | zu einer oder-Verknüpfung. Mit dem Zeichen ! ist die Negation verknüpft. Es kann dem zu negierenden boolschen Ausdruck sowohl vor- als auch nachgestellt sein.

a := true;
b := false;
log (a & b);
false
log (a | b);
true
log (! b);
true
log a'!;
false

6.3.4.1.3 Ablaufsteuerung durch boolsche Werte

Stand: 06.04.22 16:32

Kommandos mit dem Symbol if dienen z.B. dazu, in Abhängigkeit vom Wert eines boolschen Ausdrucks, einer Variablen den einen oder einen anderen Wert zuzuweisen, oder um das Script mit dem einen oder einem anderen Programmzweig fortzusetzen. Dafür gibt es mehrere Varianten.

Variante 1:

if boolean
   { Block1 }
   { Block2 }
;
weitere Kommandos

Hier wird in Abhängigkeit davon, ob der dem Symbol if folgende Wert true oder false ist, das Script mit dem Block1 oder dem Block2 fortgestzt. Danach werden, falls vorhanden, die weiteren Kommandos abgearbeitet. Fehlt der Block2 und ist der boolsche Wert false wird sofort mit den Kommandos nach dem if-Befehl fortgesetzt.

Variante 2:

if
   boolean1
      { Block1 }
   ...
   booleank
      { Blockk }
   { Block }
;
weitere Kommandos

Diese Variante ist eine Verallgemeinerung der Variante 1. Auch hier wird, wenn der boolsche Wert true ist, der unmittelbar folgende Kommandoblock ausgeführt und danach mit den weiteren Kommandos fortgesetzt. Ist keiner der boolschen Werte true wird der letzte Block ausgeführt - wenn er angegeben ist. Dieser Block ist optional. Fehlt er, und ist kein einziger boolscher Wert true, werden die weiteren Kommandos abgearbeitet.

Variante 3:

if wert
   wert1
      { Block1 }
   ...
   wertk
      { Blockk }
   { Block }
;
weitere Kommandos

Diese Variante arbeitet ähnlich wie Variante 2. Der Block1 wird ausgeführt wenn der Vergleich wert = wert1 zu true ausgewertet wird. Analog Block2 usw.

Ähnlich wie in den obigen Varianten der Programmablauf mit boolschen Werten gesteuert werden kann, ist auch eine gesteuerte Wertzuweisung möglich. Auch hier gibt es wieder mehrere Optionen.

Variante 1:

Objekt := if bool wert1 wert;

Hier wird in Abhängigkeit davon, ob bool den Wert true oder false hat, dem Objekt der Wert wert1 oder wert2 zugewiesen. Wichtig ist, das die Angabe des alternativen Wertes wert nicht optional ist. Diese Form ist ein Spezialfall der

Variante 2:

Objekt:= if bool1 wert1 ... booln wertn wert;

Hier wird, wenn ein boolscher Wert true ist, Objekt der nachfolgende Wert zugewiesen. Sind alle boolschen Werte false, bekommt Objekt den Wert wert. Auch hier ist die Angabe von wert zwingend erforderlich.

Variante 3:

Objekt := if test
   test1 wert1
   ...
   testn wertn
   wert
;

Auch hier wird, wenn test = test1 wahr ist, an Objekt der Wert wert1 übertragen, wenn test = test2 wahr ist, an Objekt der Wert wert2 übertragen usw. Sind alle Vergleiche falsch, bekommt Objekt den Wert wert. Auch hier ist die Angabe von wert zwingend.

6.3.4.2 number

6.3.4.2.1 Zählschleifen

Stand: 06.04.22 16:32

Mit dem Kommando:

forEach k -10 11 2 {
   Block
};

wird der Block mehrfach durchlaufen. Zuerst mit dem Wert -10 für die Variable k. Dann wird k um den Wert 2 erhöht. Der Block wird erneut durchlaufen usw. Der letzte Durchlauf findet mit dem Wert 10 statt, da der nächste Wert 12 größer als die obere Grenze ist. Die Schrittweite (hier 2) muss größer als 0 sein. Die Laufvariable (hier k) muss entweder als Variable vom Typ number angelegt worden sein, oder, wenn sie nicht existiert, wird sie an dieser Stelle angelegt. Steht k für den Wert einer anderen Klasse bricht der Compiler mit einer Fehlermeldung ab.

Nach dem Durchlauf der Schleife hat die Laufvariable den letzten Wert mit dem der Block durchlaufen wurde.

Wird die letzte Zahl (die Schrittweite) nicht angegeben wird dafür 1 genommen. Wird der Startwert nicht angegeben wird dafür ebenfalls 1 angenommen. Beispiele:

forEach i 5 {
   log i;
}
1
2
3
4
5
forEach i 5 9 {
   log i " zum Quadrat = " (i i);
}
5 zum Quadrat = 25
6 zum Quadrat = 36
7 zum Quadrat = 49
8 zum Quadrat = 64
9 zum Quadrat = 81

6.3.4.3 string, word

6.3.4.3.1 Zusammensetzen von Strings

Stand: 06.04.22 16:32

Das hier Gesagte gilt sowohl für Objekte von Typ word als auch von Typ string.

Zusammengefügt werden Zeichenketten in dem man die entsprechenden Objekte hintereinander schreibt. Beispiel:

x := "Hallo " "Welt" "!";

Die Verwendung des Symbols + als Verknüpfungsoperator ist optional möglich. Obiges Kommando ist dem Folgenden äquivalent:

x := "Hallo " + "Welt" + "!";

Danach hat x den Wert "Hallo Welt!". Logischerweise ergibt sich beim Zusammenfügen von Objekten des Typs word wieder ein Objekt des Typs word. Befindet sich in der Folge der zusammen zu fügenden Zeichenketten ein Objekt string ist das Ergebnis auch vom Typ string.

6.3.4.3.2 regEx mit Strings

Stand: 06.04.22 16:32

Beim Test, ob Zeichenketten eine bestimmte Struktur oder einen bestimmten Inhalt haben kann die regEx-Syntax benutzt werden. Als charakteristisches Zeichen wird hier die Tilde ~ benutzt. Beispiel:

string := "Hallo Welt!";
log (string ~ /welt/);
false
log (string ~ /welt/i);
true
log (string ~ /Welt/);
true

Für eine ausfühliche Dokumentation von regEx sei auch an dieser Stelle auf die umfangreiche Literatur verwiesen.

Auch für die direkte Bearbeitung von Zeichenketten wurde das Konzept von regEx implementiert - mit dem Präfix s. Beispiel:

string := "Hallo Welt!";
string ~ s/Welt/User/;
log string;
Hallo User!

Über die Arbeitsweise von regEx gibt es eine Vielzahl von Dokumentationen. Deshalb wird sie an dieser Stelle nicht vertieft.

6.3.4.4 moment, timespan

6.3.4.4.1 Operationen mit moment und timespan

Stand: 06.04.22 16:32

Für die Verknüpfung von Zeitpunkten und Zeitspannen sind eine Reihe von Funktionen vordefiniert. Für die Addition und Subtraktion dieser Objekte sei zunächst auf die Tabelle in Zweistellige Verknüpfungsoperationen verwiesen. Ergänzend soll an dieser Stelle auf ein paar Dinge hingewiesen werden:

Dazu ein paar Beispiele:

m := 31.1.2019;
log m;
31.1.2019 00:00,00
m := 31.1.2019 12:34,56;
log m;
31.1.2019 12:34,56
m := 31.1.2019 1/0/0
log m;
28.2.2019 00:00,00
m := 31.1.2019 1/0/12
log m;
28.2.2019 12:00,00

Aus einem Objekt vom Typ moment lassen sich durch die Symbole second, minute, hour, day, weekday, week, month, year die entsprechenden Bestandteile aus dem Wert des Objektes "herauslösen".

m := 31.1.2019 12:34,56;
log m'second;
56
log m'minute;
34
log m'hour;
12
log m'day;
31
log m'weekday;
4
log m'week;
5
log m'month;
1
log m'year;
2019

weekday liefert die Nummer des Wochentags zurück. (1 = Montag, 2 = Dienstag, ... , 7 = Sonntag)

Steht man vor der Aufgabe festzustellen, ob die aktuelle Uhrzeit in einem bestimmten Intervall, z.B. zwischen 8:00 Uhr und 12:30 Uhr, liegt, kann das folgende Kommando benutzt werden:

log [8:00 12:30];
true

Die beiden in den eckigen Klammern stehen Argumente sind von Typ timespan. Beide Werte dürfen keine Monatsangabe besitzen und die Länge des Zeitintervalls darf nicht kleiner als 0 und darf nicht größer als eine Tageslänge (24:00) sein. Das wird zur Laufzeit überprüft und das Script bei Nichterfüllung dieser Bedingungen mit einer Fehlermeldung beendet.

Das Ergebnis ist der boolsche Wert true, wenn die aktuelle Uhrzeit im angegebenen Intervall liegt. Ansonsten ist false der Rückgabewert.

Ist der zweite Wert in der eckigen Klammer kleiner als der Erste, wird das entgegengesetzte Ergebnis zurückgegeben. Dadurch werden "Mitternacht übergreifende" Auswertungen realisiert.

log [23:00 6:00];

Diese Abfrage liefert true zwischen Abends 11 Uhr und Morgens 6 Uhr und sonst false.

Steht man vor der Aufgabe festzustellen, ob die aktuelle Zeit vor oder nach einem definierten Zeitpunkt (z.B. heute 12:00 Uhr) liegt, lässt sich das so realisieren:

isVormittag := [0:00 12:00];
isNachmittag := [12:00 24.00];

Danach haben die Variablen isVormittag und isNachmittag den Wert true oder false je nachdem, ob die aktuelle Uhrzeit vor oder nach 12:00 Uhr ist.

6.3.4.4.2 Konvertierung von number nach timespan

Stand: 06.04.22 16:32

Für den Fall, das Werte von Typ number in Zeitspannen umgewandelt werden sollen, stehen einige Möglichkeiten bereit. Angenommen n ist eine Zahl, so lässt sich mit den folgenden Kommandos diese Zahl in eine entsprechende Anzahl von Sekunden, Minuten, Stunden, Tagen, Wochen, Monate und Jahre umrechnen:

n = 3;
log n'seconds;
,3
log n'minutes;
3,0
log n'hours;
3:0,0
log n'days;
3/0:0,0
log n'weeks;
21/0:0,0
log n'months;
3/0/0:0,0
log n'years;
36/0/0:0,0

Die Ausgangswerte für eine Umrechnung in Monate oder Jahre müssen ganze Zahlen sein. Anderenfalls endet λscript mit einer Fehlermeldung.

6.3.4.5 collection

6.3.4.5.1 Existiert ein Element mit einem bestimmten Schlüssel

Stand: 06.04.22 16:32
collection exist word|symbol;?
BeschreibungDieses Kommando gibt true oder false, je nach dem, ob das Objekt vom Typ collection ein Element mit dem Schlüssel vom Typ word (oder Symbol) enthält oder nicht.
Rückgabewertboolean
Beispielcoll := new collection;

if (coll exist "x") { … };
collection notExist word|symbol;?
BeschreibungDieses Kommando gibt false oder true, je nach dem, ob das Objekt vom Typ collection ein Element mit eimem Schlüssel vom Typ word (oder Symbol) enthält oder nicht.
Rückgabewertboolean
Beispielcoll := new collection;

if (coll notExist x) { … };

6.3.4.6 fhemDevices

Es gibt eine Reihe von vordefinierten Kommandos um auf die in einer Installation enthaltenen Objekte vom Typ fhemDevices zuzugreifen:

set fhemDevice (value|symbol);?
BeschreibungDieses Kommando führt einen set-Befehl an einem Fhem-Device aus. Dazu werden alle dem Device folgenden Parameter zu einem String (durch Leerzeichen getrennt) zusammengefasst. Werte werden mit Hilfe von toString in Strings umgewandelt.
Rückgabewert-
Beispielset Lamp on;
level := 50; set Rollo pct level;
[fhemDevice word|symbol];?
BeschreibungDieses Kommando gibt den Wert eines Readings zurück.
Rückgabewertstring
Beispiels := [Thermostat humidity];
[fhemDevice word|symbol time];?
BeschreibungDieses Kommando gibt den Zeitpunkt der letzten Änderung eines Readings zurück.
Rückgabewertmoment
Beispielt := [Thermostat humidity time];
[fhemDevice];?
BeschreibungDieses Kommando gibt den Wert des Readings state zurück.
Rückgabewertstring
Beispiels := [Thermostat humidity];
[fhemDevice word|symbol time];?
BeschreibungDieses Kommando gibt den Zeitpunkt der letzten Änderung des Readings state zurück.
Rückgabewertmoment
Beispielt := [Thermostat time];
[fhemDevice internal word|symbol];?
BeschreibungDas Kommando gibt aus dem Register Internals den Wert mit dem Namen der durch word oder ein Symbol bezeichnet ist zurück.
Rückgabewertstring
Beispielt := [Thermostat internal TYPE];
Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

6.3.4.6.1 fhemLambdaDevice

setReading (@symbol|word value);?
BeschreibungDieses Kommando setzt in dem Device, welches das aktuelle lambda-script enthät, das mit einem Symbol oder mit word bezeichneten Reading auf den angegebenen Wert. Die value wird mit Hilfe des Kommandos value'toString in einen Wert vom Typ string umgewandelt.
Rückgabewert-
BeispielsetReading status "ein";
initReading (@symbol|word value);?
BeschreibungDieses Kommando arbeitet analog obigem setReading. Allerdings werden die Readings nur gesetzt wenn sie zum Zeitpunkt des Aufrufs noch nicht existieren.
Rückgabewert-
BeispielinitReading status "aus";
Warning: filemtime(): stat failed for 10 index.php in /customers/8/7/e/lambda-script.org/httpd.www/index.php on line 87
Stand: 01.01.70 00:00

6.3.4.6.2 fhemHomematicDevice

fhemHomematicDevice channel_nn;?
BeschreibungDieses Kommando gibt das Fhem-Device zurück, das dem Channel mit der angegebenen Nummer entspricht.
RückgabewertfhemHomematicDevice
Beispielwz_weather := WZ_Thermostat channel_01;

6.3.4.7 forEach - Schleifen mit wechselnden Inhalten

6.3.4.7.1 Durchlaufen eines Stacks

Stand: 06.04.22 16:32

Ist myStack eine Variable vom Typ stack wird mit dem Kommando

forEach myStack v {
   Block
};

der Block für jedes Element (beginnend mit dem untersten Element) des Stacks abgearbeitet. Der Stack selbst wird dabei nicht verändert. Die Variable v muss entweder als Variable vom Typ der Elemente des Stacks angelegt worden sein, oder, wenn sie nicht existiert, wird sie an dieser Stelle angelegt. Steht v für den Wert einer anderen Klasse bricht der Compiler mit einer Fehlermeldung ab.

Nach dem Durchlauf der Schleife hat v den Wert des obersten Elements des Stapels.

6.3.4.8 Zufallsfunktionen

6.3.4.8.1 random - Zufallszahlen

Stand: 06.04.22 16:32

Zufallszahlen werden mit Kommandos die das Symbol random enthalten erzeugt. Dem Symbol können ein oder zwei ganze Zahlen folgen. Werden zwei Zahlen angegeben gibt die zugehörige Funktion ganzzahlige Zufallszahlen, die nicht kleiner als die Erste und nicht größer als die Zweite sind, zurück. Die zweite Parameter muss dann selbstverständlich größer als der Erste sein. Ist das nicht der Fall, wird das Script mit einer Fehlermeldung beendet.

log (random 0 6);
5
log (random 0 6);
1
log (random 0 6);
6
log (random 0 6);
0
log (random 0 6);
3

Folgt dem Symbol random nur eine Zahl, muss diese größer als 1 sein, und es werden Zufallszahlen zwischen 1 und diesem Parameter erzeugt.

6.3.4.8.2 shuffle - Mischen eines Stacks

Stand: 06.04.22 16:32

Das Kommando myNewStack := myStack shuffle; kopiert die Elemente des Stacks myStack in zufälliger Reihenfolge in den Stack myNewStack.

myStack := (new stack of word) << "a" "b" "c" "d";
log myStack;
(word|a,b,c,d)
log myStack'shuffle;
(word|c,d,a,b)
log myStack;
(word|a,b,c,d)

6.3.5 wait - Wartekommandos

Wartekommandos dienen dazu, die Abarbeitung eines Scrips zu stoppen und an einem definierten Punkt fortzusetzen. Dieser Punkt kann sein:

Wurden mehrere Bedingungen angegeben, wird der Wartezustand beendet, wenn einer der obigen Punkte eingetreten ist.

Das charakteristische Symbol für die Programmierung von Wartezuständen ist wait. Es gibt mehrere, nachfolgend beschriebene, Varianten des wait-Kommandos.

6.3.5.1 wait - Basisform

Stand: 06.04.22 16:32

Die grundlegende Form des wait-Kommandos ist:

wait v1 v2 … vn;

v1 bis vn sind Werte und stellen die Parameter des wait-Kommandos dar. Hat ein übergebener Parameter den Wert empty, wird der Parameter nicht berücksichtigt. Das Kommando stoppt den laufenden Thread. Dieser Wartebezustand wird beendet, wenn einer der Parameter "feuert". Der Rückgabewert des wait-Kommandos ist die Nummer der Parameters der gefeuert hat. Feuert der Parameter v1 ist der Rückgabewert 1. Feuert der Parameter v2 ist der Rückgabewert 2. ...

Der Rückgabewert ist 0, wenn das wait-Kommando nicht ausgeführt wird. Das ist z.B. der Fall, wenn alle Parameter den Wert empty haben oder die Zeitpunkte auf die gewartet werden soll, sämtlich in der Vergangenheit liegen.

Ein Parameter feuert, wenn einer dieser Fälle eintritt:

Nachfolgend Beispiel mit dem Bezug auf ein fhemDevice. Hier wird der Rückgabewert des wait-Kommandos in einer Variablen w gespeichert:

w := wait 1:00 [WZ_Thermostat];
if (w = 1) {
   command 11;
   ...
   command 1n;
} (w = 2) {
   command 21;
   ...
   command 2n;
};

In diesem Beispiel wird darauf gewartet, dass sich das Reading state des Devices WZ_Thermostat ändert. Unabhängig davon, wird der Wartezustand spätestens nach einer Stunde beendet. In diesem Fall ist der Rückgabert des wait-Kommandos 1 und es wird der erste Kommandoblock abgearbeitet. Ändert sich aber vor dem Ablauf der Stunde das Reading state, ist der Rückgabewert 2 und es wird der zweite Komamndoblock abgearbeitet.

6.3.5.2 wait - Blockform

Stand: 06.04.22 16:32

Von der obigen Variante ist diese Form des Aufrufs abgeleitet:

wait a1 … an {blockA} b1 … bn {blockB}   …   v1 … vn {blockV};

Der letzte Block {blockv} ist optional.

Der Wartezustand wird hier beendet, wenn einer der Parameter a1 bis vn feuert. Feuert einer der Werte von a1 bis an wird der Block {blockA} abgearbeitet und danach mit den Kommandos die dem wait-Befehl folgen fortgesetzt. Analog wird, wenn einer Werte b1 bis bn feuern, der Block {blockB} ausgeführt, usw. Ist {blockV} nicht angegeben, und feuert einer der Parameter v1 bis vn, wird unmittelbar mit dem Kommando nach dem wait-Befehl fortgesetzt.

Das Beispiel aus dem vorhergehenden Abschnitt lässt sich damit wie folgt schreiben:

wait 1:00 {
   command 11;
   
   command 1n;
   } [WZ_Thermostat] {
   command 21;
   
   command 2n;
   }
;

6.3.6 threads - Parallel ablaufende Scripte

Stand: 06.04.22 16:32

Das charakteristische Symbol für den Start von parallel laufenden Prozessen ist thread.

Eine Möglichkeit des Aufrufs ist:

thread {
   ...
};

Das heißt, dem Schlüsselwort thread folgt ein Kommandoblock. Dieser wird gestartet und abgearbeitet. Ein Thread wird bis zu einem wait-Kommando ausgeführt. Danach wird gewartet bis ein anderer Thread bereit ist. In einem Thread kann auf alle in den übergeordneten Blöcken definierten Devices, Klassen und definierten Funktionen zugegriffen werden. Ansonsten ist der Kommandoblock des Threads gekapselt. Das bedeutet, der Zugriff auf die in den übergeordneten Blöcken verwendeten Variablen ist so nicht möglich. Sollen innerhalb des Threads Variable von außerhalb verwendet werden, müssen diese nach dem Schlüsselwort thread explizit aufgelistet werden.

a := "Abend!";
thread a {
   log "Guten " a;
};
log "Guten Morgen!";

Dieses Script gibt die beiden Nachrichten "Guten Morgen!" und "Guten Abend!" aus. Die Reihenfolge mit der das geschieht ist nicht definiert, da das System entscheidet, welcher der Threads wann abgearbeitet wird. Das ist im nachfolgenden Beipiel anders:

a := "Abend!";
w := 0;
thread a w {
   wait w;
   log "Guten " a;
};
log "Guten Morgen!";
w := 1;

Hier wird zuerst "Guten Morgen!" und danach "Guten Abend!" ausgegeben. Der Grund ist: Dem untergeordneten Thread wird eine zweite Variable w übergeben. wait w; stoppt den Thread bis sich w ändert. Diese Änderung durch das Kommado w := 1; erfolgt erst nach der Ausgabe von "Guten Morgen!".

Threads können ineinander verschachtelt werden. Das heist, in Threads können wieder Threads gestartet werden. Genau genomment ist es sogar so, dass jedes λscript einer FHEM-Installation ein Thread eines systemweiten Scripts ist.

Mit dem Kommando terminate; wird ein laufender Thread beendet. Gleichzeig werden auch alle Threads beendet die in diesem gestartet wurden.

w := 0;
thread w {
   wait w;
   log "Guten Abend!";
   terminate;
   log "Gute Nacht!";
};
log "Guten Morgen!";
w := 1;

Hier wird "Gute Nacht!" nicht mehr ausgebeben, weil der Thread vorher beendet wurde.

Verwandt mit dem Kommando terminate; ist das Kommando stop;. Damit wird das gesamte λscript beendet.

6.3.7 Wiederholung von Programmteilen

6.3.7.1 repeat

Kommandos mit dem Symbol repeat dienen dazu, Kommandoblöcke wiederholt zu durchlaufen. Um einen Kommandoblock unendlich oft zu wiederholen, dient das Kommando:

repeat {
   command1;
   ...
   commandk;
};

Soll der Block n-mal (n ist eine ganze Zahl ≥ 0) wiederholt werden, wird die Anzahl nach dem Schlüsselwort notiert:

repeat n {
   command1;
   ...
   commandk;
};
repeat 10,0 {
   set WZ_Lampe toggle;
};

Vor die Zeitspanne, kann eine Zeitpunkt (Typ moment) gesetzt werden. Liegt dieser Zeitpunkt in der Zukunft, wird mit der ersten Ausführen des Block gewartet bis der Zeitpunkt erreicht wird. Liegt der Zeitpunkt in der Vergangenheit, wird mit der erstmalige Ausführen des Block gewartet, bis durch wiederholte Addition der Zeitspanne ein zukünftiger Zeitpunkt erreicht wird.

repeat 22:00'today 10,0 {
   set WZ_Lampe toggle;
};

Auch hier kann als dritter Parameter eine ganze Zahl ≥ 0 angegeben werden. Dann wird die Schleife so oft durchlaufen wie diese Zahl angibt. Beispiel:

repeat hour 1:00 {
   repeat 0,1.5 now'hour {set WZ_Gong on}
};

Dieses Script lässt einen Gong zu jeder vollen Stunde schlagen. Die Anzahl der Schläge ist die Stundenzahl. Die äußere Schleife hat als Startzeipunkt hour. Das ist der Beginn der laufenden Stunde. Von dort an (der Zeitpunkt liegt in der Vergangenheit wird der Block repeat 0,1.5 now'hour {set WZ_Gong on} stündlich, zu jeder vollen Stunde, wiederholt. Dieser Block besteht aus einer Schleife die sofort startet und alle 1,5 Sekunden das Kommando zum Ertönen des Gongs gibt. Die aktuelle Stundenzahl liefert der Ausdruck now'hour (siehe hier). Diese ganze Zahl bestimmt, wie oft die Schleife durchlaufen wird - wie oft der Gong ertönt.

6.3.7.1.1 loop

Stand: 31.12.22 11:10

Das Kommando loop; startet sofort einen neuen Schleifendurchlauf. Dazu ein Beispiel:

repeat {
   wait 1:0;
   if ([WZ_Lampe] ~ /off/) {loop};
   log "WZ_Lampe ist an";
};

Auch hier wird stündlich protokolliert, ob WZ_Lampe eingeschaltet ist. Die Schleife wird nie beendet. Wenn das Device ausgeschaltet wird, wird wieder an den Anfang der Schleife, zum Wartebefehl, gesprungen.

Eine Variation diese Kommandos ist die Übergabe eines Zeitspanne. In diesen Fall wird die wiederholte Abarbeitung des Blocks erst nach der angegebenen Zeitspanne durchgeführt. Beispiel: Ein- bzw. Ausschalten einer Lampe nach 10 Minuten:

6.3.7.1.2 quit

Stand: 31.12.22 11:12

Das Kommando quit; dient zum Beenden einer Schleife. Im nachfolgenden Beispiel wird stündlich protokolliert ob WZ_Lampe eingeschaltet ist. Die Schleife wird beendet, wenn das Device ausgeschaltet wurde:

repeat {
   if ([WZ_Lampe] ~ /off/) {quit};
   log "WZ_Lampe ist an";
   wait 1:0;
};

6.3.7.2 always

Stand: 06.04.22 16:32

Der in der Praxis häufig vorkommende Fall, das die Endloswiederholung eines Kommandoblocks in einem seperaten Thread gestartet werden soll, wobei die Variablen v1 bis vn an den Thread übergeben werden, würde so aussehen:

thread v1 ... vn {
   repeat {
      command1;
      ...
      commandk;
   };
};

Dafür gibt es die abkürzende Schreibweise unter Verwendung des Schlüsselwortes always:

always v1 ... vn {
   command1;
   ...
   commandk;
};

7 Auslagerung und Import von Scripten

Um selbst definierte Klassen und Funktionen in mehreren Scripten verfügbar zum machen, kann man die entsprechenden Definitionen auslagern und mit dem Kommando import in jedem Script, an jeder beliebigen Stelle wieder verwenden.

Die Auslagerung kann in ein Device vom Typ lambda oder in eine externe Datei mit der Endung ".lbd" erfolgen.

Angenommen die eigenen systemweit benutzen Klassen und Funktionen stehen in der Datei "myLambda.lbd", dann können diese Definitionen in einem anderen Script mit dem Kommando:

import pfadangabe/mylambda.lbd;

zur Verfügung gestellt werden.

Es ist ebenfalls möglich, die eigenen Klassen und Funktionen in einem Device vom Typ lambda (z.B. mit dem Namen "myClasses") zu hinterlegen und mit dem Befehl:

import myClasses;

zu importieren.