Also, das Wichtigste gleich zu Anfang: Wenn Du in openHAB Rules programmierst, dann ist das kein Java. Nie.
Du kannst über die UI Scripte erstellen und ECMA nutzen, was JavaScript entspricht (eine bestimmte Geschmacksrichtung...) ECMA ist an Bord, weil Blockly ECMA Code erzeugt.
Du kannst auch (sowohl über die UI als auch direkt über Textdateien) DSL Rules erstellen. DSL-> DomainSpecificLanguage. Die openHAB DSL ist ursprünglich in XTend programmiert worden, welches in Java geschrieben wurde. Deshalb sieht Code in der DSL oftmals sehr ähnlich aus wie in Java, aber es ist halt kein Java.
Das zweitwichtigste (oder vielleicht ist es auch noch wichtiger...): openHAB arbeitet eventbasiert. Auch wenn Schleifen in openHAB3 nicht mehr so problematisch sind, sollte man dennoch lieber mit dem System programmieren, statt gegen das System. openHAB bringt einen Scheduler mit. Du möchtest ein Ventil Öffnen und nach einer Zeit x schließen. Es ist also sinnvoller, das Ventil zu öffnen und dem Scheduler mitzuteilen, dass er zum Zeitpunkt x das Ventil schließen soll, als selbst mittels sleep() die Wartezeit innerhalb des Codes zu vertrödeln.
Die Frage ist, was die Regel exakt bewirken soll. Sollen einfach alle Ventile geöffnet werden? Oder ist es vielmehr so, dass ein Ventil geöffnet werden soll, nach Zeit x geschlossen werden und das nächste Ventil geöffent werden soll usw.? Und die Bewässerungszeit soll sich nach der zu Beginn gemessenen Bodenfeuchte richten?
openHAB kennt das Konzept der Gruppen. All Deine Ventile und all Deine Feuchtesensoren bilden jeweils eine Gruppe (wenn Du das so definierst). Die Gruppe hat einen Vorteil, Du kannst einfach auf die Member der Gruppe zugreifen, in der Form
Code: Alles auswählen
gVentile.members.filter[i|i.name.contains("1")].head.sendCommand(ON)
Da es einen Zusammenhang zwischen Ventil und Sensor gibt, ist es auch ein Leichtes, das passende Item innerhalb der Gruppe zu allozieren und dessen Status zu verwenden.
Du könntest über die Gruppe auch ein forEach[] verwenden, allerdings bist Du dann wieder in dem Dilemma, sleep() verwenden zu müssen, was einfach nicht schön ist.
Stattdessen wäre es sinnvoller, einen selbstaufrufenden Timer zu verwenden, der sich merkt, welches Ventil er gerade beackert. Z.B. so:
Code: Alles auswählen
var Timer tRegner = null
var Integer iRegner = 0
rule "Regner starten"
when
Item RegnerStart received command ON // Auslöser der Beregnung
then
tRegner?.cancel // Initialisieren ist immer eine gute Idee
iRegner = 0 // dito
tRegner = createTimer(now.plusSeconds(1), [| // definiere einen Timer
if(Pumpe.state != ON) // Falls Pumpe aus
Pumpe.sendCommand(ON) // einschalten
gVentile.members.filter[i| // filtere die Gruppe gVentile nach Items
i.name.endsWith(iRegner.toString) && // deren Name auf die Zahl in iRegner endet und
i.state == ON // deren Zustand ON ist
].head.sendCommand(OFF) // schalte das erste dieser Items aus (es sollte immer maximal eines an sein)
iRegner += 1 // erhöhe den Zähler
if(iRegner > gVentile.members.size) { // Falls der Zähler das letzte Element überschritten hat
Pumpe.sendCommand(OFF) // schalte die Pumpe aus
tRegner = null // und deinitialisiere den Timer
} else { // ansonsten
gVentile.members.filter[i| // filtere die Gruppe gVentile nach Items
i.name.endsWith(iRegner.toString) && // deren Name auf die Zahl in iRegner endet und
i.state != ON // deren Zustand OFF ist
].head.sendCommand(ON) // schalte das erste dieser Elemente ein
val Integer iFeuchte = // definiere eine lokale Integer Konstante
(gFeuchte.members.filter[i| // filtere die Gruppe gFeuchte nach Items
i.name.endsWith(iRegner.toString) // deren Name auf die Zahl in iRegner endet
].head.state as Number).intValue // hole vom ersten Item den Wert und setze die Konstante
val Integer iTime = 1+ ((100 - iFeuchte) * 0.2).intValue // Berechne irgendwas... ;)
tRegner.reschedule(now.plusMinutes(iTime)) // plane den Timer erneut in iTime Minuten
}
])
end
Ich habe den Code hier der besseren Verständlichkeit halber etwas umgebrochen.
Wenn das Item RegnerStart einen ON-Befehl empfängt, wird ein evtl. laufender Timer abgebrochen (tRegner?.cancel) und die Zähler-Variable initialisiert. Anschließend wird der Timer mit Ablauf in einer Sekunde angelegt. Zu diesem Zeitpunkt ist die Rule beendet!
Eine Sekunde später wird der Timer ausgeführt, dieser führt den Code aus, der beim Anlegen des Timers übergeben wurde.
Hier geschieht nun die eigentliche Arbeit.
Zunächst prüft die Rule, ob die Pumpe läuft. Ist dies nicht der Fall, schaltet sie die Pumpe ein.
Nun schaltet die Rule das gerade aktuelle Ventil aus. Falls kein entsprechende Ventil existiert (beim ersten Aufruf mit Nummer 0 im Namen und eingeschaltet...) wird auch kein Befehl gesendet.
Nun wird der Zähler um eins erhöht.
Anschließend prüft der Code, ob bereits das letzte Ventil erreicht ist (ich gehe von einer streng monoton wachsenden Zahl aus, also 1, 2, 3, ... , n-1, n)
Hat der Zähler die Anzahl Ventile überschritten, so wurde gerade das letzte Ventil geschlossen und die Pumpe kann abgeschaltet werden. Außerdem wird der Timer nicht mehr gebraucht und entsprechend kann die Timer Variable geleert werden.
Wurde das Ende noch nicht überschritten, so muss nun errechnet werden, wann das aktuelle Ventil geschlossen werden soll.. Dazu holt der Code den Messwert aus dem zugehörigen Item und führt seine Berechnung aus (hier beispielhaft zwischen 1 und 21 Minuten, könnte man auch sekundengenau machen und die Formel kann natürlich auch komplexer sein).
Abschließend plant der Code sich selbst zum errechneten Zeitpunkt erneut ein, der Code ist damit beendet.
In dieser Form kannst Du den Code nur schwer über die UI realisieren, weil die UI keine globalen Variablen erlaubt. (Ich hoffe sehr, dass diese Einschränkung aufgehoben wird oder es einen vernünftigen Ersatz dafür gibt).
Der Code kann auch leicht um eine allgemeine Abschaltfunktion erweitert werden, hier als separate Regel:
Code: Alles auswählen
rule "Regner stoppen"
when
Item RegnerStart received command OFF
then
tRegner?.cancel
gVentile.members.filter[j|j.state != OFF].forEach[i|i.sendCommand(OFF)]
if(Pumpe.state != OFF)
Pumpe.sendCommand(OFF)
end
HIer wird klar, wie elegant die Lösung über Gruppen ist. Die Gruppe wird nach Elementen gefiltert, die nicht OFF sind. Das Ergebnis des Filters ist eine Liste, die nun durchlaufen wird. Jedes Element bekommt das Kommando OFF gesendet. Anschließend wird noch die Pumpe abgeschaltet.
Esbraucht weder Imports noch kompliziertes Jonglieren mit der ItemRegistry. Sollen weitere Ventile zur Gruppe dazu, so müssen lediglich die entsprechenden Items mit in die Gruppe. Sollen es mehr als 9 Ventile sein, müsste man natürlich die Namenskennung etwas umgestalten, z.B. so:
hier wird der Namensteil nach dem ersten Unterstrich als String verglichen. Ist hier aber unnötig, weil es ja eh nur sieben Ventile sind.
openHAB4.3.3 stable in einem Debian-Container (bookworm) (Proxmox 8.3.5, LXC), mit openHABian eingerichtet