Rechnen mit Variablen in rule

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

Antworten
kdb
Beiträge: 19
Registriert: 9. Jan 2024 18:00
Answers: 0

Rechnen mit Variablen in rule

Beitrag von kdb »

Irgendwie sehe ich momentan scheinbar den Wald vor alter Bäumen nicht.
Alles läuft unter OpenHAB 3 auf Raspi. Die Regel wird mit Notepad manuell erstellt.
Über http Binding beziehe ich eine Zeitangabe und muss die Differenz in Stunden zur aktuellen Zeit ausrechnen.
Das Aktuelle Datum wird in die Variablen jahr_akt, monat_akt usw. zerlegt und gespeichert.
Das über http gelieferte Datum wird entsprechend in Jahr, monat … gespeichert.
Senden an das Item ist erfolgt nur zu Testzwecken. Später soll hiermit die Heizung gesteuert werden.

Code: Alles auswählen

    val datum = httpBUSHzgSet_Datum.state.toString.split(' ').get(0)
    val zeit = httpBUSHzgSet_Datum.state.toString.split(' ').get(1)
    var int jahr = datum.toString.split('/').get(0)
    var int monat = datum.toString.split('/').get(1)
    var int tag = datum.toString.split('/').get(2)
    var int stunde = zeit.toString.split(':').get(0)
    var int minute = zeit.toString.split(':').get(1)
    var int jahr_akt = now.getYear()
    var int monat_akt = now.getMonth.getValue()
    var int tag_akt = now.getDayOfMonth()
    var int stunde_akt = now.getHour()
    var int minute_akt = now.getMinute()    
Diese Aufteilung der Daten wurde mehrfach durch Senden eines Strings an das item kdbINFOxx erfolgreich getestet.
Sobald auch nur der erste Versuch , der jetzt auskommentierten Befehle versucht wird, funktioniert nichts.

Code: Alles auswählen

//var int delta_stunde = (jahr_akt - jahr) * 365 * 24 
//     var int delta_stunde =  ( jahr_akt - jahr ) * 365 * 24 
//    var int  delta_stunde1 = delta_stunde + ( monat_akt - monat ) * 30 * 24
//    delta_stunde = delta_stunde + ( tag_akt - tag ) * 24
//    delta_stunde = delta_stunde + ( stunde_akt - stunde )
//    var curr = "kdbINFO " + jahr + " / monat " + monat + "  / tag  " + tag + " stunde " + stunde   + " : " + minute + " NOW  " + jahr_akt + " " + monat_akt + " " + tag_akt
    var curr = "kdbINFOxx " + datum +  "( " + jahr + "_" + monat +"_" + tag +" ) " + " //  " + zeit + " ( " + stunde + "_" + minute + " ) NOW  " + jahr_akt + " " + monat_akt + " " + tag_akt + " " + stunde_akt + " " + minute_akt
//    var curr = "kdbINFOaa " + datum + " " + jahr + " " + delta_stunde.toString
    kdbINFOxx.sendCommand(curr)
//     var int delta_stunde =  ( jahr_akt - jahr ) * 365 * 24 
//    var int  delta_stunde1 = delta_stunde + ( monat_akt - monat ) * 30 * 24
//    delta_stunde = delta_stunde + ( tag_akt - tag ) * 24
//    delta_stunde = delta_stunde + ( stunde_akt - stunde 

    var curr = "kdbINFOxx " + datum +  "( " + jahr + "_" + monat +"_" + tag +" ) " + " //  " + zeit + " ( " + stunde + "_" + minute + " ) NOW  " + jahr_akt + " " + monat_akt + " " + tag_akt + " " + stunde_akt + " " + minute_akt
//    var curr = "kdbINFOaa " + datum + " " + jahr + " " + delta_stunde.toString

    kdbINFOxx.sendCommand(curr)
PS. Meldungen aus log file werden nachgereicht.

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

Re: Rechnen mit Variablen in rule

Beitrag von udo1toni »

Erster Punkt: Der Code ist unvollständig. Es fehlt der Kontext, also z.B. ob es sich um eine DSL Rule handelt, oder um ein DSL Script usw.
Zweitens: Bitte Code immer als Code markiert einfügen (ich habe das mal angepasst und die doppelten Zeilen im ersten Block entfernt)

Inhaltlich: int ist eine Primitive Variable, nicht nur fehlen damit grundlegende Funktionen, openHAB benötigt auch etwa Faktor 20(?) länger, die Rule zu berechnen. Wenn eine Variable fix definiert werden muss, bitte nach Möglichkeit immer als Objekt (hier z.B Integer)

Aber vor allem: Warum?
Du hast einen String in der Form yyyy/mm/dd HH:MM, wahlweise vielleicht incl. Sekunden? Ein Zeitstempel in ISO-8601 sieht so aus: yyyy-MM-ddTHH:mm:ss.SSSZ, das ist also ziemlich dicht an Deinem String dran, dicht genug, um den String umzuformen, z.B. so (getestet mit Sekunden...):

Code: Alles auswählen

    val          datum = httpBUSHzgSet_Datum.state.toString.split(' ').get(0)
    val           zeit = httpBUSHzgSet_Datum.state.toString.split(' ').get(1)
    val strDT          = datum.replace('/','-') + 'T' + zeit + '.000Z'
Anschließend kannst Du mit

Code: Alles auswählen

    val dtTimestamp = ZonedDateTime.parse(strDT).withZoneSameInstant(ZoneId.systemDefault())
das Ganze als DateTime Wert umrechnen.
Mit now() steht dieses Format auch für den aktuellen Zeitpunkt zur Verfügung.

Damit ist die Berechnung des Delta trivial. Auch die Umrechnung in andere Einheiten ist damit leicht möglich.
Weiterhin kann man von einem DateTime Wert einfach per getYear, getMonth, getDay, getHour und getMinute den entsprechenden Wert erfahren.

Die wichtigste Frage wäre aber zuerst: welches Format müssen die Zielwerte exakt haben? Durch den Spaghetti Code ist es nicht eben einfach, herauszufinden, was das gewünschte Zielformat ist...

Wenn Du In einer Rule Werte berechnest und während der Entwicklung eine Ausgabe wünschst, ist ein Item nicht die erste Wahl. Besser ist es, eine Logzeile zu erzeugen. Mit openHABian als Basis steht das Log per frontail zur Verfügung, ohne frontail kannst Du aber eiun zweites Fenster öffnen und dort das openhab.log ausgeben lassen (tail -f /var/log/openhab/openhab.log), dann siehst Du den Output solcher Befehle:

Code: Alles auswählen

logInfo("heat","Test InDate: {} InTime: {} Out: {}",datum,zeit,dtTimestamp)
Das sieht dann z.B. so aus:

Code: Alles auswählen

11:04:10.539 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'httpBUSHzgSet_Datum' changed from 2024/10/24 12:23:45 to 2024/10/24 12:23:46
11:04:10.541 [INFO ] [org.openhab.core.model.script.heat   ] - Test InDate: 2024/10/24 InTime: 12:23:46 Out: 2024-10-24T12:23:46Z[Etc/UTC]
Der DateTime Wert wird automatisch als Zeitstempel formatiert, man kann aber "ganz normal" damit rechnen:

Code: Alles auswählen

    val diff = now.toEpochSecond - dtTimestamp.toEpochSecond
    logInfo("heat","Diff to now() in Seconds: {}",diff)
    logInfo("heat","Year {} Month {} Day {} Hour {} Minute {}",dtTimestamp.getYear,dtTimestamp.getMonthValue,dtTimestamp.getDayOfMonth ,dtTimestamp.getHour,dtTimestamp.getMinute)
ergibt:

Code: Alles auswählen

11:16:20.543 [INFO ] [org.openhab.core.model.script.heat   ] - Diff to now() in Seconds: 82354
11:16:20.544 [INFO ] [org.openhab.core.model.script.heat   ] - Year 2024 Month 10 Day 24 Hour 12 Minute 23
Lässt man Value weg, erhält man den Namen des Monats (leider nur in Englisch), und es gibt noch weitere nützliche Werte, z.B. getDayOfYear liefert den Tag im Jahr, oder gettDayOfWeek liefert den Wochentag (analog zum Monat, also Klartext, oder mit angehängtem Value als Zahl (1 = Montag, 7 = Sonntag)
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.7, LXC), mit openHABian eingerichtet

kdb
Beiträge: 19
Registriert: 9. Jan 2024 18:00
Answers: 0

Re: Rechnen mit Variablen in rule

Beitrag von kdb »

Hallo Udo vielen Dank für die Ausführungen. Das auf den ersten Blick umständliche Zerlegen der Werte erfolgt, um später mit den Minuten, Stunden etc. zu arbeiten. ( Es sollen zu unterschiedlichen Zeiten verschiedene Thermostate gesteuert werden )
Das Senden zum Item ist für mich der schnellste Weg zum Testen: Auf PC ändere ich die Regel, führe sie aus und auf iPad ist dann fast in Echtzeit das Ergebnis zu sehen. Ist vielleicht unkonventionell, aber für mich am einfachsten.
Die Regeln werden im Verzeichnis etc/openhab/rules mit Notepad angelegt. ( Sind das DSL Regeln ?)

Warum aber kann die Berechnung delta_stunde nicht durchgeführt werden, sondern führt zum Fehler:

An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.IntegerExtensions.operator_minus(int,int) on instance: null in regel3

Dieses ist für mich - als Windows VB.NET Entwickler - einfach unverständlich.

Vielen Dank

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

Re: Rechnen mit Variablen in rule

Beitrag von udo1toni »

Wie gesagt, die Umwandlung ist umständlich - zumindest aus openHAB Sicht. - Nun ja, auch in VB.NET nutzt man DateTime als Datentyp, heißt dort eventuell anders, und auch dort wird man bevorzugt Objekte verwenden, statt Primitives.

Vermutlich liefert schon die erste Zuweisung nicht die korrekten Daten, denn someString.split().get() liefert halt einen Teilstring und keine Zahl. Die Variable ist aber ein Primitive. Eventuell ist openHAB gnädig und castet nach int, verlassen würde ich mich nicht darauf.
Es kann also sehr gut sein, dass schon die Variable jahr keine Zahl enthält, sondern null.
Alle anderen nachfolgenden Variablen funktionieren dann ebensowenig.
Und das besagt die Fehlermeldung ja auch:
kdb hat geschrieben: Gestern 14:52 Could not invoke method [...] operator_minus(int,int) on instance: null
"Konnte die Methode Minus(int,int) nicht auf die Instanz null anwenden".

Und die Aussage:
kdb hat geschrieben: Gestern 14:52 ist vielleicht unkonventionell, aber für mich am einfachsten.
kann so ja nicht stimmen, schließlich suchst Du hier nach einer Lösung, das kann definitiv nicht der einfachste Weg sein.

Wenn es um Zeitpunkte geht, verwende bitte einfach die korrekten Methoden, dann wird das auch sehr einfach funktionieren.
Der große Aufwand besteht in dem Fall dann nur darin, zu lernen, wie das richtig geht, dafür funktioniert die Rule dann aber auch.

Logging ist übrigens in VB.NET ebenfalls die empfohlene Möglichkeit, während der Entwicklung Daten zur Laufzeit sehen. Ja, man kann auch mal auf die Schnelle ein Feld im Programm einbauen, sinnvoller ist aber die Ausgabe direkt als Text in eine Debug Konsole. Da es bei openHAB Rules keine Debug Konsole gibt, ist die einfachste Variante das Logging. Das kann man z.B. super auch über die Karaf Konsole live beobachten, und man hat nicht das Risiko, dass die Übernahme des Wertes ins Item schief geht.


Also so wie ich das sehe, ist z.B. das Deine Wunschausgabe:
kdbINFOxx 2024/10/24( 2024_10_24 ) // 12:34:56 ( 12_34 ) NOW 2024 10 26 00 56
Mit Deinem Code ginge es vermutlich so (ungetestet):

Code: Alles auswählen

    val                 datum = httpBUSHzgSet_Datum.state.toString.split(' ').get(0)
    val                  zeit = httpBUSHzgSet_Datum.state.toString.split(' ').get(1)
    var Integer          jahr = Integer.parseInt(datum.toString.split('/').get(0))
    var Integer         monat = Integer.parseInt(datum.toString.split('/').get(1))
    var Integer           tag = Integer.parseInt(datum.toString.split('/').get(2))
    var Integer        stunde = Integer.parseInt(zeit.toString.split(':').get(0))
    var Integer        minute = Integer.parseInt(zeit.toString.split(':').get(1))
    var Integer      jahr_akt = now.getYear()
    var Integer     monat_akt = now.getMonth.getValue()
    var Integer       tag_akt = now.getDayOfMonth()
    var Integer    stunde_akt = now.getHour()
    var Integer    minute_akt = now.getMinute()
    var Integer delta_stunde  = (jahr_akt - jahr) * 365 * 24
    var Integer delta_stunde1 = delta_stunde + (monat_akt - monat) * 30 * 24
                delta_stunde  = delta_stunde + ( tag_akt - tag ) * 24
                delta_stunde  = delta_stunde + ( stunde_akt - stunde )

// Nur eine der nachfolgenden Zeilen darf aktiv sein...
//    var curr = "kdbINFO "    + jahr + " / monat " + monat + "  / tag  " + tag + " stunde " + stunde   + " : " + minute + " NOW  " + jahr_akt + " " + monat_akt + " " + tag_akt
    var curr = "kdbINFOxx " + datum + "( " + jahr + "_" + monat +"_" + tag +" ) " + " //  " + zeit + " ( " + stunde + "_" + minute + " ) NOW  " + jahr_akt + " " + monat_akt + " " + tag_akt + " " + stunde_akt + " " + minute_akt
//    var curr = "kdbINFOaa " + datum + " " + jahr + " " + delta_stunde.toString
    kdbINFOxx.postUpdate(curr)
Das Datum wird in Teilstrings zerlegt, die anschließend nach Integer geparsed und in passende Objekte gespeichert werden.
Anschließend funktioniert die Berechnung (also abgesehen davon, dass diese fehlerhaft ist)

Beachte bitte auch, dass sendCommand() in diesem Kontext schlecht ist.
Du möchtest den Status des Items ändern, um diesen Status anzuzeigen. sendCommand() ändert nicht den Status, sondern sendet einen Befehl. Dass Du dennoch eine Ausgabe erhältst, ist nur dem Umstand zu verdanken, dass openHAB davon ausgeht, dass der Befehl zu einem identischen Zustand führen soll. Deshalb führt openHAB (default Verhalten) automatisch nach jedem sendCommand() auch ein postUpdate() aus.
Da Du mit dem Befehl nichts anstellen wirst, ist es also zielführender, direkt das postUpdate() zu verwenden.

Hier eine Rule, wie ich sowas mache.
Da ich nicht weiß, wie der Status von httpBUSHzgSet_Datum exakt aussieht, verwende ich Deine Ausgangslösung zumindest zum Teil.
Ich erstelle hier auch eine ähnliche Ausgabe wie in Deinem Code, leicht abgewandelt:

Code: Alles auswählen

rule "Test"
when
    Item httpBUSHzgSet_Datum received update
then
    val    ZoneId zoneId = ZoneId.of(ZoneId.systemDefault)                                  // Die Zeitzone
    val    dtfmFormatter = java.time.format.DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm')
    val          lsInput = httpBUSHzgSet_Datum.state.toString.split(' ')
    val           strLDT = lsInput.get(0).replace('/','-') + 'T' + lsInput.get(1)           // Falls Zeit mit  Sekunden ausgegeben wird
//  val            strDT = lsInput.get(0).replace('/','-') + 'T' + lsInput.get(1)           // Alternativ, falls Zeit ohne Sekunden ausgegeben wird
    val            dtLTs = LocalDateTime.parse(strLDT)                                      // Zeitstempel in LocalDateTime überführen
    val             dtTs = dtLTs.atZone(zoneId)                                             // Zeitstempel in ZonedDateTime überführen
    val Number  iDelta_s = now.toEpochSecond - dtTs.toEpochSecond                           // Differenz in Sekunden
    val Integer iDelta_h = (iDelta_s/3600).intValue                                         // Different in ganzen Stunden (abgerundet)
    var          strCurr = "kdbINFO: " + dtTs.format(dtfmFormatter) +
                              " NOW: " + now.format(dtfmFormatter) +
                            " Delta: " + iDelta_h.toString + " Stunden"
    kdbINFO.postUpdate(strCurr)                                                             // Ausgabe ins String Item
    logInfo("test","Ergebnis: {}", strCurr)                                                 // Ausgabe ins Log
end
Im Unterschied zu Deinem Code berücksichtigt die Berechnung hier auch, dass manche Monate 31 Tage oder auch nur 28 Tage haben.
Eine weitere Fehlerquelle ist die Zeitumstellung, Das gilt allerdings genauso für Deinen Code, bei meinem Code ist es aber so offensichtlich, dass ich direkt daran denken musste. Deshalb wandle ich jetzt den Zeitstempel zunächst in LocalDateiTime um (da fehlt die Zeitzone) und "verschiebe" die Zeit anschließend in die korrekte Zeitzone (die vom System vorgegeben ist)
So sieht das im Log aus:

Code: Alles auswählen

02:32:27.926 [INFO ] [org.openhab.core.model.script.test   ] - Ergebnis: kdbINFO: 2024-10-24 01:00 NOW: 2024-10-26 02:32 Delta: 49 Stunden
02:32:27.928 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'kdbINFO' changed from kdbINFO: 2024-10-24 01:00 NOW: 2024-10-26 02:11 Delta: 49 Stunden to kdbINFO: 2024-10-24 01:00 NOW: 2024-10-26 02:32 Delta: 49 Stunden
Die erste Zeile stammt vom logInfo() Befehl, die zweite Zeile stammt vom changed Ereignis, welches durch eine Änderung des Items kdbINFO ausgelöst wurde.

EDIT: Anpassung wegen der Zeitzone und Berücksichtigung von Sommer/Winterzeit
openHAB4.2.2 stable in einem Debian-Container (bookworm) (Proxmox 8.2.7, LXC), mit openHABian eingerichtet

Antworten