Abfolge mehrerer Befehle in einer Rule

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

violine21
Beiträge: 600
Registriert: 20. Sep 2019 05:49
Answers: 7

Abfolge mehrerer Befehle in einer Rule

Beitrag von violine21 »

Hallo,
ich möchte eine Abfolge mehrerer Kommandos (Schrittkette), die in einer Bedingung zueinander stehen, in einem Programm realisieren.
Beispiel: Ein motorbetriebenes Dachfenster soll zu fahren, einen Moment verweilen und dann einen kleinen Spalt zur Lüftung wieder öffnen.

Ich habe dafür 3 Rules geschrieben und 2 virtuelle Switch-Items angelegt:

Code: Alles auswählen

var Timer tShelly_1_off = null
var Timer tShelly_2_off = null
var Timer tShelly_3_off = null

rule "Schritt 1"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    SRelais_B1_1.sendCommand(ON)
    tShelly_1_off = createTimer(now.plusSeconds(5),[ SRelais_B1_1.sendCommand(OFF)
    virtual_fenster_1.sendCommand(ON) ]
    )   
end

rule "Schritt 2"
when
    Item virtual_fenster_1 changed from OFF to ON
then
    tShelly_2_off = createTimer(now.plusSeconds(2),[ virtual_fenster_2.sendCommand(ON) ]
    )    
end

rule "Schritt 3"
when
    Item virtual_fenster_2 changed from OFF to ON
then
    SRelais_B1_2.sendCommand(ON)
    tShelly_3_off = createTimer(now.plusSeconds(5),[ SRelais_B1_2.sendCommand(OFF) 
    virtual_fenster_1.sendCommand(OFF)
    virtual_fenster_2.sendCommand(OFF) ]
    )    
end


Das funktioniert zwar, kommt mir aber irgendwie fragil vor :? .
Kann ich das nicht auch einfacher, evtl. in einer Rule lösen?

Vielen Dank für Eure Mithilfe!

Tokamak
Beiträge: 183
Registriert: 20. Aug 2019 08:37
Answers: 4
Wohnort: Aachen

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von Tokamak »

Da du die Timer offensichtlich niemals abbrichst, auch nicht, wenn TestKontakt1STATE von CLOSED zu OPEN wechselt, brauchst du sie dir nicht global zu merken.

Daher geht es relativ einfach:

Code: Alles auswählen

rule "..."
when
	Item TestKontakt1STATE changed from OPEN to CLOSED
then
	SRelais_B1_1.sendCommand(ON)
	createTimer(now.plusSeconds(5),[ SRelais_B1_1.sendCommand(OFF) ])
	createTimer(now.plusSeconds(7),[ SRelais_B1_2.sendCommand(ON) ])
	createTimer(now.plusSeconds(12),[ SRelais_B1_2.sendCommand(OFF) ])
end
Ich finde es eher ungewöhnlich, dass das ganze, wenn einmal gestartet, nicht mehr abgebrochen werden kann.
Proxmox mit OH 4.2 und HABApp 24 im LXC-Container

violine21
Beiträge: 600
Registriert: 20. Sep 2019 05:49
Answers: 7

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von violine21 »

Ah! Danke! Da habe ich wohl zu kompliziert gedacht.
Die rule macht genau das gleiche.

Eine Frage: Werden dort 3 Timer gestartet? Oder ist das einer, bei dem 3 Zeitpunkte abgefragt werden?

Ich finde es eher ungewöhnlich, dass das ganze, wenn einmal gestartet, nicht mehr abgebrochen werden kann.
Wie ich schon geschrieben habe, handelt es sich um den Schliessvorgang von elektr. Dachfenstern.
Da gibt es für mich nur zwei Szenarien:
1. trockenes, windstilles Wetter >>> komplett schliessen mit kurzer Bewegungsumkehr zwecks Spaltlüftung
2. Schlechtwetter >>> sofort komplett schliessen

Der Testkontakt ist der Versuchsaufbau bei mir auf dem Schreibtisch. Das wird später natürlich das Signal meiner Wetterstation.
Der Aktor ist ein Shelly 2.2.

Viele Grüße

Tokamak
Beiträge: 183
Registriert: 20. Aug 2019 08:37
Answers: 4
Wohnort: Aachen

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von Tokamak »

violine21 hat geschrieben: 2. Nov 2019 14:59 Eine Frage: Werden dort 3 Timer gestartet? Oder ist das einer, bei dem 3 Zeitpunkte abgefragt werden?
Drei createTimer(), also drei Timer, wie in deiner Lösung.

Man könnte mit zwei Thread::sleep() in der Lambda eines Timer arbeiten. Da hat aber den Nachteil, dass du einen Timer hast, der über 7 Sekunden läuft. Und da meines Wissens nur zwei Timer gleichzeitig ausgeführt werden können, wäre für diesen Zeitraum nur ein weiterer laufender Timer möglich.

Ein reschedule() im Timer-Lambda bietet sich auch nicht an, da verschiedene Aktionen angestoßen werden.

Letztlich sind die drei Timer meines Erachtens die beste Lösung. Es gibt keine Begrenzung bei der Anzahl von erzeugten Timern, zumindest keine, die eine praktische Rolle spielen würde.
Proxmox mit OH 4.2 und HABApp 24 im LXC-Container

violine21
Beiträge: 600
Registriert: 20. Sep 2019 05:49
Answers: 7

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von violine21 »

Vielen Dank für die Erklärungen!

Habe da mal noch eine Anfängerfrage:
Oft sehe ich in rule-Codes diesen Codeschnipsel

Code: Alles auswählen

createTimer(now.plusSeconds(5),[ | SRelais_B1_1.sendCommand(OFF) ])
Worin besteht der Unterschied mit bzw. ohne Pipe-Zeichen (|)?

Dankeschön!

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

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von udo1toni »

Das Pipe Zeichen ist in dem Fall optional und nur angegeben, um den Code vollständig anzugeben. Alles zwischen [ und ] ist ein Lambda. Das Lambda kann auch Variablen beinhalten, z.B. um eine Liste zu durchlaufen:

Code: Alles auswählen

gMyGroup.members.filter[i|i.state == ON].forEach[o|
    o.sendCommand(OFF)
    logInfo("mylogger","Item {} erhielt den Befehl OFF",o.name)
]
Die Liste aller Gruppenmember wird auf die Items gefiltert, welche ON sind, exakt diese Items erhalten im Anschluss einen OFF-Befehl, für jeden Befehl wird auch ein Log ausgegeben.

Ich persönlich halte nichts von marodierenden Timern. Was passiert zum Beispiel, wenn der Kontakt flattert? (Die Rule wird mehrfach getriggert, und für jeden Trigger werden mehrere Timer angelegt)
Sauber ist diese Variante:

Code: Alles auswählen

var Timer tShellies = null
var Number nShellies = 0

rule "Schrittsteuerung"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    if(tShellies !== null) return; // Timer läuft bereits -> Abbruch
    nShellies = 0
    tShellies = createTimer(now.plusMillis(10),[|
        nShellies = nShellies + 1
        switch (nShellies.intValue) {
            case 1 : {
                SRelais_B1_1.sendCommand(ON)
                tShellies.reschedule(now.plusSeconds(5))
            }
            case 2 : {
                SRelais_B1_1.sendCommand(OFF)
                tShellies.reschedule(now.plusSeconds(2))
            }
            case 3 : {
                SRelais_B1_2.sendCommand(ON)
                tShellies.reschedule(now.plusSeconds(5))
            }
            default : {
                SRelais_B1_2.sendCommand(OFF)
                tShellies = null
            }
        }
    ])
end
Die leichte Verzögerung zu Beginn (10 Millisekunden) könnte man natürlich einsparen, aber diese Form hat den Vorteil, dass alle Schritte im Timer stehen, es wird also vielleicht besser klar, was da passiert.

Diesen Ablauf kann man jederzeit unterbrechen, indem man an den Timer mittels tShellies.cancel abbricht. Man muss dann natürlich für geordnete Verhältnisse sorgen (z.B. tShellies wieder auf null setzen, die Shellies auf OFF setzen usw.) Da innerhalb der Rule der Timer geprüft wird, kann der Vorgang nicht aus Versehen (und auch nicht absichtlich!) durcheinander kommen.
openHAB4.3.3 stable in einem Debian-Container (bookworm) (Proxmox 8.3.5, LXC), mit openHABian eingerichtet

violine21
Beiträge: 600
Registriert: 20. Sep 2019 05:49
Answers: 7

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von violine21 »

Die rule sieht ja interessant aus!
Ich habe sie getestet. Leider läuft da was schief. Es schaltet kein Shelly.
Ich habe ein paar log-Infos eingefügt:

Code: Alles auswählen

var Timer tShellies = null
var Number nShellies = 0

rule "Schrittsteuerung"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    if(tShellies !== null) return; // Timer läuft bereits -> Abbruch
    nShellies = 0
    logInfo("SK","var_nShellies {}",nShellies)
    tShellies = createTimer(now.plusMillis(10),[|
        nShellies = nShellies + 1
        logInfo("SK","var_nShellies {}",nShellies)
        switch (nShellies) {
            case 1 : {
                logInfo("SK","Schritt 1")
                SRelais_B1_1.sendCommand(ON)
                tShellies.reschedule(now.plusSeconds(5))
            }
            case 2 : {
                logInfo("SK","Schritt 2")
                SRelais_B1_1.sendCommand(OFF)
                tShellies.reschedule(now.plusSeconds(2))
            }
            case 3 : {
                logInfo("SK","Schritt 3")
                SRelais_B1_2.sendCommand(ON)
                tShellies.reschedule(now.plusSeconds(5))
            }
            default : {
                logInfo("SK","Ende")
                SRelais_B1_2.sendCommand(OFF)
                tShellies = null
            }
        }
    ])
end
Als Ergebnis:

Code: Alles auswählen

==> /var/log/openhab2/openhab.log <==
2019-11-03 13:37:03.497 [INFO ] [rg.eclipse.smarthome.model.script.SK] - var_nShellies 0
2019-11-03 13:37:03.512 [INFO ] [rg.eclipse.smarthome.model.script.SK] - var_nShellies 1
2019-11-03 13:37:03.514 [INFO ] [rg.eclipse.smarthome.model.script.SK] - Ende

==> /var/log/openhab2/events.log <==
2019-11-03 13:37:03.518 [ome.event.ItemCommandEvent] - Item 'SRelais_B1_2' received command OFF
2019-11-03 13:37:03.521 [nt.ItemStatePredictedEvent] - SRelais_B1_2 predicted to become OFF
Die Variable wird zwar auf 1 erhöht aber scheinbar wird nur die default-Anweisung durchlaufen. Wenn ich den Shelly manuell auf ON schalte, wir der Shelly-Kanal ausgeschaltet.
Was ich nicht verstehe, wie wird nShellies weiter erhöht? Dazu müsste es ja eine Schleife durchlaufen, damit die case-Anweisung den entsprechenden Wert vorfindet?
marodierenden Timern
:D Das habe ich noch nie gehört und sofort in mein Repertoir aufgenommen :D

Vielen Dank!

violine21
Beiträge: 600
Registriert: 20. Sep 2019 05:49
Answers: 7

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von violine21 »

Ich habe noch etwas anderes getestet:

Code: Alles auswählen

rule "Test_Sleep"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    SRelais_B1_1.sendCommand(ON)
    Thread::sleep(10000)
    SRelais_B1_1.sendCommand(OFF)
    Thread::sleep(1000)
    SRelais_B1_2.sendCommand(ON)
    Thread::sleep(5000)
    SRelais_B1_2.sendCommand(OFF)
end
Ausser, das der Vorgang wenn er gestartet wurde, nicht zu stoppen ist, funktioniert das auch.
Oder sollte man von dem Kommando Thread::sleep die Finger lassen?

Vielen Dank und schönen Restsonntag!

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

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von udo1toni »

Nein, das sollte man keinesfalls tun. Außer, dass der Vorgang nicht gestoppt werden kann, blockiert ein Thread::sleep() einen thread, die Rule läuft mindestens 16 Sekunden, in der Zeit steht ein Thread weniger zur Verfügung. Wenn Du die Rule mehrfach triggern lässt, kannst Du damit das System komplett lahm legen, bis die Rules abgearbeitet sind.

Der Fehler in meiner Rule ist, dass ich aus Versehen

Code: Alles auswählen

switch (nShellies) {
statt

Code: Alles auswählen

switch (nShellies.intValue) {
geschrieben habe. :) Ich hab das oben korrigiert...
openHAB4.3.3 stable in einem Debian-Container (bookworm) (Proxmox 8.3.5, LXC), mit openHABian eingerichtet

Tokamak
Beiträge: 183
Registriert: 20. Aug 2019 08:37
Answers: 4
Wohnort: Aachen

Re: Abfolge mehrerer Befehle in einer Rule

Beitrag von Tokamak »

Die "marodierenden Timer" sind nach wie vor die beste Lösung, wenn es kein Szenario gibt, um die Timer abzubrechen. Das hatte ich eingangs auch erwähnt.

Sollen die Timer abbrechbar sein, ist reschedule() eine Möglichkeit, die auch im Design Pattern vorgeschlagen wird (a. https://community.openhab.org/t/design- ... mers/47699 oder https://community.openhab.org/t/why-hav ... idea/47695).

Allerdings halte ich die Lösung wie im Design Pattern nur dann für sinnvoll, wenn man immer wieder und endlos den gleichen Code ausführen will.

Als Lösung für die Eingangsfrage verstellt das reschedule() und der damit verbundene Aufwand zur Unterscheidung der Aktionen (switch()) den Blick auf das, was eigentlich ausgeführt werden soll.

Intuitiver finde ich etwa den Ansatz:

Code: Alles auswählen

import java.util.Collection
import java.util.concurrent.ConcurrentLinkedQueue

var Collection<Timer> shellyTimers=null

rule "Start"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    if (shellyTimers===null)
    	shellyTimers=new ConcurrentLinkedQueue()
    else
    	shellyTimers.forEach[ t | t.cancel() ]
    	
    SRelais_B1_1.sendCommand(ON)
    shellyTimers.add(createTimer(now().plusSeconds(5),  [ SRelais_B1_1.sendCommand(OFF) ]) )
    shellyTimers.add(createTimer(now().plusSeconds(7),  [ SRelais_B1_2.sendCommand(ON) ]) )
    shellyTimers.add(createTimer(now().plusSeconds(12), [ SRelais_B1_2.sendCommand(OFF); shellyTimers=null ]) )
end
oder, wenn die Timer nicht restartet werden sollen:

Code: Alles auswählen

import java.util.Collection
import java.util.Arrays
import java.util.LinkedList

var Collection<Timer> shellyTimers=null

rule "Start"
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    if (shellyTimers===null) {
	SRelais_B1_1.sendCommand(ON)

	shellyTimers=new LinkedList(Arrays::asList(
    	    createTimer(now().plusSeconds(5),  [ SRelais_B1_1.sendCommand(OFF) ]),
    	    createTimer(now().plusSeconds(7),  [ SRelais_B1_2.sendCommand(ON) ]),
    	    createTimer(now().plusSeconds(12), [ SRelais_B1_2.sendCommand(OFF); shellyTimers=null ])
	))
    }
end
Die Collecton shellyTimers hält dabei die eigentlichen Timer als Einheit zusammen. Über sie können wie auch zu Beginn der ersten Start-Rule die Timer abgebrochen werden:

Code: Alles auswählen

rule "Abbruch"
when
    ...
then
    if (shellyTimers!==null) {
    	shellyTimers.forEach[ t | t.cancel() ]
    	shellyTimers=null
    }
end
Mehr akademisch ist diese Lösung:

Code: Alles auswählen

var Timer shellyTimer=null

rule "..."
when
    Item TestKontakt1STATE changed from OPEN to CLOSED
then
    if (shellyTimer!==null) shellyTimer.cancel()
    SRelais_B1_1.sendCommand(ON)
    shellyTimer=createTimer(now().plusSeconds(5),[
        SRelais_B1_1.sendCommand(OFF)
        shellyTimer=createTimer(now().plusSeconds(2),[
            SRelais_B1_2.sendCommand(ON)
            shellyTimer=createTimer(now().plusSeconds(5),[
                SRelais_B1_2.sendCommand(OFF)
                shellyTimer=null
            ])
        ])
    ])
end    
Hier wird im Timer-Code der Timer immer wieder neu generiert.
Proxmox mit OH 4.2 und HABApp 24 im LXC-Container

Antworten