Ich versuche mich mal an einem Skelett:
Code: Alles auswählen
var Timer tRegner = null
var int iRegner = 0
rule "Beregnung"
when
Item Beregnung received command ON
then
logInfo("watering","Beregnung gestartet.")
if(tRegner !== null) {
logInfo("watering","Regner scheint noch zu laufen! Abbruch")
return;
}
iRegner = 0
Sperre.postUpdate(ON)
tRegner = createTimer(now.plusMillis(10),[|
iRegner++ // nächsten Regener anwählen
gRegner.members.filter[i|i.name.contains("Power")].filter[j| j.state != OFF].forEach[r|r.sendCommand(OFF)] // alle Regner abschalten
maxRegner = gRegner.members.filter[i|i.name.contains("Sperre")].filter[j| j.state == OFF].size // wieviele Regner sollen insgesamt beregnen?
if(iRegner > maxRegner) { // letzten Regner schon erreicht?
tRegner = null // Dann Schluss!
Sperre.postUpdate(OFF)
return;
}
Thread::sleep(500) // kleine(!) Pause einlegen
val strRegner = gRegner.members.filter[i|i.name.contains("Sperre")].filter[j| j.state == OFF].sortBy[name].get(iRegner-1).name.split("_").get(2) //Namen des Regner ermitteln
gRegner.members.filter[i|i.name.contains("Power") && i.name.contains(strRegner)].head.sendCommand(ON) // Regner einschalten
tRegner.reschedule(now.plusMinutes((gRegner.members.filter[i|i.name.contains("Dauer") && i.name.contains(strRegner)].head.state as Number).intValue)) // Nächste Ausführung planen
])
end
Die Rule ist natürlich noch nicht fertig
aber so zum ausprobieren...
Hinweis an dieser Stelle: Es ist egal, ob damit 2 oder 1000 Regner gesteuert werden, an der Rule ändert sich exakt nichts. (!)
Funktionsweise der Rule:
Wenn die Rule getriggert wird, prüft sie zunächst, ob der Timer bereits läuft. Ist das der Fall bricht sie sofort ab. (Das ist potenziell ein Schwachpunkt, aber man kann eine zweite Rule schreiben, die den Timer gezielt löscht, falls sich openHAB mal verschluckt haben sollte...)
Ist der Timer noch leer, wird die Zählvariable iRegner mit 0 initialisiert und der Timer erzeugt. Danach ist die Rule beendet(!)
Der Timer (hier passiert die eigentliche Arbeit) zündet nach 10 Millisekunden das erste Mal. Die 10 ms dienen nur der Sicherheit, dass der Timer schon komplett angelegt und initialisiert wurde.
Zunächst wird iRegner um eins erhöht, anschließend werden alle eingeschalteten Regner abgeschaltet.
Nun wird geprüft, wieviele Regner überhaupt beregnen sollen (die Menge [.size] der Items, deren Name das Wort "Sperre" enthält und deren Status OFF ist)
Falls die Menge der zu verwendenden Regner kleiner als der Zähler iRegner ist, hat der letzte Regner schon seine Arbeit getan,der Timer deinitialisiert sich (tRegner = null) und bricht ab (return;).
Andernfalls wartet die Rule einen kleinen Moment (500 Millisekunden; das ist für Thread::sleep() noch gut vertretbar) und berechnet anschließend den Namen des Regners, der nun an der Reihe ist. Dabei wird die Liste der Regner alphabetisch sortiert. Es wird nur der regnerspezifische Teil des Namens ermittelt. All das passiert in einer Zeile, die vielleicht auf den ersten Blick eher komplex ist... deshalb nehme ich die Zeile mal auseinander:
Code: Alles auswählen
gRegner // die Gruppe gRegner
.members // die Mitglieder der Gruppe, als Liste
.filter[i| // filtere die Liste und nutze dabei i als Platzhalter für jedes Item der Liste
i // prüfe für jedes Item i
.name // ob dessen Name
.contains("Sperre") // den Teilstring "Sperre" enthält
] // dies ist nun eine Liste aller Sperritems
.filter[j| // filtere das Ergebnis und nutze dabei j als Platzhalter für jedes Item der Liste
j // prüfe für jedes Item j
.state == OFF // ob dessen Status OFF ist
] // dies ist nun eine Liste aller Sperritems, die den Status OFF haben
.sortBy[name] // Sortiere die Liste alphabetisch
.get(iRegner-1) // nimm das n-te Element der Liste (0-indiziert, deshalb -1)
.name // nimm den Namen des Elements
.split("_") // teile den String am Zeichen _ auf und erzeuge eine Liste der Teilstrings (das Zeichen _ wird dabei entfernt)
.get(2) // nimm den 3. Teilstring (die Liste ist 0-indiziert)
Dieser Teilstring wird nun der Konstanten strRegner zugewiesen.
Nun kann zum einen der Regner mit diesem Namen eingeschaltet werden, zum anderen kann die hinterlegte Beregnungsdauer aus dem passenden Item geladen werden.
Die Items kommen alle in die Gruppe gRegner, alle Items folgen der Beschriftungsform Regner_xxx_nnn, wobei xxx jeweils pro nnn einmal Power, einmal Sperre und einmal Dauer enthält (Itemtypen passend als Switch bzw. bei Dauer als Number). nnn ist je Regner einmalig, es muss nicht zwingend eine Zahl sein, aber die Zeichenfolge muss für alle Items eines Regners identisch sein und für jeden Regner eindeutig, also z.B. so:
Code: Alles auswählen
Switch Sperre // nur für visibility...
Group gRegner "Alle Regneritems"
Switch Regner_Power_001 "Regner hinten [%s]" (gRegner) {... Verknüpfung auf das Magnetventil }
Switch Regner_Power_002 "Regner vorne [%s]" (gRegner) {... Verknüpfung auf das Magnetventil }
Switch Regner_Power_strasze "Regner Straße [%s]" (gRegner) {... Verknüpfung auf das Magnetventil }
Switch Regner_Sperre_001 "Regner hinten [MAP(enable.map):%s]" (gRegner) // ungebundenes Item
Switch Regner_Sperre_002 "Regner vorne [MAP(enable.map):%s]" (gRegner) // ungebundenes Item
Switch Regner_Sperre_strasze "Regner Straße [MAP(enable.map):%s]" (gRegner) // ungebundenes Item
Number Regner_Dauer_001 "Regner hinten [%d min]" <time> (gRegner) // ungebundenes Item
Number Regner_Dauer_002 "Regner vorne [%d min]" <time> (gRegner) // ungebundenes Item
Number Regner_Dauer_strasze "Regner Straße [%d min]" <time> (gRegner) // ungebundenes Item
Und das Mapping (transform/enable.map):
Wie oben erwähnt, ist die Anzahl der Regner nun vollkommen irrelevant, die Rule ermittelt alles dynamisch.
Mögliche Fallen: Ist der Beregnungszyklus gestartet, dürfen die Items nicht mehr verändert werden, also weder die Dauer noch die Sperre. Man könnte dafür ein Item anlegen, welches beim Start des Timers auf ON geschaltet wird und zum Ende auf OFF geschaltet wird. Dieses kann man dann in der Sitemap zum Ausblenden der Items nutzen, bzw. eine andere Darstellung einblenden, so z.B.:
Code: Alles auswählen
Frame label="Regner" {
Switch item=Regner_Sperre_001 visibility=[Sperre==OFF]
Text item=Regner_Sperre_001 visibility=[Sperre==ON]
Setpoint item=Regner_Dauer_001 visibility=[Sperre==OFF]
Text item=Regner_Dauer_001 visibility=[Sperre==ON]
Switch item=Regner_Sperre_002 visibility=[Sperre==OFF]
Text item=Regner_Sperre_002 visibility=[Sperre==ON]
Setpoint item=Regner_Dauer_002 visibility=[Sperre==OFF]
Text item=Regner_Dauer_002 visibility=[Sperre==ON]
}
Ein etwaiger Korrekturfaktor für die Beregnungsdauer könnte im Reschedule berrücksichtigt werden, wobei es sich dann natürlich anbietet, das vorher in einer Konstanten zu berechnen (dann selbstverständlich sekundengenau).
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.2, LXC), mit openHABian eingerichtet