Timer Ablauf

Allgemeine Fragen rund um die "Smart Home" Hardware/Komponenten

Moderatoren: seppy, udo1toni

shuo
Beiträge: 181
Registriert: 1. Sep 2018 18:24
Answers: 0

Re: Timer Ablauf

Beitrag von shuo »

Hi Udo.

Vielleicht kannst Du mir bei meinem Problem hier nochmal helfen. Grundsätzlich hat alles super funktionert. Da es jedoch alle 5 Min das Auto abgefragt wurde, konnte es oft passieren, dass ich das Ladeziel nicht rechtzeitig treffen konnte. Also das bei der nächsten Abfrage die Batterie bereits in den 5 Min 2-5% über dem eingestellten Ziel lag. Jetzt dachte ich, ich kann das relativ einfach lösen, indem ich kurz vor dem erreichen des Ladeziels die Abstände von 5 auf 1 Minute heruntersetze. Dazu hatte ich folgendes implementiert:

Code: Alles auswählen

rule "set charing target"
when
    Item KebaState changed or
    Item KebaPower changed or
    System started
then
    if(KebaPower.state > 0 && KebaState.state == 3 ){ // wenn die Wallbox in state "laden" übergeht
        if(tMyTimer === null) { // nur falls der Timer noch nicht existiert
                tMyTimer = createTimer(now.plusSeconds(1), [|  // Timer flott starten
                    // die Berechnung, welche zyklisch erfolgen soll
                    RenaultZEServices_Zoe_Refresh.sendCommand(ON)
                    if((RenaultZEServices_Zoe_ChargeLevel.state as Number) >= (RenaultZEServices_Zoe_Charging_Target.state as Number)) { // Abbruchbedingung
                        tMyTimer = null
                        KebaSwitch.sendCommand(OFF)
                        logInfo("RemainingCharingTarget", "Zoe set charging target achieved. Switching off at : " + RenaultZEServices_Zoe_Charging_Target.state)
                        sendBroadcastNotification("Ladeziel erreicht. Schalte Ladevorgang ab bei: " + RenaultZEServices_Zoe_ChargeLevel.state + "%"  )
                        // und was sonst noch zu erledigen ist, z.B. Ladevorgang abbrechen
                    } else{
                        if((RenaultZEServices_Zoe_ChargeLevel.state as Number) >= ((RenaultZEServices_Zoe_Charging_Target.state as Number)-3)){ //ab 3% weniger als Zielladung, aktualisiere alle 2 Min um genauer stoppen zu können
                            tMyTimer.reschedule(now.plusMinutes(2)) // Timercode in 5 Minuten erneut ausführen
                        } else{
                                tMyTimer.reschedule(now.plusMinutes(5)) // Timercode in 5 Minuten erneut ausführen
                            }
                        }
                ])
            }
    }
    
end

Aber irgendwie hängt das komplette System gegen Ende und ich muss openhab komplett neu starten. Weißt Du wo ich den Denkfehler habe?

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

Re: Timer Ablauf

Beitrag von udo1toni »

Der Denkfehler dürfte darin liegen, dass Du aus dem Timer heraus über das Item RenaultZEServices_Zoe_Refresh vermutlich die Daten neu einlesen lässt. Das geht totsicher schief, denn die Daten können anschließend innerhalb des Timers noch gar nicht zur Verfügung stehen.
Zeig doch mal, wie genau Du die Daten anforderst, vielleicht ist es sinnvoller, die Rule anders aufzubauen.
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

shuo
Beiträge: 181
Registriert: 1. Sep 2018 18:24
Answers: 0

Re: Timer Ablauf

Beitrag von shuo »

In der RenaultZEServices_Zoe_Refresh führe ich ein Pyton-Skript aus. Die Rule sieht so aus:

Code: Alles auswählen

rule "RenaultZEService_Zoe_Refresh_server"
when
	Item RenaultZEServices_Zoe_Refresh received command ON or 
	System started 

then
    
    val results_status = executeCommandLine(Duration.ofSeconds(30), "sudo", "-u", "openhabian", "/usr/local/bin/pyze", "status", "--km")
    logInfo("resultsStatus", "results_Status Plugged " + results_status)

    val results_vehicle = executeCommandLine(Duration.ofSeconds(30),"sudo", "-u", "openhabian", "/usr/local/bin/pyze", "vehicles")
    logInfo("resultsVehicle", "results_Vehicle Plugged " + results_vehicle)

    RenaultZEServices_Zoe_Refresh.postUpdate(OFF)

end

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

Re: Timer Ablauf

Beitrag von udo1toni »

Und wie kommen die Werte in die Items? Schreibst Du die via REST API rein?
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

shuo
Beiträge: 181
Registriert: 1. Sep 2018 18:24
Answers: 0

Re: Timer Ablauf

Beitrag von shuo »

Genau, die Werte werden über ein Python - Skript via REST API in die ITEMS geschrieben.

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

Re: Timer Ablauf

Beitrag von udo1toni »

Dann solltest Du komplett anders vorgehen.
Schreibe eine Rule, welche die Auswertung vornimmt. Die wird über Item xy changed getriggert.
Die Rule kann als Steuerwert auch ein Item setzen, welches dann die Zykluszeit des Timers darstellt.
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

shuo
Beiträge: 181
Registriert: 1. Sep 2018 18:24
Answers: 0

Re: Timer Ablauf

Beitrag von shuo »

Kann Dir leider nicht ganz folgen.
udo1toni hat geschrieben: 15. Jan 2022 21:32 Schreibe eine Rule, welche die Auswertung vornimmt. Die wird über Item xy changed getriggert.
Das ist ja bereits der Fall.
udo1toni hat geschrieben: 15. Jan 2022 21:32 Die Rule kann als Steuerwert auch ein Item setzen, welches dann die Zykluszeit des Timers darstellt.
Kannst mir das näher erklären?

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

Re: Timer Ablauf

Beitrag von udo1toni »

shuo hat geschrieben: 17. Jan 2022 09:15 Kann Dir leider nicht ganz folgen.
udo1toni hat geschrieben: 15. Jan 2022 21:32 Schreibe eine Rule, welche die Auswertung vornimmt. Die wird über Item xy changed getriggert.
Das ist ja bereits der Fall.
Nein. Du hast eine Rule, welche einen Timer startet. Im Timer triggerst Du eine Abfrage und untersuchst unmittelbar die Items, in welchen irgendwann mal die Ergebnisse landen. Die aktuell vorliegenden Daten sind aber noch vom vorigen Durchlauf (also gewöhnlich 5 Minuten alt) Dass die Daten aktuell sind, ist ausgeschlossen, da die 2. Rule einige Millisekunden braucht, bis sie ausgeführt wird. Dann muss das Python Script laufen, welches ebenfalls eininge Millisekunden braucht (plus Antwortzeit des Renault Service).
Zu dem Zeitpunkt ist der Timer schon abgearbeitet.
shuo hat geschrieben: 17. Jan 2022 09:15
udo1toni hat geschrieben: 15. Jan 2022 21:32 Die Rule kann als Steuerwert auch ein Item setzen, welches dann die Zykluszeit des Timers darstellt.
Kannst mir das näher erklären?
Na, Du musst die Rules genau anders herum aufbauen:

Code: Alles auswählen

rule "update timespan and eventually stop charging"
when
    Item RenaultZEServices_Zoe_ChargeLevel changed or
    Item RenaultZEServices_Zoe_Charging_Target changed
then
    var nChargeLevel  = 0                                                                                             // Default Wert, falls kein gültiger Wert
    if(RenaultZEServices_Zoe_ChargeLevel.state instanceof Number)                                                     // falls gültiger Wert
        nChargeLevel  = (RenaultZEServices_Zoe_ChargeLevel.state as Number).floatValue                                // übernimm diesen Wert
    var nChargeTarget = 0                                                                                             // Default Wert, falls kein gültiger Wert
    if(RenaultZEServices_Zoe_Charging_Target.state instanceof Number)                                                 // falls gültiger Wert
        nChargeTarget = (RenaultZEServices_Zoe_Charging_Target.state as Number).floatValue                            // übernimm diesen Wert
    if(nChargeLevel >= nChargeTarget) {                                                                               // Ziel überschritten?
        tMyTimer?.cancel                                                                                              // laufenden Timer abbrechen (falls vorhanden)
        tMyTimer = null                                                                                               // Zeiger löschen
        myTime.postUpdate(5)                                                                                          // Default Zyklus setzen
        logInfo("RemainingCharingTarget", "Zoe set charging target achieved. Switching off at : {} %", nChargeTarget)
        sendBroadcastNotification("Ladeziel erreicht. Schalte Ladevorgang ab bei: " + nChargeTarget.toString + "%"  )
    } else if(nChargeLevel > nChargeTarget - 4 && myTime.state != 2) {                                                // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(2)                                                                                          // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(2))                                                                       // und Timer neu planen (optional)
    }
end

rule "schedule charging timer"
when
    Item KebaState changed or
    Item KebaPower changed
then
    if(KebaPower.state > 0 && KebaState.state == 3 && tMyTimer === null)                                              // falls Timer nicht existiert und Zoe lädt
        tMyTimer = createTimer(now.plusSeconds(1), [|                                                                 // Timer anlegen und gleich starten
            val tSched = if(myTime.state instanceof Number) (myTime.state as Number) else 0                           // Zykluszeit in Minuten
            val results_status = executeCommandLine(Duration.ofSeconds(30), "sudo", "-u", "openhabian", "/usr/local/bin/pyze", "status", "--km")
            logInfo("charge", "results_Status Plugged {}", results_status)
            val results_vehicle = executeCommandLine(Duration.ofSeconds(30),"sudo", "-u", "openhabian", "/usr/local/bin/pyze", "vehicles")
            logInfo("charge", "results_Vehicle Plugged [}", results_vehicle)
            if(tSched > 0) {
                logInfo("charge","Timer wird in {} Minuten erneut ausgeführt!",tSched)
                tMyTimer.reschedule(now.plusMinutes(tSched))
            } else {
                logInfo("charge","Timer wird nicht mehr ausgeführt!")
                tMyTimer = null
            }
        ])
end
Innerhalb des Timers werden nur die aktuellen Werte angefordert.
Ändern sich die Werte, so löst die 2. Rule aus, welche die Zykluszeit berechnet und den Ladeschluss erkennt.
Das Switch Item RenaultZEServices_Zoe_Refresh kann entfallen, dafür braucht es ein Number Item myTime, in welchem die Zykluszeit in Minuten hinterlegt wird.
Es können beliebig weitere Zeitspannen eingebaut werden, wenn der Ladestand z.B. um mehr als 20%, 40%, 60% abweicht, entsprechend z.B. alle 10 Minuten oder gar nur alle 20 Minuten.

Man könnte noch ein DateTime Item anlegen, welches innerhalb des Timers jeweils den aktuellen Zeitstempel erhält. Dann kann man eine Anzeige gestalten in der Art "Letzte Aktualisierung um xx:xx Uhr, nächste Aktualisierung nach yy Minuten". Oder man definiert ein String Item, in welchem man die Info in Klartext durch den Timer eintragen lässt ("Nächste Aktualisierung um xx:xx Uhr")

Code: Alles auswählen

val StringBuilder strNext = new StringBuilder
strNext.append("Nächste Aktualisierung um ")
strNext.append(String::format("%02d",now.plusMinutes(tSched).getHour))
strNext.append(":")
strNext.append(String::format("%02d",now.plusMinutes(tSched).getMinute))
strNext.append(" Uhr.")
myMessge.postUpdate(strNext.toString)
Das nur als Beispiel, man kann die Uhrzeit auch anders über String::format() erhalten, ich war jetzt aber zu faul, die korrekte Formatierung rauszusuchen.

Ich habe in den beiden Rules unterschiedliche Verfahren angewandt, den Status eines Number Items in einer Variablen bzw. Konstanten zu speichern, um ihn lokal zu nutzen. Bei Verfahren sind gleichwertig, solange man den Wert nicht innerhalb der Rule nochmals ändern will. Dann müsste man natürlich auf jeden Fall eine Variable statt einer Konstanten (var statt val) verwenden. Es ging mir hier eher darum, beide Möglichkeiten aufzuzeigen.
Der ternäre Operator a = if(b) c else d ist halt sehr schlank, aber leider nicht ganz so eingängig wie a = d; if(b) a = c. Der ternäre Operator ist zwingend, wenn man eine Konstante alternativ mit unterschiedlichen Werten belegen will.
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

shuo
Beiträge: 181
Registriert: 1. Sep 2018 18:24
Answers: 0

Re: Timer Ablauf

Beitrag von shuo »

Hi Udo. Du bist für heute mein persönlicher Held. Vielen Dank für Deine tolle Erklärung.

Das Item myTimer ist nur als Zwischenspeicher benutzt und default sind hier immer die 5 Minuten.
udo1toni hat geschrieben: 17. Jan 2022 14:29 Es können beliebig weitere Zeitspannen eingebaut werden, wenn der Ladestand z.B. um mehr als 20%, 40%, 60% abweicht, entsprechend z.B. alle 10 Minuten oder gar nur alle 20 Minuten.
Wenn ich Dich richtig verstanden habe, dann wären das jeweils eine weitere else if Anweisungen nach dem Block:

Code: Alles auswählen

else if(nChargeLevel > nChargeTarget - 4 && myTime.state != 2) {                                                // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(2)                                                                                          // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(2))                                                                       // und Timer neu planen (optional)
    }
braucht hier die else nicht eigentlich noch eine Bedingung, wenn beide if's, also nChargeLevel >= nChargeTarget oder nChargeLevel > nChargeTarget - 4 && myTime.state != 2 nicht erfüllt sind?
udo1toni hat geschrieben: 17. Jan 2022 14:29 val tSched = if(myTime.state instanceof Number) (myTime.state as Number) else 0
Hat esinen bestimmten Grund warum Du in der ersten Rule direkt mit myTime arbeitest und bei der zweiten mit tSched?

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

Re: Timer Ablauf

Beitrag von udo1toni »

shuo hat geschrieben: 18. Jan 2022 14:38 braucht hier die else nicht eigentlich noch eine Bedingung, wenn beide if's, also nChargeLevel >= nChargeTarget oder nChargeLevel > nChargeTarget - 4 && myTime.state != 2 nicht erfüllt sind?
Also, die 1. Bedingung lautet

Code: Alles auswählen

if(nChargeLevel >= nChargeTarget)
nChargeTarget wurde also überschritten oder erreicht. Damit soll der Ladevorgang abgebrochen werden.

die 2. Bedingung lautet

Code: Alles auswählen

else if(nChargeLevel > nChargeTarget - 4 && myTime.state != 2) {                                                // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(2)                                                                                          // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(2))                                                                       // und Timer neu planen (optional)
    }
Das else sorgt dafür, dass diese Bedingung nur geprüft wird, falls die erste Bedingung nicht zutrifft. Hier wird geprüft, ob nChargeLevel höher als nChargeTarget minus 4 ist. Da die erste Bedingung nicht erfüllt sein kann, muss nChargeLevel zwischen nChargeTarget und nChargeTarget-4 liegen, oder anders:

Code: Alles auswählen

nChargeTarget - 4 < nChargeLevel < nChargeTarget
Außerdem muss gleichzeitig myTime.state != 2 sein. Nur wenn beide Bedingungen erfüllt sind, muss der Timerwert angepasst werden.
Wenn du weitere Grenzwerte einführen willst (wie von mir vorgeschlagen) muss man aufpassen, wie man die Abfragen staffelt. Entweder man staffelt streng monoton fallend oder streng monoton wachsend (ansonsten müsstest Du jeweils Ober- und Untergrenze angeben, und wir sind faul...). In jedem der Blöcke muss außerdem geprüft werden, ob der Wert nicht ohnehin schon dem Sollwert entspricht. so:

Code: Alles auswählen

    if(nChargeLevel >= nChargeTarget) {                                                                               // Ziel überschritten?
        tMyTimer?.cancel                                                                                              // laufenden Timer abbrechen (falls vorhanden)
        tMyTimer = null                                                                                               // Zeiger löschen
        myTime.postUpdate(5)                                                                                          // Default Zyklus setzen
        logInfo("RemainingCharingTarget", "Zoe set charging target achieved. Switching off at : {} %", nChargeTarget)
        sendBroadcastNotification("Ladeziel erreicht. Schalte Ladevorgang ab bei: " + nChargeTarget.toString + "%"  )
    } else if(nChargeLevel > nChargeTarget - 4 && myTime.state != 2) {                                                // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(2)                                                                                          // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(2))                                                                       // und Timer neu planen (optional)
    } else if(nChargeLevel > nChargeTarget - 10 && myTime.state != 5) {                                               // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(5)                                                                                          // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(5))                                                                       // und Timer neu planen (optional)
    } else if(nChargeLevel > nChargeTarget - 20 && myTime.state != 10{                                                // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(10)                                                                                         // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(10))                                                                      // und Timer neu planen (optional)
    } else if(myTime.state != 20{                                                                                     // weniger als 4 % bis zum Ladeziel
        myTime.postUpdate(20)                                                                                         // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(20))                                                                      // und Timer neu planen (optional)
    }
Wenn es so viele mögliche Varianten gibt, ist es naheliegend, den Code etwas anders zu gestalten:

Code: Alles auswählen

    var Number nSoll
    if(nChargeLevel >= nChargeTarget)                                                                                 // Ziel überschritten?
        nSoll = 0
    else if(nChargeLevel >= nChargeTarget -  4)                                                                       // Ziel -  4 überschritten?
        nSoll = 2
    else if(nChargeLevel >= nChargeTarget - 10)                                                                       // Ziel - 10 überschritten?
        nSoll = 5
    else if(nChargeLevel >= nChargeTarget - 20)                                                                       // Ziel - 20 überschritten?
        nSoll = 10
    else                                                                                                              // sonst
        nSoll = 20

    if(nSoll == 0) {
        tMyTimer?.cancel                                                                                              // laufenden Timer abbrechen (falls vorhanden)
        tMyTimer = null                                                                                               // Zeiger löschen
        myTime.postUpdate(5)                                                                                          // Default Zyklus setzen
        logInfo("RemainingCharingTarget", "Zoe set charging target achieved. Switching off at : {} %", nChargeTarget)
        sendBroadcastNotification("Ladeziel erreicht. Schalte Ladevorgang ab bei: " + nChargeTarget.toString + "%"  )
    } else if(myTime.state != nSoll) {                                                                                // Sollwert weicht von Istwert ab
        myTime.postUpdate(nSoll)                                                                                      // Zyklus anpassen
        tMyTimer.reschedule(now.plusMinutes(nSoll))                                                                   // und Timer neu planen (optional)
    }
So wird es eventuell auch etwas klarer. Der Sollwert ergibt sich aus dem Abstand nChargeTarget zu nChargeLevel. Ist das Soll 0, so muss der Timer abgebrochen werden, da der Zielladestand erreicht oder überschritten wurde.
Ansonsten muss geprüft werden, ob das errechnete Soll vom aktuellen Soll abwicht. Ist ads der Fall, so muss das Soll angepasst werden (mit dem postUpdate) und der Timer muss neu geplant werden.
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

Antworten