Seite 1 von 1

Batterieüberwachung mit Whatsapp

Verfasst: 19. Okt 2024 11:30
von Boris
Hallo zusammen,
ich nutze seit langem dieses Forum zum mitlesen, und ab heute bin ich auch aktives Mitglieg, da ich mit meiner Problematik nicht weiterkomme.
Ich versuche eigentlich etwas sehr Banales. Ich möchte mit einer Rule, beim unterschreiten des SoC der Batterie eine Whatsappnachricht auf mein Handy schicken lassen. Ist die Batterie dann irgendwann wieder über den Schwellwert, soll auch eine Nachricht geschickt werden. Zum experimentieren, habe ich als Eingangsgöße einen Temperatursensor genommen, welchen ich über Anfassen und Loslassen an dem Schwellwert vorbei laufen lasse.
Leider greift meine IF Abfrage nicht. Obwohl die Bedingung erfüllt ist, geht er nicht in den Zweig, welcher das Versenden der Nachricht auslöst.
Der Code ist wie folgt:

Code: Alles auswählen

rule "WA Batterie fast leer"
when
    Item Hzng2kW_Temp changed
then 
    if(!(previousState instanceof Number)) {
        return;
    }
    if(!(newState instanceof Number)) {
        return;
    }
    logInfo("Rule Batterymonitor", "Vorher: "+previousState)  //debug
    logInfo("Rule Batterymonitor", "Aktuell: "+newState) //debug
    if(previousState >= 28 && newState < 28.0) {
        logInfo("Rule Batterymonitor", "Battery low") //debug
        var MessageWhatsApp = ("Batterie unter 28%.")
        MessageWhatsApp = MessageWhatsApp.replace(" ", "%20")
        executeCommandLine ("wget", "https://api.callmebot.com/whatsapp.php?source=openHAB&phone=+xxxxxxxxxxxxx&apikey=xxxxxx&text="+MessageWhatsApp)
    }
    else if(previousState < 28.0 && newState >= 28.0) {
        logInfo("Rule Batterymonitor", "Battery OK") //debug
        var MessageWhatsApp = ("Batterie wieder über 28%.")
        MessageWhatsApp = MessageWhatsApp.replace(" ", "%20")
        executeCommandLine ("wget", "https://api.callmebot.com/whatsapp.php?source=openHAB&phone=xxxxxxxxxxxxxx&apikey=xxxxxx&text="+MessageWhatsApp)
    }
end
Im Log erscheint das dann so:

Code: Alles auswählen

2024-10-19 11:03:40.761 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'whatsappnotifications.rules'
2024-10-19 11:03:58.292 [INFO ] [ore.model.script.Rule Batterymonitor] - Vorher: 26.7 °C
2024-10-19 11:03:58.292 [INFO ] [ore.model.script.Rule Batterymonitor] - Aktuell: 26.6 °C
2024-10-19 11:04:08.276 [INFO ] [ore.model.script.Rule Batterymonitor] - Vorher: 26.6 °C
2024-10-19 11:04:08.277 [INFO ] [ore.model.script.Rule Batterymonitor] - Aktuell: 27.2 °C
2024-10-19 11:04:13.276 [INFO ] [ore.model.script.Rule Batterymonitor] - Vorher: 27.2 °C
2024-10-19 11:04:13.277 [INFO ] [ore.model.script.Rule Batterymonitor] - Aktuell: 29.3 °C
2024-10-19 11:04:18.278 [INFO ] [ore.model.script.Rule Batterymonitor] - Vorher: 29.3 °C
2024-10-19 11:04:18.279 [INFO ] [ore.model.script.Rule Batterymonitor] - Aktuell: 30.6 °C
2024-10-19 11:04:23.280 [INFO ] [ore.model.script.Rule Batterymonitor] - Vorher: 30.6 °C
Meiner Meinung nach, müsste er in den Else Zweig gehen, da die Temperatur (später SoC) ja von unten kommt. Aber wie man an den Logeinträgen sieht, kommt die entsprechende Meldung nicht, obwohl previousState 27.2 und newState 29.3 ist.
Wo liegt der Fehler... ich sehe es nicht. Wahrscheinlich betriebsblind...
Vielen Dank vorab für eure Hilfe,
Boris

Re: Batterieüberwachung mit Whatsapp

Verfasst: 19. Okt 2024 15:56
von udo1toni
Wenn Du genau auf Deine Logzeilen schaust, siehst Du, dass dort eine Einheit mit ausgegeben wird, die Du aber in der Definition der Log Meldung nicht stehen hast.
Daraus schließe ich, dass es sich bei Hzng2kW_Temp um ein Item vom Typ Number:Temperature handelt. QuantityType Status muss man entweder auch mit der passenden Einheit vergleichen, oder vor dem Vergleich die Einheit strippen.
Es bietet sich an, die Rule auch sonst noch etwas zu "verschönern" ;)

Code: Alles auswählen

rule "WA Batterie fast leer"
when
    Item Hzng2kW_Temp changed
then 
    if(!(previousState instanceof Number)) return;
    if(!(newState instanceof Number))      return;

    val nNew = (newState as Number).floatValue
    val nOld = (previousState as Number).floatValue
    var String strMessage = ""

    logDebug("battery", "Alter Wert: {} Aktueller Wert: {}",nOld, nNew)

    if(nOld >= 28 && nNew < 28) {                   // unter 28 gefallen
        logDebug("battery", "Battery low")
        strMessage = ("Batterie unter 28%.")
    }
    else if(nOld < 28 && nNew >= 28) {              // über 28 gestiegen
        logDebug("battery", "Battery OK") 
        strMessage = ("Batterie wieder über 28%.")
    }
    if(strMessage.length > 1) {
        strMessage = strMessage.replace(" ", "%20")
        executeCommandLine ("wget", "https://api.callmebot.com/whatsapp.php?source=openHAB&phone=xxxxxxxxxxxxxx&apikey=xxxxxx&text="+strMessage)
    }
end
Die Abbruchbedingungen lösen jeweils nur einen Befehl aus, entsprechend kann man die geschweiften Klammern weg lassen.

Aus den beiden Variablen newState und previousState werden die Zahlenwerte ermittelt - .floatValue ignoriert alle Zeichen außer die Ziffern 0-9 und dem ersten Dezimalpunkt, das ist also eine sehr bequeme Variante, die Einheiten loszuwerden.
val definiert eine lokale Konstante, da wir den Wert als solchen nicht mehr ändern wollen, reicht das hier auch.
Mit der nächsten Zeile definieren wir schon mal eine String Variable, die später die Nachricht enthalten wird.

Um die Werte nur für's Debugging zu loggen, bietet sich logDebug() an. Standardmäßig ist das Logging auf INFO eingestellt, dann werden die Logmeldungen nicht generiert. Die geschweiften Klammern werden mit dem nächsten kommaseparierten Wert substituiert, dabei wandelt openHAB automatisch Zahlenwerte in Text (Substituierung funktioniert nicht überall, aber bei den log-Befehlen geht es...)
Nun kommt die Auswertung, die jetzt fehlerfrei funktionieren sollte, einmal für gefallen unter 28, einmal gestiegen über 28. Allerdings wird nur die oben definierte Variable gesetzt und eventuell eine Logzeile generiert.

Zum Schluss prüft die Rule, ob in der Variablen Text enthalten ist. Ist da der Fall, so werden die beiden übrigen Zeilen ausgeführt, um die Meldung auszugeben. Merke: vermeide doppelten Code, wo immer das möglich ist. Sollte sich in der URL ein Fehler eingeschlichen haben, tritt er bei Deiner Variante eventuell nur bei der einen Hälfte der Meldungen auf, so wie gezeigt tritt er dann immer auf und lässt sich einfacher lokalisieren.

Was das Logging betrifft: der erste angegeben String ist der Name des Loggers, genauer der letzte Teil des Loggernamens. Der Loggername sollte keine Leer- oder Sonderzeichen enthalten und sich am besten an die Namenskonventionen halten, hier also vor allem Kleinbuchstaben und nach Möglichkeit kurz.
Wenn Du die Logzeilen zu Gesicht bekommen möchstest, rufst Du die Karaf Konsole auf (z.B. ssh openhab@localhost -p8101 oder alternativ openhab-cli console), das Passwort ist gewöhnlich habopen.
Default lauscht der Karaf ssh Server nur auf dem lokalen Interface, man kann es aber auch auf die IP des Rechners erweitern, dann ist die Karaf Konsole aus dem gesamten LAN erreichbar. Allerdings sollte man dann mindestens das Passwort ändern oder besser noch einen anderen User anlegen, am besten mit einem private key für die Authentisierung (leider momentan nur RSA möglich)
Hier mal als Beispiel eine komplette ssh Session, damit klar wird, wie das funktioniert (das Listing habe ich aber gekürzt auf die wichtigste Zeile):

Code: Alles auswählen

Using username "openhabian".
Authenticating with public key "rsa-key-20240201" from agent
Linux oh4test 6.8.12-2-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-2 (2024-09-05T10:03Z) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Oct 11 09:33:34 2024 from 192.168.178.56

DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
iiiiiiiiiiiiiii   192.168.178.56    iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
DD        Ip -> 192.168.178.65
DD   Release -> Debian GNU/Linux 12 (bookworm)
DD    Kernel -> Linux 6.8.12-2-pve
DD  Platform -> lxc
DD    Uptime -> 19 day(s). 05:56:56
DD CPU Usage -> 6.82% avg over 4 cpu(s) (6 core(s) x 1 socket(s))
DD  CPU Load -> 1m: 0.18, 5m: 0.50, 15m: 0.64
DD    Memory -> Free: 1.74GB (58%), Used: 1.26GB (42%), Total: 3.00GB
DD      Swap -> Free: 0.00GB (0%), Used: 0.00GB (100%), Total: 0.00GB
DD      Root -> Free: 6.29GB (78%), Used: 1.70GB (22%), Total: 8.00GB
DD   Updates -> 3 apt updates available.
DD  Sessions -> 1 session(s)
DD Processes -> 34 running processes of 4194304 maximum processes
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS

                          _   _     _     ____   _
  ___   ___   ___   ___  | | | |   / \   | __ ) (_)  ____   ___
 / _ \ / _ \ / _ \ / _ \ | |_| |  / _ \  |  _ \ | | / _  \ / _ \
| (_) | (_) |  __/| | | ||  _  | / ___ \ | |_) )| || (_) || | | |
 \___/|  __/ \___/|_| |_||_| |_|/_/   \_\|____/ |_| \__|_||_| | |
      |_|                  openHAB 4.3.0.M1 - Milestone Build

Looking for a place to get started? Check out 'sudo openhabian-config' and the
documentation at https://www.openhab.org/docs/installation/openhabian.html
The openHAB dashboard can be reached at http://oh4test:8080
To interact with openHAB on the command line, execute: 'openhab-cli --help'

openhabian@oh4test:~$ openhab-cli console

Logging in as openhab
Password:

                           _   _     _     ____
   ___   ___   ___   ___  | | | |   / \   | __ )
  / _ \ / _ \ / _ \ / _ \ | |_| |  / _ \  |  _ \
 | (_) | (_) |  __/| | | ||  _  | / ___ \ | |_) )
  \___/|  __/ \___/|_| |_||_| |_|/_/   \_\|____/
       |_|       4.3.0.M1 - Milestone Build

Use '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
To exit, use '<ctrl-d>' or 'logout'.

openhab> log:list
Logger                                             │ Level
───────────────────────────────────────────────────┼──────
...
org.openhab.core.model.script.battery              │ INFO
...
openhab> log:set DEBUG org.openhab.core.model.script.battery
openhab> log:list
Logger                                             │ Level
───────────────────────────────────────────────────┼──────
...
org.openhab.core.model.script.battery              │ DEBUG
...
openhab> logout
openhabian@oh4test:~$
Der Befehl log:set DEBUG org.openhab.core.model.script.battery schaltet den Logger org.openhab.core.model.script.battery auf DEBUG Level. Alle DSL Rule Logger sind Kinder von org.openhab.core.model.script und erben ohne spezifisches Setting dessen Log Level. Man könnte also auch das Logging für alle Meldungen der DSL auf einen Schlag auf DEBUG setzen, oder auch auf WARN, ERROR oder gar OFF :) Aber meist möchte man ja etwas gezielter steuern...

log:set wirkt unmittelbar, es bedarf keines Neustarts, das ist also ungemein praktisch, um im laufenden Betrieb mal eben mehr Informationen zu bekommen (oder halt auch weniger...)

EDIT: Code korrigiert (.length statt .size)

Re: Batterieüberwachung mit Whatsapp

Verfasst: 19. Okt 2024 16:56
von Boris
Wahnsinn, das ist ja wie aus dem Lehrbuch.
Aber mit der Textvariablen scheint er doch Schwierigkeiten zu haben. Hast Du da eine Idee?

Code: Alles auswählen

2024-10-19 16:50:13.940 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'whatsappnotifications-1' failed: 'size' is not a member of 'java.lang.String'; line 22, column 8, length 15 in whatsappnotifications
Gibt es eine andere Methode als über size abzufragen?
Aber ansonsten sehr schön geschrieben.

Re: Batterieüberwachung mit Whatsapp

Verfasst: 19. Okt 2024 18:35
von udo1toni
Kann sein, dass es .length heißen muss... da komme ich gerne mal durcheinander...

Re: Batterieüberwachung mit Whatsapp

Verfasst: 20. Okt 2024 11:06
von Boris
... .length() Jetzt gehts.
Perfekt vielen Dank. Das sieht durch deine Hilfe jetzt echt sehr sauber aus.
Schönen Sonntag,
Boris

Re: Batterieüberwachung mit Whatsapp

Verfasst: 21. Okt 2024 20:27
von Boris
Hab noch eine Nachfrage zur Stringverarbeitung. Wenn ich in den String noch ein Item mit unterbringen will... sagen wir mal battery_energy.

Code: Alles auswählen

        strMessage = ("Batterie wieder über 25%. "+ battery_energy +"geladen")
Dann schmeißt er mir folgendes raus:
battery_energy (Type=NumberItem, State=5558.153802, Label=Battery Energie, Category=null, Groups=[Victron])

Hätte aber gerne "5558Wh" als Ausgabe.

Das Item ist wie folgt definiert.

Code: Alles auswählen

Number  battery_energy              "Batterie Energie [%.0f Wh]"       (Victron)     {channel = "mqtt:topic:Victron:Venus:Wallbox_Energy"}
Irgendwie habe ich diese Itemdefinition noch nicht ganz verstanden...

Re: Batterieüberwachung mit Whatsapp

Verfasst: 21. Okt 2024 21:52
von peter-pan
Hast du schon "battery_energy.state.toString" probiert ?

Evtl. musst du den Wert für die Ausgabe formatieren.
Ein Beispiel:

Code: Alles auswählen

logInfo("astro-test", "das formatierte Datum ist {}", Date_distance.state.format("%1$ta, der %1$td.%1$tb %1$ty ")) 
Das Ergebnis sieht dann so aus:

Code: Alles auswählen

2024-10-21 19:41:53.626 [INFO ] [openhab.core.model.script.astro-test] - das formatierte Datum ist Mo., der 21.Okt. 24 
oder vielleicht so:

Code: Alles auswählen

battery_energy.state.format("%,.0f Wh").toString
(ungetestet)

Re: Batterieüberwachung mit Whatsapp

Verfasst: 22. Okt 2024 07:42
von Boris
Vielen Dank schonmal für den Tipp. Werde es gleich heute Abend mal testen. Ich hatte sowas auch schon mal im Kopf... hab es jedoch verworfen, weil ich dachte, dass in der Konfiguration des Items ja schon die Formatierung hinterlegt ist. Und wie Udo schon sehr richtig sagte, soll man ja doppelten Quelltext möglichst vermeiden.
Ich habe diesen Typ Item immer noch nicht ganz verstanden.

Re: Batterieüberwachung mit Whatsapp

Verfasst: 22. Okt 2024 12:21
von udo1toni
Woher kommt denn das Item battery_energy? Grundsätzlich kannst Du in DSL Rules nur auf einen Teil der Metadaten eines Items zugreifen, das hat damit zu tun, dass die DSL das Urgestein ist :) und etliche Aspekte von Items erst sehr viel später in openHAB eingebaut wurden. Und leider hat man beim Design der Items zwar an vieles gedacht, aber eben nicht an flexible Erweiterbarkeit...

Was das Format als String betrifft: Ein Item Status ist zunächst immer ein Status, also kein String. Das heißt, Du musst aus dem .state gewöhnlich einen String erzeugen, wenn Du ihn als Teilstring irgendwo einfügen willst.
Leider (oder auch nicht...) gibt sich openHAB oftmals Mühe, solche "Kleinigkeiten" on-the-fly zu ergänzen, z.B. wenn Du einen Item Status mit einer Zahl vergleichst, versucht openHAB von sich aus, den Status als Zahl zu interpretieren. Allerdings ist der Datentyp Number (den nutzt openHAB im Zweifel) mächtig, er enthält auch den Datentyp QuantityType. In der Folge wird openHAB z.B. aus einem Number:Temperature Status 23.25 °C den Number Wert 23.25 °C generieren. Und wenn Du dann einen Vergleich if(23 > itemstatus) ausführst, macht openHAB daraus automatisch if(23 > 23.25 °C). Statt nun zu motzen, weil die beiden Datentypen zueinander inkompatibel sind, wird der Vergleich immer true oder immer false ergeben, egal ob der reine Zahlenwert nun tatsächlich größer oder kleiner ist (bin gerade zu faul, es auszuprobieren).
Deshalb ist es wichtig, nach Möglichkeit notwendige Konvertierungen bevorzugt selbst auszuführen. Kostet keine zusätzliche Zeit (außer beim Erstellen der Rule...), macht die Rule aber type safe. Leider (zum zweiten) muss man bei solchen Konvertierungen darauf gefasst sein, dass die Konvertierung nicht möglich ist, weil der Status gar kein gültiger Wert ist. Entsprechend muss man vor jeder Konvertierung sicherstellen, dass auch ein gültiger Wert anliegt, der sicher konvertiert werden kann. Diese Prüfung habe ich oben in der Rule in den ersten zwei Zeilen erledigt. instanceof liefert true, wenn der angegebene Wert Teil des gewünschten Datentyps ist, ansonsten false. Das ! bedeutet nicht, ist also die Negierung, aus true wird false und umgekehrt. Damit wird return; ausgeführt, wenn der Datentyp nicht stimmt.
Wenn Du in der selben Rule den Status von battery_energy mit ausgeben willst, wäre es allerdings sinnvoll, die Prüfung anders vorzunehmen, denn die Rule kann ja trotzdem durch laufen.

Nun zum Format: Du kannst tatsächlich in DSL Rules auf verschiedene Aspekte des Items zugreifen, z.B. auf den Status (.state), das Label (.label) oder gar auf die Unit (.unit, natürlich nur, wenn diese auch vorhanden und gesetzt ist). Du kannst auch auf die State Description zugreifen, bzw. auf das dort hinterlegte Pattern (.stateDescription.pattern), nur hilft das alles nur bedingt, denn wenn Du die Formatierung im Label definierst, wird diese Information nicht durchgereicht und die Formatierung im State Description Pattern folgt nicht zwingend der gleichen Schreibweise wie die der String.format() Funktion.
Man könnte in der Rule also z.B. folgendes Konstrukt bauen:

Code: Alles auswählen

    val strFormat = battery_energy.stateDescription.pattern
    var strValue   = "-- Wh"
    if(battery_energy.state instanceof Number)
       strValue = String.format(strFormat ,(battery_energy.state as Number).floatValue)
Nun enthält die lokale Konstante myString entweder "-- Wh", wenn nämlich kein gültiger Wert vorliegt, oder eben den formatierten Text, so wie im State Description Pattern angegeben - immer vorausgesetzt, das Pattern wird von String.format() in der angegebenen Form unterstützt. Sollte im Pattern %unit% verwendet werden, wird das z.B. ziemlich sicher schief gehen ;)
Was nicht funktioniert, ist auf .displayState zuzugreifen, das wäre der Status in der durch das Pattern vorgegebenen Formatierung.

Einfacher ist es, direkt den Status als String zu verwenden battery_energy.state.toString, der String enthält dann bei QuantityType Status auch die Einheit. Er ist aber nicht formatiert, kann also z.B. auch sehr viele Nachkommastellen beinhalten.
Will man das verhindern, nutzt man String.format() und schreibt einfach das gewünschte Format hin, statt es sich umständlich aus den Metadaten zu ziehen.

Code: Alles auswählen

    var strValue   = "-- Wh"
    if(battery_energy.state instanceof Number)
       strValue = String.format("%,.0f Wh",(battery_energy.state as Number).floatValue)
Die Prüfung auf einen gültigen Wert ist zwingend, weil Du den Status als Number.floatValue benötigst, das Type Casting nach Number führt zu einer nullPointer Exception, wenn der Status nicht vom Typ Number ist.

In der vorliegenden Rule ist es aber sehr fragwürdig, sich die ganze Mühe zu machen, denn per Definition wird die Meldung nur in exakt zwei Fällen generiert, nämlich wenn der SoC den Wert 28 % über- bzw. unterschreitet. Damit wäre battery_energy ja immer auf einem sehr ähnlichen Level.
Oder hat battery_energy gar nichts mit Hzng2kW_Temp zu tun? Dann ist allerdings die Ausgabe dieser Information evtl. fragwürdig.