Script mit Schleife und variablem Item

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

Antworten
Lizzel
Beiträge: 1
Registriert: 14. Jul 2022 21:31
Answers: 0

Script mit Schleife und variablem Item

Beitrag von Lizzel »

Hallo und guten Abend zusammen,

ich wage mich gerade vor und (versuche) meine ersten Scripts zu schreiben.

Ich habe ein kleines Bewässerungssystem mit sieben Sensoren und sieben Ventilen und Pumpe.

Bevor ich den Code jetzt unnötig in die Länge ziehe, wollte ich den Code mit einer Schleife abkürzen.

Ich hab meine Items alle durchnummeriert (Ventil_1, Ventil_2, ....).

Code: Alles auswählen

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var Ventil 
var Feuchtigskeitssensor
var i

i=0

for(i=0;i<=7;i++){
    
 i=i+1
 Ventil = 'Ventil_'& i
 Feuchtigskeitssensor = 'Feuchtigskeitssensor_' & i
  
 if ('Feuchtigkeitssensor' < '30') {
// Überprüfung, ob die Pumpe bereits läuft
  if (itemRegistry.getItem('Pumpe').getState() == 'ON') {
// Bewässerung an
    events.sendCommand('Pumpe', 'ON');
    events.sendCommand('Ventil', 'ON');
// Variabler Wert, der die Länge der Bewässerung bestimmt
    java.lang.Thread.sleep('Bewaesserungslaenge');
// Bewässerung aus
    events.sendCommand('Pumpe', 'OFF');
    events.sendCommand('Ventil', 'OFF');
  }
}

}
Mit VBA komme ich einigermaßen zurecht, aber bei JAVA tue ich mir noch etwas schwer ...

Schon mal vorab eine Dank für die Hilfe!

Gruß Lars

Benutzeravatar
udo1toni
Beiträge: 15249
Registriert: 11. Apr 2018 18:05
Answers: 242
Wohnort: Darmstadt

Re: Script mit Schleife und variablem Item

Beitrag von udo1toni »

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:

Code: Alles auswählen

i.name.split("_").get(1) == iRegner.toString
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

Antworten