Timer funktioniert nicht wie gewünscht

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

Antworten
Benutzeravatar
Raptor
Beiträge: 84
Registriert: 26. Sep 2018 16:46
Answers: 0

Timer funktioniert nicht wie gewünscht

Beitrag von Raptor »

Hallo zusammen,

ich habe eine Rule gebaut, die die Stromzufuhr zu meinem Computer & allen anhängenden Geräten kappt, sobald dieser ausgeschaltet wird (mit einem Timer als Puffer zur Sicherheit). Das "Ausschalten" definiere ich anhand der abgezapften Leistung der Steckdose und setze über einen Schwellwert eine Variable auf ON und OFF. Das funktioniert alles meistens auch ganz gut. Manchmal kommt es aber zu gewissen Leistungsschwankungen nachdem der Computer ausgeschaltet wurde. Das bewirkt dann, dass der Timer zwischenzeitlich noch einmal abgebrochen wird. Das Problem ist jetzt, dass er dann beim nächsten Unterschreiten des Schwellwertes zwar wieder in die Regel geht, aber den Timer nicht mehr startet und daher die Stromzufuhr an bleibt.

Hier meine Regel:

Code: Alles auswählen

rule "Computer Statuswechsel Aktionen"
when
    Item Computer_Status changed
then
    if (Computer_Status.state == MODE_OFF) {
        // Starte Timer für Computer Ausschalten
        logInfo("COMPUTER", "Computer Schalter wird in 1 min ausgeschaltet!")
        if (computerShutdownTimer === null || computerShutdownTimer.hasTerminated) {
            logInfo("COMPUTER", "Timer wird gestartet!")
            computerShutdownTimer = createTimer(now.plusMinutes(1), [|
                if (Computer_Status.state == MODE_OFF) {
                    // Wenn Computer immer noch aus --> Strom trennen und Licht aus
                    logInfo("COMPUTER", "Computer Schalter wird jetzt ausgeschaltet!")
                    //Hue_Play_Computer_Schalter.sendCommand(OFF)
                    //Hue_Play_Computer_Dimmer.sendCommand(0)
                    Computer_Schalter.sendCommand(OFF)
                } else {
                    logInfo("COMPUTER", "Computer Schalter wird nicht ausgeschaltet!")
                }
                computerShutdownTimer = null
            ])
        }
    } else {
        if (computerShutdownTimer !== null) {
            logInfo("COMPUTER", "Ausschalt-Timer wird abgebrochen!")
            computerShutdownTimer.cancel()
        }
    }
end
Und der dazugehörige Log:

Code: Alles auswählen

2020-06-24 20:57:24.934 [vent.ItemStateChangedEvent] - Computer_Leistung_w changed from 116.9 to 37.9
2020-06-24 20:57:24.958 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Die Leistung ist zu niedrig, Status wird geändert zu: 0
2020-06-24 20:57:24.970 [vent.ItemStateChangedEvent] - Computer_Status changed from 1 to 0
2020-06-24 20:57:24.986 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Computer Schalter wird in 1 min ausgeschaltet!
2020-06-24 20:57:30.931 [vent.ItemStateChangedEvent] - Computer_Leistung_w changed from 37.9 to 40.2
2020-06-24 20:57:30.946 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Status wird geändert zu: 1
2020-06-24 20:57:30.961 [vent.ItemStateChangedEvent] - Computer_Status changed from 0 to 1
2020-06-24 20:57:30.976 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Ausschalt-Timer wird abgebrochen!
2020-06-24 20:57:37.931 [vent.ItemStateChangedEvent] - Computer_Leistung_w changed from 40.2 to 19.4
2020-06-24 20:57:37.965 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Die Leistung ist zu niedrig, Status wird geändert zu: 0
2020-06-24 20:57:37.990 [vent.ItemStateChangedEvent] - Computer_Status changed from 1 to 0
2020-06-24 20:57:37.995 [INFO ] [ipse.smarthome.model.script.COMPUTER] - Computer Schalter wird in 1 min ausgeschaltet!
Der Log legt nahe, dass nach dem Aufruf der Methode computerShutdownTimer.cancel der Timer bei der if-Abfrage computerShutdownTimer === null || computerShutdownTimer.hasTerminated eine negative Antwort erhält. Aber woran liegt das? Welchen Status hat der Timer, nachdem Cancel aufgerufen wurde? Gibt es eine Methode hasCanceled oder sowas? Oder muss ich den Timer nach dem Cancel auch noch auf null setzen?

Vielen Dank im Voraus!
openHABian PI mit RaZberry 2 Modul für Z-Wave

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

Re: Timer funktioniert nicht wie gewünscht

Beitrag von udo1toni »

Also, Du unterschlägst hier ein paar Dinge ;)
Zum einen hast Du vermutlich eine globale Konstante MODE_OFF definiert, welche dann 0 ist.
Dann ist noch die Frage, woher das Item Computer_Status seinen Status erhält (mutmaßlich eine andere Rule, welche Computer_Leistung_w auswertet...)

Viel wichtiger ist aber, warum der Timer nicht funktioniert. Das liegt daran, dass .hasTerminated nur dann wahr ist, wenn ein Timer seinen Code ausgeführt hat. Dieser Wert ist bei Dir also nie wahr (denn Du löschst zum Abschluss die Referenz auf den Timer, so wie sich das gehört).
Die Lösung für das Problem ist aber zum Glück sehr einfach. Lass die Überprüfung auf .hasTerminated weg und setze stattdessen computerShutdownTimer auf null, nachdem Du den Timer gecancelt hast. also so:

Code: Alles auswählen

val MODE_OFF = 0

rule "Computer Statuswechsel Aktionen"
when
    Item Computer_Status changed
then
    if (Computer_Status.state == MODE_OFF) {
        // Starte Timer für Computer Ausschalten
        logInfo("COMPUTER", "Computer Schalter wird in 1 min ausgeschaltet!")
        if (computerShutdownTimer === null) {
            logInfo("COMPUTER", "Timer wird gestartet!")
            computerShutdownTimer = createTimer(now.plusMinutes(1), [|
                if (Computer_Status.state == MODE_OFF) {
                    // Wenn Computer immer noch aus --> Strom trennen und Licht aus
                    logInfo("COMPUTER", "Computer Schalter wird jetzt ausgeschaltet!")
                    //Hue_Play_Computer_Schalter.sendCommand(OFF)
                    //Hue_Play_Computer_Dimmer.sendCommand(0)
                    Computer_Schalter.sendCommand(OFF)
                } else {
                   logInfo("COMPUTER", "Computer Schalter wird nicht ausgeschaltet!")
                }
                computerShutdownTimer = null
            ])
        }
    } else {
        if (computerShutdownTimer !== null) {
            logInfo("COMPUTER", "Ausschalt-Timer wird abgebrochen!")
            computerShutdownTimer.cancel()
            computerShutdownTimer = null
        }
    }
end
Allerdings ist in dem Code ohnehin ein bisschen was doppelt gemoppelt. Im Timer kann Computer_Status nur den Status MODE_OFF haben, da der Timer ansonsten abgebrochen wurde (Ausnahme: Du schaffst es, den Computer exakt in dem Moment einzuschalten, in dem der Timer los läuft. Allerdings wird das Item Computer_Status dann auch noch nicht auf MODE_ON gewechselt haben. Deswegen ist der Code kürzer und einfacher so:

Code: Alles auswählen

rule "Computer Statuswechsel Aktionen"
when
    Item Computer_Status changed
then
    computerShutdownTimer?.cancel                                              // falls ein Timer läuft, abbrechen
    if (Computer_Status.state == 0) {                                          // Falls Computer aus, Timer starten
        logInfo("computer", "Computer Schalter wird in 1 min ausgeschaltet!")
        computerShutdownTimer = createTimer(now.plusMinutes(1), [|             // Starte Timer für Computer Ausschalten
            logInfo("computer", "Computer Schalter wird jetzt ausgeschaltet!")
            //Hue_Play_Computer_Schalter.sendCommand(OFF)
            //Hue_Play_Computer_Dimmer.sendCommand(0)
            Computer_Schalter.sendCommand(OFF)
        ])
    } else {                                                                   // ansonsten nur Meldung
        logInfo("computer", "Ausschalt-Timer wurde abgebrochen!")
    }
end
Die Rule triggert bei changed. Es gibt also nur drei Möglichkeiten:
1. Computer_Status war NULL (oder UNDEV)
2. Computer_Status war 0
3. Computer_Status war 1
In jedem der Fälle ist es günstig, für geordnete Verhältnisse zu sorgen und einen eventuell laufenden Timer zu beseitigen. Entsprechend passiert das als erstes. (wenn der Status von 1. oder 3. kommt, darf kein Timer laufen, ein laufender Timer wäre also fehlerhaft. Wenn der Statud von 2. kommt, ist er jetzt nicht mehr 0 (wegen changed) und muss ebenfalls abgebrochen werden.)
Der neue Status kann eigentlich nur 1 oder 0 sein. Also wird entweder ein Timer angelegt, oder nicht. Da wir gleich zu Beginn einen eventuell laufenden Timer gelöscht haben, müssen wir nichts mehr prüfen :) sondern können einfach den Timer anlegen.
Wenn der Timercode ausgeführt wird, kann Computer_Status seinen Status nicht geändert haben :) also keine Prüfung nötig...
openHAB4.3.3 stable in einem Debian-Container (bookworm) (Proxmox 8.3.5, LXC), mit openHABian eingerichtet

Benutzeravatar
Raptor
Beiträge: 84
Registriert: 26. Sep 2018 16:46
Answers: 0

Re: Timer funktioniert nicht wie gewünscht

Beitrag von Raptor »

Hey,

danke für die schnelle und ausführliche Antwort! Nicht nur den Fehler gefunden, sondern auch gleich noch den Code optimiert, wow :)

Ein erster Testlauf hat funktioniert!

Du hast Recht, ich hatte natürlich die zweite Rule unterschlagen, die den Status wechselt aufgrund der Leistung. Der Vollständigkeit halber hier noch einmal der gesamte Code, falls das jemand nachbauen will:

Code: Alles auswählen

// Computer Status Definition
val Number MODE_OFF = 0
val Number MODE_ON = 1

val Number THRESHOLD_ON = 40
val Number THRESHOLD_ERROR = 0

var Timer computerShutdownTimer = null

// ===================================================
// Änderung des Computer Status
//
rule "Aenderung des Computer Status"
when
    Item Computer_Leistung_w changed
then
    switch (Computer_Status.state) {
        case MODE_OFF: {
            if (Computer_Leistung_w.state as Number > THRESHOLD_ON) {
                logInfo("COMPUTER", "Status wird geändert zu: " + MODE_ON)
                Computer_Status.postUpdate(MODE_ON)
            }
        }
        case MODE_ON: {
            if (THRESHOLD_ON > Computer_Leistung_w.state as Number && Computer_Leistung_w.state as Number >= THRESHOLD_ERROR) {
                logInfo("COMPUTER", "Die Leistung ist zu niedrig, Status wird geändert zu: " + MODE_OFF)
                    Computer_Status.postUpdate(MODE_OFF)
            }
        }
        default: {
            if (Computer_Leistung_w.state as Number > THRESHOLD_ON) {
                logInfo("COMPUTER", "Der Computer Status war unbekannt und wird geändert zu: " + MODE_ON)
                Computer_Status.postUpdate(MODE_ON)
            } else {
                logInfo("COMPUTER", "Der Computer Status war unbekannt und wird geändert zu: " + MODE_OFF)
                Computer_Status.postUpdate(MODE_OFF)
            }
        }
    }
end

// ===================================================
// Aktionen bei Statuswechsel
//
rule "Computer Statuswechsel Aktionen"
when
    Item Computer_Status changed
then
    computerShutdownTimer?.cancel                                               // Falls ein Timer läuft, abbrechen
    if (Computer_Status.state == MODE_OFF) {                                    // Falls Computer aus, Timer starten
        logInfo("COMPUTER", "Computer Schalter wird in 1 min ausgeschaltet!")
        computerShutdownTimer = createTimer(now.plusMinutes(1), [|              // Starte Timer für Computer Ausschalten
            logInfo("COMPUTER", "Computer Schalter wird jetzt ausgeschaltet!")
            Hue_Play_Computer_Schalter.sendCommand(OFF)
            Hue_Play_Computer_Dimmer.sendCommand(0)
            Computer_Schalter.sendCommand(OFF)
        ])
    } else {                                                                    // ansonsten nur Meldung
        logInfo("COMPUTER", "Ausschalt-Timer wurde abgebrochen!")
    }
end
Den zusätzlichen Check innerhalb des Timers hatte ich tatsächlich zur Sicherheit drin, falls der Status sich genau in dem Moment noch mal ändert, in dem der Timer aufgelöst wird. Aber das ist schon sehr unwahrscheinlich, habe den doppelten IF entfernt.
Und deine Logik ist natürlich genial, wenn die Rule ausgeführt wird brauch ich ja nur auf den OFF-Status checken, alles andere erübrigt sich ja und muss in dem Fall einfach nur den Timer abbrechen. Das spart Code! :)

Die Syntax computerShutdownTimer?.cancel war mir noch gar nicht bekannt, ich nehme an da wird geprüft ob die Variable != NULL ist und falls ja, wird die entsprechende Methode (hier cancel) ausgeführt?
openHABian PI mit RaZberry 2 Modul für Z-Wave

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

Re: Timer funktioniert nicht wie gewünscht

Beitrag von udo1toni »

Genau. Das ? bewirkt, dass der nachfolgende Code nur ausgeführt wird, falls der Zeiger nicht auf null zeigt. (null ist übrigens etwas völlig anderes als NULL, also bitte aufpassen ;) )
openHAB4.3.3 stable in einem Debian-Container (bookworm) (Proxmox 8.3.5, LXC), mit openHABian eingerichtet

Antworten