logoFHEM CONTROL 2.0

Sprachbeschreibung

1 Einleitung

Stand: 11.05.19 15:36


Diese Seite eher zum Nachschlagen als zum Erlernen der Sprache gedacht. Es ist kaum möglich, etwas vollständig zu erklären ohne auf später Beschriebenes vorzugreifen. Das macht es für den Einsteiger, der noch nie programmiert hat, vielleicht etwas schwierig einige Textpassgen zu verstehen. Für erfahrene Programmierer trifft das sicher weniger zu.

Für den Anfänger ist es einfacher, mit simplen Scripts, wie sie hier 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 dieser Sprachbeschreibung leichter.

2 Einordnung in die Welt der Programmiersprachen

Stand: 11.06.19 15:05

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

3 Grundlegende Begriffe

3.1 Symbol

Stand: 22.07.19 18:39

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

Einige Symbole werden in λscript für die Definition von Funktionen verwendet. Das sind:

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

Das Zuweisen von Werten oder Klassen an diese Symbole verändert sie zu Variablen. Dann sind die damit definierten Funktionen in ihrer ursprünglichen Form nicht mehr aufrufbar. (Der gleiche Effekt tritt ebenfalls auf, wenn diese Symbole als Namen für ein λscript verwendet werden.) Das stellt kein prinzipielles Problem dar; der Aufruf der 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 auftauchen, 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.

3.2 Klasse

Stand: 18.06.19 11:03

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 neu Variable die mit dem Namen der Klasse identisch ist.

3.3 Werte

Stand: 18.06.19 07:05

λ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.

3.4 Variable

Stand: 20.06.19 08:08

Einem Symbol kann ein Wert zugewiesen werden. Dann wird aus diesem Symbol eine Variable. Die Zuweisung geschieht durch das Symbol :=. 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 Wert, wird der durch den neu Berechneten ersetzt.

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 die

3.5 Devices

Stand: 01.08.19 07:27

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. Ein weiteres Device ist die aktuelle Log-Datei. Dort kann mit dem Kommando log Text ausgegeben werden. De fakto ist der außerhalb von λscript laufende Timer ebenfalls ein Teil der Hardwareumgebung und wird bei wait-Kommandos verwendet. Während der Zugriff auf Variable in λscripts eingeschränkt sein kann, sind Devices systemweit verfügbar.

3.6 Kommando

Stand: 18.06.19 07:19

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.

3.7 Block

Stand: 18.06.19 07:20

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:

3.8 Programm

Stand: 01.08.19 07:29

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.

3.9 Zugriffsoperator

Stand: 18.06.19 09:08

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 Wert und einem dem folgenden Symbol bestehen. Das Apostroph als Zugriffsoperator wurde lediglich aus Gründen der kürzeren Schreibweise und der besseren Lesbarkeit eingeführt. λ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.

3.10 Kommentarzeile

Stand: 01.08.19 07:30

Skriptzeilen, die mit einem # (es können Leerzeichen vorangehen) beginnen, werden beim Einlesen des Scripts übergangen. Diese Zeilen können für eine Kommentierung benutzt werden.

4 Klassen

Stand: 15.05.19 19:57

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.

4.1 Vordefinierte Klassen

4.1.1 value
Stand: 18.06.19 07:37

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: 08.06.19 21:09

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 der Regeln der Bruchrechnung exakt und ohne Rundungsfehler gerechnet.

4.1.3 string
Stand: 18.06.19 08:59

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.4 word
Stand: 18.06.19 08:59

Die Klasse word ist eine von der Klasse string abgeleitete Klasse. Sie repräsentiert Zeichenketten die kein Leerzeichen (und keinen Tabulator oder Zeilenumbruch) enthält. Analog string werden sie in einem Script in Anführungszeichen eingeschlossen. So wird "Hallo" automatisch als eine Zeichenkette von Typ word erkannt.

4.1.5 measurement
Stand: 16.08.19 10:06

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.

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 führt zu einer Fehlermeldung.

4.1.6 boolean
Stand: 16.06.19 09:26

Von 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: 01.08.19 07:34

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: 18.06.19 09:20

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.) 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: 09.08.19 17:24

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 selbstverständlich 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.

4.1.10 collection
Stand: 12.06.19 10:55

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. Das Einspeichern von Werten geht 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: 16.06.19 09:29

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: 08.06.19 21:12

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.

Auf ein Reading kann nur lesend zugegriffen werden. Es gibt eine Ausnahme: Ein λscript kann seine eigenen Readings setzen, so wie jedes Device seinen eigenen 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. Die dem ersten Parameterpaar folgende Klammer symbolisiert, das beliebig viele Parameterpaare folgen können. Beispiel für das Setzen der Readings temperature und door:

setReading temperature 12 door "closed";

Der Wert eines Readings muss immer ein string sein. Das Kommando setReading wandelt alle Werte (hier die Zahl 12) automatische in eine Zeichenkette (string) um. Siehe auch Typumwandlungen

4.1.13 fhemDevice
Stand: 08.06.19 21:13

Ein Objekt von Typ fhemDevice bildet ein Device der FHEM-Installation ab. Objekte dieses Typs können innerhalb eines λscripts nicht instanziiert werden, da sie quasi die unveränderliche Hardwareumgebung abbilden. Beim Systemstart werden für alle gefundene Devices je eine Variable vom Typ fhemDevice 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.

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 die alternative Schreibweise:

l := [WZ_Lampe level];

Ist der Name des Readings state, reicht die Schreibweise:

s := [WZ_Lampe];

Danach steht in s der Wert des Readings state von WZ_Lampe als string.

4.1.14 sun
Stand: 15.05.19 19:57

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

mySun := new sun;

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.

4.2 Typumwandlungen

Stand: 01.08.19 07:40

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: 07.07.19 16:09

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: 14.06.19 10:53

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: 01.08.19 07:45

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 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 := "12 m";
log (s toNumber or 123);
123

Hier wird jetzt 123 zurückgegeben.

4.3 Definition neuer Klassen

Stand: 01.08.19 07:45

Eine neue Klasse kann mit folgendem Kommando definiert werden:

newClass := oldClass
   Eigenschaft1 Wert1
   Eigenschaft2 Wert2
    ...
;

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 folgt ein initialer Wert oder ein Klassenname. Ist z.B. Wert1 die Instanz eine bestimmten Klasse, ist damit auch, wie bei einer Variablen, der Typ der Eigenschaft festgelegt. Wert1 kann aber auch der Name einer Klasse sein. Dann muss Eigenschaft1 beim Instanziieren eines Repräsentanten der neuen Klasse einen entsprechenden Wert bekommen. Dazu 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.

newClass
   Eigenschaftx Wertx
   Eigenschafty Werty
    ...
;

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 auf die Zweite 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.

5 Variable

Stand: 28.05.19 10:24

Variable entstehen, indem man einem Symbol eine Wert zuweist. Jeder Wert ist von einer Klasse abgeleitet. Variablen sind statisch typisiert. Das heißt: Einer Variablen kann als neuer Wert nur ein Objekt der gleichen Klasse zugewiesen werden mit der sie definiert wurde. Die Kommandofolge:

zahl := 123;
...
zahl := "Hallo";

würde vom System mit einer Fehlermeldung quittiert.

5.1 Direkte Definition einer Variablen

Stand: 15.05.19 19:57

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;
neueZahl := zahl;
text := "Ergebnis: " zahl'toString;
wort := 5 * "Hi" ;
uhrzeit := now - today;
offen? := 3 > 5;

5.2 Definition durch Instanziierung von Klassen

Stand: 20.08.19 19:05

Variable können auch direkt von Klassen abgeleitet werden. Charakteristisch dafür ist die Verwendung des Schlüsselwortes new. Hier gibt es mehrere Möglichkeiten:

5.3 Definition durch Zuweisung eines Funktionsaufrufs

Stand: 01.08.19 07:51

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 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 die Festlegung eines Wartezustandes 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 := case (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 := case (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.4 Gültigkeitsbereich

Stand: 18.06.19 09:06

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.

6 Kommandos und Funktionen

Stand: 08.06.19 21:22

Er gibt keinen prinzipiellen Unterschied zwischen Funktionen die in λscript vordefiniert sind und selbst definierten Funktionen. Vordefinierte Funktionen können durch Eigene ersetzt oder sie können modifiziert 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.) 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: 08.06.19 21:23

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 ist die Signatur. 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, führt das zu einer Fehlermeldung. In diesem Fall aber, wird das Ergebnis 30 zurückgegeben.

6.2 Definition eigener Funktionen

Stand: 20.06.19 18:05

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 das nicht als ein Kommando interpretiert wird die Liste mit einem Apostroph ' versehen. 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. Daran schließt sich der Deklarationsblock mit dem Kommando an. Das Multiplikationszeichen könnte auch weggelassen werden.

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 im Deklarationsteil von Weiteren neu zu definierenden Funktionen auftauchen. 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) = 0
};

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) = 0
};

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

6.3 Vordefinierte Kommandos

6.3.1 Arithmetische Operationen
Stand: 09.06.19 13:49

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
6.3.2 Zweistellige Verknüpfungsoperationen
Stand: 01.08.19 08:00

Es gibt eine Vielzahl von Operationen bei denen zwei Operanden mit Hilfe eines dazwischen stehenden Symbols 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.

Symbol Op 1Op 2 Ergebnis Bemerkung
| boolean oder- Verknüpfung. Der Ergebnisausdruck ist genau dann true, wenn einer der beiden Ausdrücke true ist.
&boolean und - Verknüpfung. Der Ergebnisausdruck ist genau dann true, wenn beiden Ausdrücke true sind.
= value Test auf Gleichheit. Objekte sind gleich, wenn sie der selben Klasse angehören und in alle Merkmalen übereinstimmen. Gehören die Operanden unterschiedlichen Klassen an, beendet der Compiler das Programm mit einer Fehlermeldung.
!= value Test 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.
<=
<
>
=>
number moment string booleanVergleich von Objekten. (Siehe auch hier: Vergleichsoperationen) Strings werden lexikografisch verglichen. Ein Zeitpunkt ist kleiner als ein anderer wenn er vor Diesem liegt.
~ string regexpr
string
boolean Die Zeichenkette wird darauf getestet ob der Wert des regexpr oder string auf sie passt.
!~ string regexpr
string
boolean Die Zeichenkette wird darauf getestet ob der Wert des regexpr oder string nicht auf sie passt.
+ number string timespan Zahlen werden addiert und Zeichenketten zusammengefügt. Das Symbol + zwischen zwei Zeichenketten ist optional. Ebenso zwischen zwei Zeitspannen.
moment timespan moment Das Ergebnis ist der Zeitpunkt der gegenüber moment um timespan versetzt ist. Das Symbol + ist optional.
timespan moment moment
- number Ausführung der Subtraktion
moment timespan Berechnung der Zeitspanne zwischen zwei Zeitpunkten
moment timespan moment Berechnung eines Zeitpunktes der um den Wert von timespan vor moment liegt.
* number Multiplikation von Zahlen. Das Symbol * ist optional.
number string Vervielfachung eines Strings. number muss ganzzahlig und 0 oder größer sein.
/ number Division von Zahlen. Der Versuch einer Division durch 0 führt zu einem Programmabbruch.
^ number Potenzierung 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.3 Operationen mit Zeichenketten
Stand: 17.06.19 09:45

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" "!";

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.

Für die direkte Bearbeitung von Zeichenketten wurde das Konzept von Regex implementiert. Als charakteristisches Zeichen wurde hier die Tilde ~ definiert. Beispiel:

x ~ s/regexpression/parameter;

Dabei ist x eine zu modifizierende Zeichenkette. Über die Arbeitsweise von Regex gibt es eine Vielzahl von Dokumentationen. Deshalb wird sie an dieser Stelle nicht vertieft.

6.3.4 Logische Operationen
Stand: 17.06.19 08:48

Hier werden einerseits die Kommandos aufgeführt, die logische Werte als Ergebnis haben. Andererseits die Kommandos, die logische Werte als Ausgangspunkt benutzen.

6.3.4.1 Vergleichsoperationen
Stand: 01.08.19 08:03

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

Zwei Objekte sind gleich, wenn sie der selben Klasse entstammen und wenn alle Eigenschaften, die die Objekte ausmachen, paarweise gleich sind. Anderenfalls sind sie ungleich.

Ist über den Objekten einer Klasse die Operation <= definiert, dann sind auch die Operationen mit den Symbolen <, => und > möglich und müssen nicht explizit definiert werden. Die Operation mit dem Symbol <= heißt Ordnungsrelation. Bei einer selbst definierten Ordnungsrelation über einer Klasse, muss zwingend sicher gestellt werden, dass wenn x und y beliebige Objekte der Klasse sind, gelten muss:

Die Definition einer Ordnungsrelation <= ist nicht mit jedem Objekttyp sinnvoll möglich. Z.B. bei den boolschen Typen true und false. Das Gleiche trifft auch auf den Datentyp timespan zu. Es lässt sich nämlich z.B. nicht allgemein sagen, ob ein Monat 1/0/0:00 mehr oder weniger als 30 Tage 0/30/0:00 sind. 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 < >= > automatisch auch einige weitere Funktionen:

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 Typ cls sein.

6.3.4.2 Logische Operationen
Stand: 01.08.19 08:04

Logische Werte können mit den Symbolen ! & | verknüpft bzw. verändert 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

Zur Auswertung von logischen Ausdrücken, um zum Beispiel einen Objekt den eine oder den anderen Wert zuzuweisen, oder um den einen oder den anderen Programmzweig weiter fortzusetzen dienen Kommandos mit dem Symbol case. Dafür gibt es mehrere Varianten.

Variante 1:

case boolean
   { Block1 }
   { Block2 }
;
weitere Kommandos

Hier wird in Abhängigkeit davon, ob der dem Symbol case 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 Block 2 und ist der boolsche Wert false wird sofort mit den weiteren Kommandos fortgesetzt.

Variante 2:

case
   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 entsprechend Kommandoblock ausgeführt und danach mit den weiteren Kommandos fortgesetzt. Ist kein Wert true wird der letzte Block ausgeführt. Dieser Block ist optional. Fehlt er, und ist kein einziger boolscher Wert true, werden die weiteren Kommandos abgearbeitet.

Variante 3:

case wert
   wert1
      { Block 1 }
   ...
   wertk
      { Block k }
   { Block }
;
weitere Kommandos

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

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

Variante 1:

Objekt := case bool wert1 wert2;

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 von wert2 nicht optional ist. Diese Form ist ein Spezialfall der

Variante 2:

Objekt:= case bool-1 wert-1 ... bool-n wert-n 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:= case test
   test-1 wert-1
   ...
   test-n wert-n
   wert
;

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

6.3.4.3 Mustervergleich mit Strings
Stand: 17.06.19 08:55

Beim Test, ob Zeichenketten eine bestimmete Struktur oder einen bestimmten Inhalt haben wird die Regex-Syntax benutzt. Beispiel:

string := "Hallo Welt!";
a := string ~ /welt/;
a := string ~ /Welt/;

Der erste Vergleich setzt a auf den Wert false, der Zweite auf den Wert true. Für eine ausfühliche Dokumentation von Regex sei auch an dieser Stelle auf die einschlägige Literatur verwiesen.

6.3.5 Operationen mit moment und timespan
Stand: 12.08.19 07: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:00];
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 muss kleiner als eine Tageslänge 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, gibt es die beiden Kommandos isEarlier und isLater:

isVormittag := 12:00'today isEarlier;
isNachmittag := 12:00'today isLater;

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.6 Konvertierung von number nach timespan
Stand: 12.06.19 11:12

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.7 wait - Der Wartebefehl
Stand: 31.07.19 13:27

Das charakteristische Symbol für die Programmierung von Wartezuständen ist wait. Diesem Symbol folgen ein oder mehr Objekte. Der Rückgabewert eines wait-Kommandos ist eine Zahl. Es ist im allgemeinen die Nummer des Objektes der für die Beendigung des Wartezustandes verantwortlich war.

Ist ein Parameter des wait-Kommandos ein Objekt vom Typ moment, wird der Wartezustand beendet, wenn der angegebene Zeitpunkt erreicht wird. Liegt der Zeitpunkt beim Aufruf des wait-Kommandos bereits in der Vergangenheit wird diese Zeitangabe ignoriert. Ist kein weiterer Parameter vorhanden, wird das Programm sofort fortgesetzt und der Rückgabewert des wait-Kommandos ist 0.

Ist ein Parameter des wait-Kommandos ein Objekt vom Typ timespan, wird auf das Erreichen des sich aus der aktuellen Zeit und der Zeitspanne berechneten Zeitpunkts gewartet. Liegt dieser Zeitpunkt in der Vergangenheit, gilt das im vorhergehenden Absatz gesagte.

In allen anderen Fällen wird der Wartezustand beendet, wenn sich der Wert des Objektes ändert. Der Wert kann sich selbstverständlich nur ändern, wenn er sich entweder auf die Hardwareumgebung der Installation bezieht, oder wenn es sich um eine Variable handelt die in einem parallel laufenden Thread verändert wird. Dazu gibt es im Abschnitt "threads" einige Beispiel. Ein Beispiel mit dem Bezug auf ein fhemDevice:

w := wait [WZ_Thermostat] 1:00;
case (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. Außerdem wird der Wartezustand nach einer Stunde beendet. Ändert sich das Reading state des Devices WZ_Thermostat ist der Rückgabwert des wait-Kommandos 1 und es wird der erste Kommandoblock abgearbeitet. Ändert sich aber vor dem Ablauf der Stunde das Reading state nicht, ist der Rückgabewert 2, und es wird der zweite Komamndoblock abgearbeitet.

6.3.8 threads - Parallel ablaufende Scripte
Stand: 08.06.19 21:26

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.9 repeat, always - Wiederholung von Programmteilen
6.3.9.1 repeat
Stand: 20.06.19 15:19

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

repeat {
   command 1;
   ...
   command k;
};

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

repeat n {
   command 1;
   ...
   command k;
};

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 {
   case ([WZ_Lampe] ~ /off/) {quit};
   log "WZ_Lampe ist an";
   wait 1:0;
};

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

repeat {
   wait 1:0;
   case ([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:

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.9.2 always
Stand: 20.06.19 10:29

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 {
      command 1;
      ...
      command k;
   };
};

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

always v1 ... vn {
   command 1;
   ...
   command k;
};
6.3.10 forEach - Schleifen mit wechselnden Inhalten
6.3.10.1 Zählschleifen
Stand: 14.06.19 12:12

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.10.2 Durchlaufen eines Stacks
Stand: 12.06.19 08:09

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.11 Zufallsfunktionen
6.3.11.1 random - Zufallszahlen
Stand: 20.06.19 07:08

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.11.2 shuffle - Mischen eines Stacks
Stand: 16.08.19 12:09

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)

7 Auslagerung und Import von Scripten

Stand: 16.06.19 12:38

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 einlesen.

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

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

import pfadangabe/mylambda.lbd;

zur Verfügung gestellt werden.

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

import myClasses;

zu importieren.

8 Beispielprojekt Rollladensteuerung

8.1 Aufgabenstellung

Stand: 26.06.19 07:25

Nachfolgend soll ein λscript entwickelt werden, dass eine umfangreiche Steuerung der im Haus vorhandenen Rollläden ermöglicht. Zielstellung ist:

Das Script wird schrittweise entwickelt und kann leicht an die individuellen Anforderungen angepasst und erweitert werden.

8.2 Definition der notwendigen Klassen

Stand: 25.06.19 20:08

Die Steuerung der Rollläden soll raumbasiert erfolgen. In jedem Raum befinden ein oder mehrere Rollläden. Außerdem ist der Zustand des Raumes durch mehrere Parameter definiert. Diese sollen zunächst sein:

Dem entsprechend wird eine neue Klasse clsRoom definiert:

clsRoom := value
   name string
   indoorTemperature number
   heating boolean
   shadingTemperature 23
;

Die Eigenschaft shadingTemperature wird auf 23 °C vordefiniert. Die anderen Eigenschaften müssen bei der Erstellung von Objekten explizit angegeben werden. Als nächste Klasse wird eine Klasse clsShutter definiert. Sie wird von der Klasse fhemDevice abgeleitetet, und erweitert die in FHEM als Rollläden angelegten Devices um die notwendigen Eigenschaften. Diese sollen sein:

clsShutter := fhemDevice
   windowState word
   doorState word
   sunLeft 20
   sunRight 190
   shadingLevel 60
   upLevel 0
   downLevel 100
   autoLevel -1
   autoTime 1:00
;

Nachdem diese beiden grundlegenden Klassen angelegt wurden, werden die Definitionen so erweitert, das die Objekte, die sie repräsentieren sollen, miteinander verknüpft werden können. In die Definition von clsRoom wird ein stack mit den Rollos eingefügt. In die Definition von clsShutter wird eine Eigenschaft room hinzugefügt, die das Rollo in den Raum in dem er sich befindet bezeichnet.

clsRoom
   shutters (stack of clsShutter)
;
clsShutter
   room clsRoom
;

Danach werden Objekte definiert die die notwendigen Objekte für die Steuerung enthalten. Das sind:

myHouse := new stack of clsRoom;
mySun := new sun;

8.3 Definition der benötigten Objekte

Stand: 25.06.19 10:33

Als nächstes werden von den definierten Klassen die notwendigen Objekte abgeleitet und den realen Gegebenheiten spezifiziert. Zunächst werden die Räume angelegt. Als Beispiel soll hier ein Wohnzimmer definiert werden.

Wohnzimmer := new clsRoom
   name "Wohnzimmer"
   indoorTemperature '([WZ_Klima measured-temp] toNumber)
   heating '([WZ_Thermostat actuator] != "0")
   shutters (new stack of clsShutter)
;

An dieser Stelle ist von der Möglichkeit der funktionalen Programmierung in λscript gebrauch gemacht worden. (siehe auf hier.) Die Eigenschaft indoorTemperature ist ein Objekt von Typ number. (Die Raumtemperatur in °C) Dieser Variablen wird jetzt kein Wert zugewiesen, das macht auch keinen Sinn, da er sich permanent ändert, sondern ein Funktion mit dem Rückgabewert number - erkennbar am Apostroph vor der öffnenden Klammer. In diesem Fall wird das Reading measured-temp des Klima-Channels mit dem Namen "WZ_Klima" eines Thermostats von Typ "HM-CC-TC" ausgelesen und in eine Zahl umgewandelt. Obwohl '([WZ_Klima measured-temp] toNumber) keinen konkreten Wert hat, kann damit gerechnet, kann die Eigenschaft mit anderen Variablen verglichen werden, kann sie als Parameter in Funktionsaufrufen dienen. Sie ist von einer gewöhnlichen Variablen nicht zu unterscheiden. Der konkrete Aufruf der Funktion findet erst dann statt, wenn das Ergebniss für eine Kommunikation mit der Hardwareperipherie des Systems benötigt oder die Auflösung mit Hilfe des Symbols evaluate erzwungen wird. Die Eigenschaft heating ist analog über ein Funktional definiert. Die Funktion liefert true, wenn das Heizkörperventil geöffnet ist. Zur Festlegung der Eigenschaft shutters wird ein neuer, zunächst leerer Stack angelegt.

In analoger Weise werden die anderen Räume definiert. Z.B. die Küche:

Küche := new clsRoom
   name "Küche"
   indoorTemperature '(2 + ([Flur_Klima measured-temp] toNumber))
   heating false
   shutters (new stack of clsShutter)
;

In diesem Beispiel wurde davon ausgegangen, dass in der Küche kein eigenes Thermostat vorhanden ist. Aus Erfahrung ist bekannt, das es immer 2 K wärmer ist als im Flur - und durch das Kochen und Backen die Heizung nie an ist.

So werden nacheinander alle Räume, in denen die Rollläden gesteuert werden sollen, angelegt. Diese Räume werden dann in das Objekt myHouse (ein Stack vom Typ clsRoom) eingespeichert.

Wohnzimmer Küche ... > myHouse;

Jetzt folgt die Integration der Rollladen-Devices. Beispielhaft sollen im Wohnzimmer zwei Fenster-Rollläden (die Namen der entsprechenden Devices in der FHEM-Installation seien WZ_Rollo1 und WZ_Rollo2) vorhanden sein. In der Küche gibt es ein Tür-Rollo mit dem Namen K_Rollo.

Stand: 25.06.19 10:35

wird fortgesetzt ...