rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

Antworten
TomW80
Beiträge: 125
Registriert: 7. Mai 2021 19:11
Answers: 0

rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von TomW80 »

Hallo,

ich habe folgende Rule die jahrelang ohne Probleme funktioniert hat, aber jetzt irgendwie nicht mehr.

Code: Alles auswählen

var String logPrefix = 'Fensterüberwachung: '
var boolean log = true   
var boolean useBot = false
var String meldeText = ''  
var Number fensterOffenWarnungKueche = 60  
var Number fensterOffenWarnungKG = 60 
var Number lastNotification

// Die ursprüngliche Rule ist von hier -> https://www.forwardme.de/2019/10/03/offene-fenster-nie-wieder-vergessen-dank-openhab/
rule "Fenster Benachrichtigung"
when
     Time cron "0 0/1 * * * ?" // Alle 1 Minute
then
    // Check ob Variablen passen, ansonsten defaultwert setzen
    if ((NotificationFenster.state != OFF) && (NotificationFenster.state != ON)) {
        NotificationFenster.sendCommand(ON)
        logInfo('rules', logPrefix + 'Setze Default-Wert für NotificationFenster=An')
    }
    if (fensterOffenWarnungMinuten.state == null) {
        fensterOffenWarnungMinuten.postUpdate(30)
        logInfo('rules', logPrefix + 'Setze Default-Wert für fensterOffenWarnungMinuten=30')
    }
    if (lastNotification == null) {
        lastNotification = now.toInstant().toEpochMilli();
        logInfo('rules', logPrefix + 'Setze Wert für lastNotification auf die aktuelle Zeit ' + lastNotification )
    }
    if ((now.getMonthValue() >= 11 || now.getMonthValue() <= 3) && fensterOffenWarnungMinuten.state != 10) // zwischen November und März die Zeit auf 10 Minuten ändern.
    {
        fensterOffenWarnungMinuten.postUpdate(10)
        logInfo('rules', logPrefix + 'Setze Wert für fensterOffenWarnungMinuten=10 (Winter)')
    }
    else if ((now.getMonthValue() >= 4 && now.getMonthValue() <= 10) && fensterOffenWarnungMinuten.state != 30) // zwischen April und Oktober die Zeit auf 30 Minuten ändern.
    {
        fensterOffenWarnungMinuten.postUpdate(30)
        logInfo('rules', logPrefix + 'Setze Wert für fensterOffenWarnungMinuten=30')
    }

    useBot = false
    // Ist Benachrichtigung über offene Fenster aktiviert?
    if (NotificationFenster.state == ON) {
        var Number n1 = gKontakte.members.filter[i|i.state == OPEN].size + gKontakte.members.filter[i|i.state == ON].size

        if (n1 > 0) 
        {
            meldeText = ''   
            // zuerst schauen wir, ob Fenster noch in die Liste rein müssen, wenn noch nicht enthalten
            gKontakte.members.filter[ i | i.state.toString() == 'OPEN' || i.state.toString() == 'ON'].forEach[i|
                var lastUpdate = i.lastUpdate("mapdb").toInstant();
                // Ermitteln, wie lange in Sekunden das Fenster nun offen ist
                var long offenInMinuten = (now.toInstant().toEpochMilli() - lastUpdate.toEpochMilli())/(1000*60);
                if (log) logInfo('rules', 'letztes Update von ' + i.name + ' (Status: ' + i.state.toString() + '): ' + lastUpdate + ' - offen seit Minuten: ' + offenInMinuten.toString())
                
                if ((offenInMinuten >= (fensterOffenWarnungKueche)) && (i.name == "EG_Kueche_Fenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }
                else if ((offenInMinuten >= (fensterOffenWarnungKG)) && (i.name == "KG_Kellerfenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }
                else if ((offenInMinuten >= (fensterOffenWarnungMinuten.state as DecimalType)) && (i.name != "EG_Kueche_Fenster" )&& (i.name != "KG_Kellerfenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }

                if ((offenInMinuten == (fensterOffenWarnungKueche)) && (i.name == "EG_Kueche_Fenster" )) {
                    // Nun per Pushover warnen
                    useBot = true                
                }
                else if ((offenInMinuten == (fensterOffenWarnungKG)) && (i.name == "KG_Kellerfenster" )) {
                    // Nun per Pushover warnen
                    useBot = true                
                }

                else if ((offenInMinuten == (fensterOffenWarnungMinuten.state as DecimalType)) && (i.name != "EG_Kueche_Fenster" )&& (i.name != "KG_Kellerfenster" )) {
                    // Nun per Pushover warnen
                  if (now.getHour() >= 22 || now.getHour() <= 7) // keine Nachricht zwischen 22 Uhr und 07 Uhr versenden
                  {
                    useBot = false
                    logInfo('rules', logPrefix + 'useBot=false')
                  }
                  else
                  {                  
                    useBot = true
                    logInfo('rules', logPrefix + 'useBot=true')
                  }
                }
            ]
            if (meldeText != '')
            {
                // Meldetext erzeugen
                meldeText = 'Fenster/Türen offen: ' + meldeText.replaceAll('KG_','Kellergeschoß_').replaceAll('EG_','Erdgeschoß ').replaceAll('OG_','Obergeschoß ').replaceAll('r_S','r_Süden').replaceAll('r_O','r_Osten').replaceAll('r_W','r_Westen').replaceAll('r_N','r_Norden').replaceAll('_',' ').replaceAll('ue','ü').replaceAll('ae','ä').replaceAll('Fenster','').replaceAll('Kind2','T****') + ' bitte schließen!'

                if (log) logInfo('rules', logPrefix + 'Meldung über offene Fenster wird erfolgen...')

                if (meldeText.length() > 250) 
                {
                    meldeText = meldeText.substring(0,250)
                    if (log) logInfo('rules', logPrefix + 'Meldetext gekürzt...')
                }

                if (useBot) 
                {
                   if (((now.toInstant().toEpochMilli() - lastNotification)/(1000*60)) > 10)
                   {
                                        
                        val actions = getActions("pushover", "pushover:pushover-account:*********")
                        actions.sendMessage("Information", meldeText)

                        lastNotification = now.toInstant().toEpochMilli();

                        logInfo('rules', logPrefix + 'Meldung über offene Fenster wird gesendet...')
                        if (log) logInfo('rules', logPrefix + meldeText)
                   }
                     else
                   {
                        logInfo('rules', logPrefix + 'Meldung über offene Fenster wird NICHT gesendet, letzte Meldung noch keine 10 Minuten her.')
                        if (log) logInfo('rules', logPrefix + meldeText)
                   }

                }
                else
                {
                    if (log) logInfo('rules', logPrefix + 'Meldung über offene Fenster wird NICHT gesendet...')
                    if (log) logInfo('rules', logPrefix + meldeText)
                }
            }

        }
        else 
        {
            if (log) logInfo('rules', logPrefix + 'Es sind keine Fenster/Türen offen!')
        }
    }
end 
Das Problem ist der lastUpdate-Status vom geöffneten Fenster.
Gerade habe ich ein Fenster geöffnet und als Status folgendes bekommen:
letztes Update von OG_Ankleide_Fenster (Status: OPEN): 2025-10-29T12:19:44Z - offen seit Minuten: 2672
Ich habe es aber gerade erst geöffnet. Warum wird der Status nicht aktualisiert?
Hier müsste ja die aktuelle Zeit stehen, also offen seit Minuten: 0.

Meine mapdb.persist sieht so aus:

Code: Alles auswählen

Strategies {
   default = everyChange
}
 
Items {
   *: strategy = everyChange, restoreOnStartup

  NotificationFenster : strategy =  everyChange, restoreOnStartup
  kontakteoffen : strategy =  everyChange, restoreOnStartup
  ...
}
Ich kann nicht genau sagen seit wann das nicht mehr funktioniert und ob das mit einem Update von OH zu tun hat.
Bin aktuell auf OH 5.0.2
Wo liegt jetzt mein Problem?

P.S. Wenn ich die log Settings von mapdb auf Debug stelle, dann passt lastUpdate jedesmal.

Benutzeravatar
peter-pan
Beiträge: 2814
Registriert: 28. Nov 2018 12:03
Answers: 30
Wohnort: Schwäbisch Gmünd

Re: rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von peter-pan »

Ich hab gesehen, dass du des öfteren ein Semikolon in deiner Rule benutzt. Das ist eigentlich in einer DSL-Rule nicht er normale Weg, ausser bei dem Befehl "return".
Ansonsten müsstest du mal nach den Ergebnissen deiner Berechnungen schauen, also ggf. Logs nach den Berechnungen ausgeben. Da ich nicht deine Umgebung habe, kann ich das leder nicht selbst testen.
Pi5/8GB(PiOS Lite 64-bit(trixie)/SSD 120GB - OH5.0.2 openhabian

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

Re: rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von udo1toni »

Meine erste Vermutung (ungeprüft) wäre, dass lastUpdate als Variablenname unglücklich gewählt ist (auch wenn das bisher funktioniert hat - es ist ein Schlüsselwort, insbesondere in dieser Schreibweise).

Meine Empfehlung wäre, den Code umzuschreiben (so ziemlich komplett...), weil doch so einiges wesentlich einfacher und/oder eleganter zu erledigen wäre. Beispiel:
Statt

Code: Alles auswählen

var long offenInMinuten = (now.toInstant().toEpochMilli() - lastUpdate.toEpochMilli())/(1000*60)
wäre

Code: Alles auswählen

var Integer iOpenDuration = Duration.between(i.lastUpdate,now).toMinutes
eine "hübschere" Variante. Über den Variablennamen kann man natürlich diskutieren :)
Der Punkt ist, dass Duration.between(timestamp1,timestamp2).toMinutes die Differenz in Minuten liefert (wahlweise auch in anderen Einheiten, Sekunden, Tage... egal) und das ganz ohne Kopfzerbrechen.

Du fragst mehrfach bestimmte Werte in Abhängigkeit des Itemnamens ab, startest aber immer mit offenInMinuten. Das führt dazu, dass Du alles zigmal hinschreiben musst. Ich gehe davon aus, dass der Code "gewachsen" ist.
Hier:

Code: Alles auswählen

            gKontakte.members.filter[ i | i.state.toString() == 'OPEN' || i.state.toString() == 'ON'].forEach[i|
                var lastUpdate = i.lastUpdate("mapdb").toInstant();
                // Ermitteln, wie lange in Sekunden das Fenster nun offen ist
                var long offenInMinuten = (now.toInstant().toEpochMilli() - lastUpdate.toEpochMilli())/(1000*60);
                if (log) logInfo('rules', 'letztes Update von ' + i.name + ' (Status: ' + i.state.toString() + '): ' + lastUpdate + ' - offen seit Minuten: ' + offenInMinuten.toString())
                
                if ((offenInMinuten >= (fensterOffenWarnungKueche)) && (i.name == "EG_Kueche_Fenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }
                else if ((offenInMinuten >= (fensterOffenWarnungKG)) && (i.name == "KG_Kellerfenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }
                else if ((offenInMinuten >= (fensterOffenWarnungMinuten.state as DecimalType)) && (i.name != "EG_Kueche_Fenster" )&& (i.name != "KG_Kellerfenster" )) {
                    meldeText+=i.name.toString() + ' seit ' + offenInMinuten + ' Minuten, '
                }

                if ((offenInMinuten == (fensterOffenWarnungKueche)) && (i.name == "EG_Kueche_Fenster" )) {
                    // Nun per Pushover warnen
                    useBot = true                
                }
                else if ((offenInMinuten == (fensterOffenWarnungKG)) && (i.name == "KG_Kellerfenster" )) {
                    // Nun per Pushover warnen
                    useBot = true                
                }

                else if ((offenInMinuten == (fensterOffenWarnungMinuten.state as DecimalType)) && (i.name != "EG_Kueche_Fenster" )&& (i.name != "KG_Kellerfenster" )) {
                    // Nun per Pushover warnen
                  if (now.getHour() >= 22 || now.getHour() <= 7) // keine Nachricht zwischen 22 Uhr und 07 Uhr versenden
                  {
                    useBot = false
                    logInfo('rules', logPrefix + 'useBot=false')
                  }
                  else
                  {                  
                    useBot = true
                    logInfo('rules', logPrefix + 'useBot=true')
                  }
                }
            ]
Einfacher wird es, wenn Du die Abfrage nur leicht abwandelst, nämlich den Itemnamen zuerst als Kriterium verwendest:

Code: Alles auswählen

            gKontakte.members.filter[ i | i.state.toString() == 'OPEN' || i.state.toString() == 'ON'].forEach[i|
                var lastUpdate = i.lastUpdate("mapdb").toInstant();
                // Ermitteln, wie lange in Sekunden das Fenster nun offen ist
                var long offenInMinuten = (now.toInstant().toEpochMilli() - lastUpdate.toEpochMilli())/(1000*60);
                logDebug('window', 'letztes Update von {} (Status: {}): {} - offen seit {} Minuten.', i.name, i.state, lastUpdate, offenInMinuten)

                if(i.name == "EG_Kueche_Fenster") (
                    if(offenInMinuten >= fensterOffenWarnungKueche)
                        meldeText+=i.name + ' seit ' + offenInMinuten + ' Minuten, '
                    if(offenInMinuten == fensterOffenWarnungKueche)
                        useBot = true // Nun per Pushover warnen
                } else if(i.name == "KG_Kellerfenster") {
                    if(offenInMinuten >= fensterOffenWarnungKG)
                        meldeText+=i.name + ' seit ' + offenInMinuten + ' Minuten, '
                    if(offenInMinuten == fensterOffenWarnungKG)
                        useBot = true     // Nun per Pushover warnen
                } else {
                    if(offenInMinuten >= (fensterOffenWarnungMinuten.state as Number))
                        meldeText+=i.name + ' seit ' + offenInMinuten + ' Minuten, '
                    if(offenInMinuten == (fensterOffenWarnungMinuten.state as Number)) {
                        useBot = if(now.getHour() >= 22 || now.getHour() <= 7) false else true
                        logInfo('window', 'useBot = {}',useBot)
                    }
                }
            ]
Im letzten Block habe ich den ternären Operator a = if(x) b else c verwendet, der hier vier Zeilen zu einer schrumpfen lässt. Da die logAusgabe bis auf den Zustand der soeben gesetzten Variable identisch ist, bietet es sich an, diese einfach mit dem echten Wert vorzunehmen und so eine weitere Zeile einzusparen :)
Man kann den Code auch noch weiter vereinfachen :) wobei ohnehin einige Dinge besser mit einer weiteren Rule zu erledigen wären, statt dies monolitisch zu bauen. Die ganze Prüfung, ob ein Item einen gültigen Wert enthält, kann man prima in einer Rule erledigen, welche mit system started getriggert wird.
Auch die Rule selbst muss nicht unbedingt einmal pro Minute aufgerufen werden. Stattdessen könnte man prima mit einem Timer arbeiten, so dass der Code nur ausgeführt wird, wenn dies auch nötig ist.

.name gibt übrigens immer einen String zurück. Entsprechend kann .toString() hier entfallen. An anderer Stelle verwendest Du aber einfach eine numerische Variable und verwendest sie innerhalb eines Strings, dort wäre .toString() sinnvoll.

Noch ein paar Worte zu logInfo():
Es gibt vier Log-Befehle, logDebug(), logInfo(), logWarn() und logError(). Diese Befehle erzeugen nicht nur unterschiedliche Zeilen, man kann zur Laufzeit steuern, ob sie eine Ausgabe erzeugen. Damit das funktioniert, muss aber ein sinnvoller Loggername gesetzt sein (das ist der erste der beiden Strings, die der Befehl erwartet) Über die Main UI in openHAB5 kann man den LogLevel für DSL Rule Log-Befehle im LogViewer einstellen.
Gegeben:

Code: Alles auswählen

logInfo('window', 'useBot = {}',useBot)
Der zugehörige Loggername lautet vollständig org.openhab.core.model.script.window, also alles bis zum letzten Punkt kennzeichnet alle log-Befehle aus DSL Rules, window ist der String, der als Loggername gesetzt wurde.
Im LogViewer kannst Du das Verhalten steuern, indem Du unten rechts auf das Zahnrad klickst und anschließend den vollständigen zu steuernden Loggernamen oben im Textfeld eingibst und mit Return bestätigst. Anschließend sollte der Loggername unten in der Liste auftauchen und Du kannst den LogLevel über das DropDown Menü einstellen. Die Einstellung wirkt unmittelbar. Der Defaultwert ist INFO, womit die logDebug-Meldung in der fünften Meldung nicht ausgegeben wird, solange Du den Loggernamen nicht explizit auf DEBUG oder TRACE setzt (TRACE ist im Zusammenhang mit DSL Rules aber witzlos, das ist nur für Addons und den Core Code relevant). Wenn Du den Level weiter erhöhst (also z.B. auf ERROR) werden auch die logInfo-Befehle nicht mehr ausgeführt.
Das Konstrukt

Code: Alles auswählen

if (log) logInfo('rules', logPrefix + meldeText)
Kann man sich auf die Weise komplett sparen, man muss die Befehle nur so nutzen, wie die Programmierer sie sich gedacht haben... ;)

Sinnvollerweise nutzt man unterschiedliche Loggernamen, z.B. in unterschiedlichen Rules, oder auch abhängig von der Funktion.
openHAB5.0.1 stable in einem Debian-Container (trixie, OpenJDK 21 headless runtime) (Proxmox 9.0.11, LXC)

TomW80
Beiträge: 125
Registriert: 7. Mai 2021 19:11
Answers: 0

Re: rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von TomW80 »

Ich habe die die ursprüngliche Rule von hier genommen und dann an meine Bedürfnisse angepasst.
Da das immer ohne Probleme funktioniert hat, habe ich an der Rule auch nichts geändert. Nach dem Motto "Never touch a running rule" :D

Ich werde Eure Hinweise mal umsetzen und schauen ob die Rule dann wieder ohne Probleme funktioniert.

Danke erstmal.

TomW80
Beiträge: 125
Registriert: 7. Mai 2021 19:11
Answers: 0

Re: rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von TomW80 »

udo1toni hat geschrieben: 31. Okt 2025 21:07 Beispiel:
Statt

Code: Alles auswählen

var long offenInMinuten = (now.toInstant().toEpochMilli() - lastUpdate.toEpochMilli())/(1000*60)
wäre

Code: Alles auswählen

var Integer iOpenDuration = Duration.between(i.lastUpdate,now).toMinutes
eine "hübschere" Variante.
Mit var Integer funktioniert es nicht, da kommt die Fehlermeldung
Type mismatch: cannot convert from long to Integer
Ich muss das Integer in long ändern und den Code außerdem so schreiben

Code: Alles auswählen

var long iOpenDuration = Duration.between(i.lastUpdate("mapdb"),now).toMinutes
Mein Problem ist allerdings immer noch vorhanden.
Ich habe das Gefühl, dass der Status des items manchmal nur beim schließen des Fensters geschrieben wird.

Ist es vielleicht sinnvoll wenn ich den Persistence-Service mapdb mal deinstalliere und dann wieder installiere?

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

Re: rule funktioniert nicht mehr wie gewünscht - Problem mit lastUpdate

Beitrag von udo1toni »

Wenn, dann bitte Long. Besser wäre aber, dennoch Integer zu nutzen und hinter das .toMinutes ein .intValue anzuhängen. Also

Code: Alles auswählen

var Integer iOpenDuration = Duration.between(i.lastUpdate("mapdb"),now).toMinutes.intValue
Wie funktionieren die Items? Von welchem Binding kommen die Status? Kannst Du die Changed Ereignisse alle sehen?
openHAB5.0.1 stable in einem Debian-Container (trixie, OpenJDK 21 headless runtime) (Proxmox 9.0.11, LXC)

Antworten