Node-RED Workflow

  1. Einleitung

Ich schreibe diesen Foreneintrag, da ich bisher noch Anleitung gefunden habe, die mir an einem Stück erklärt hat, wie sich Node-RED im Home Assistant einbinden lässt.
Mein Anwendungsfall: Ich habe einen Wasserzähler, den ich per MQTT an den HomeAssistant eingebunden habe. Der Zähler liefert einfach den Zählerstand als Rohwert. Über Node-RED möchte ich daraus Summenwerte für Tag, Monat und Jahr bilden, die ich in einer Influxdb2 Datenbank abspeichere

Von Node Red habe ich keine blassen Schimmer.
Den Sinn und Zweck von Node Red wenn man bereits Home Assistant im Einsatz hat, habe ich ehrlicherweise noch nie wirklich verstanden. :man_shrugging:

Aber Dein Vorhaben lässt sich auch wunderbar direkt in Home Assistant umsetzen. :wink:

Stichwort: Verbrauchszähler.

Edit:
Eine InfluxDB Anbindung zu Home Assistant gibt es ja auch.

Gruß
Osorkon

  1. Werte zwischen Node-Red und HA austauschen

Zunächst einmal habe ich mir ein kurzes Demoprogramm geschrieben um zu schauen, wie Daten aus dem HomeAssistant in Node-Red eingelesen und wieder zurückgeschrieben werden können.

Aktuelle Werte lese ich über das Poll-State element ein
image
Alle 10 sekunden liest der Node die Daten ein.
Das Wegspeichern zurück in den HA geschieht mittels Sensor Node
image
. Um diesen Sinnvoll zu verwenden muss man über HACS den Node-Red Companion verwenden. Ich habe zunächst versucht ein eigenes Sensor Template in meiner Sensor.yaml anzulegen. Das lässt sich dann zwar im Sensor Entity config Feld auswählen, ich habe es aber nicht hinbekommen, dass Node-Red den Sensor-Wert dann auch beschreibt.

Hi Osorkon, ja, viele Wege führen nach Rom… Ich wollte halt hier mal den Weg über Node Red Beschreiben. Ich möchte meine Daten in einem separaten Bucket meiner Datenbank speichern. Home Assistant unterstützt momentan soweit ich weiß nur ein Bucket. Das war einer der Gründe…
Ich werde meinen Weg jetzt trotzdem mal weiter beschreiben…

  1. Sensor konfigurieren

Dank der Companion Erweiterung, lässt sich in der Entity config ein neuer Sensor anlegen. Hier mal ein Beispiel für meinen Sensor der den aktuellen Tagesverbrauch aufzeichnen soll:

Sobald der Flow deployed wurde, erscheint im HomeAssistant der neue Sensor in der Entitätenliste.


Somit ist der Sensor dann auch im HomeAssistant bekannt.

  1. Daten Persistenz

Bei mir läuft der HomeAssistant auf einem Raspberry Pi. Wenn ich mal Wartungsarbeiten durchführe, oder mal den Strom abstelle, möchte ich nicht, dass meine Zählerstände wieder bei 0 beginnen.
Im Node-Red Flow verwende ich zum zwischenspeichern von Daten Context-Variablen. Werden Variablen nur lokal in einer Funktion verwendet, benutze ich context.set() bzw. context.get(). Bei Variablen, die in mehreren Funktionen verwendet werden global.set() und global.get().

Damit die Daten bei einem Reset nicht verloren gehen, speichere ich sie im Filesystem. Dazu muss jedoch die settings.js Konfigurationsdatei von Node-RED um den Eintrag

contextStorage: {
   default: "memoryOnly",
   memoryOnly: { module: 'memory' },
   file: { module: 'localfilesystem' }
}

ergänzt werden. Die Konfigurationsdatei habe ich per SSH über den Root Access verändert. Die Datei befindet sich im Verzeichnis ./mnt/data/supervisor/addon_config im *_nodered Verzeichnis.

Laut Dokumentation ist es egal an welcher Stelle der Eintrag erfolgt. Daher habe ich den Eintrag ganz unten in die settings.js eingefügt.

Damit dann Context-Varialben auch wirklich im Filesystem abgelegt werden muss der Speicherort angegeben werden.

Beispielsweise wird die Variable daily_consumption auf Fileebene gesichert.

var daily = global.get("daily_consumption", "file");
global.set("daily_consumption", daily, "file");

Nach einem echten Shutdown bleibt der Kontext weiterhin bestehen.

  1. Cron Job

Zum zyklischen abspeichern der Daten in der Influx Datenbank verwende ich Cron Jobs. Dazu habe ich das Plugin node-red-contrib-cron-plus installiert.
Über drei CronJobs löse ich jeweils ein Event

  • Jeden Tag um 0 Uhr aus um die Tageswert wegzuspeichern
  • Jeden Monat für den Monatswert
  • Jedes Jahr für den Jahreswert
  1. Influxdb2

Ich verwende eine Influx2 Datenbank zum speichern der Daten. In der Datenbank habe ich einen Bucket angelegt, der Langzeitdaten enthält. Die Retention steht also quasi auf unendlich.
Da Homeassistant nur eine InfluxDB verwalten kann, habe ich für die Daten, die ich ansonsten von HA speichern möchte ein separates Bucket angelegt. Die Retention habe ich auf 30 Tage festgelegt.
Für den Datenaustausch mit Influx habe ich in der Node-RED Palette die Erweiterung node-red-contrib-influxdb installiert. Die Unterstützt auch influxdb2.

So sieht momentan meine Lösung aus:

[{"id":"f4d6565e7ab83bc3","type":"comment","z":"ee1fe889c3402651","name":"Wasseruhr","info":"","x":100,"y":100,"wires":[]},{"id":"876178fde21fddf8","type":"poll-state","z":"ee1fe889c3402651","name":"Poll Watermeter Raw value","server":"be360aa8.148b88","version":3,"exposeAsEntityConfig":"","updateInterval":"10","updateIntervalType":"num","updateIntervalUnits":"seconds","outputInitially":false,"outputOnChanged":false,"entityId":"sensor.watermeter_raw","stateType":"num","ifState":"","ifStateType":"str","ifStateOperator":"is","outputs":1,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":197,"y":517,"wires":[["bd2bd737f3f0c79d"]]},{"id":"8a998f4a059577ed","type":"function","z":"ee1fe889c3402651","name":"calculate current/delta","func":"// This function has 1 output ports for \n// current delta count\n\nvar current = msg.payload; //current data [L]\nvar previous = context.get(\"WasserUhr\", \"file\"); //previous data\n\n// That should never happen\nif (!current){\n    current = 0;\n}\n\n// if previous is zero, empty or lower than current, \n// things wont work as expected, return 0\nif (!previous || previous > current) {\n    context.set(\"WasserUhr\", current, \"file\"); //update previous\n        \n    msg = { payload: 0.0 }; //define a second msg object\n\n    return [msg]; //return 0.0\n}\n\n//calculate difference\nvar delta = current - previous\n\n// check for huge jumps\nif (delta > 100){\n    // limit the jump...\n    delta = 0;\n}\n\nmsg = { payload: delta}; //define a second msg object\n\n\ncontext.set(\"WasserUhr\", current, \"file\"); //update previous\nreturn [msg]; //return current and delta\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":520,"wires":[["ffdb62d325aa04b2"]]},{"id":"ffdb62d325aa04b2","type":"function","z":"ee1fe889c3402651","name":"generate values","func":"var delta = msg.payload; //current delta\n\n// read current context\nvar daily = global.get(\"daily_consumption\", \"file\"); \nvar monthly = global.get(\"monthly_consumption\", \"file\"); \nvar yearly = global.get(\"yearly_consumption\", \"file\"); \n\n// This function has 3 output ports so 3 messages\n// are provided\nvar msg1; // daily\nvar msg2; // monthly\nvar msg3; // yearly\n\n// That should never happen\nif (!daily) {\n    daily = 0;\n}\n\nif (!monthly) {\n    monthly = 0;\n}\n\nif (!yearly) {\n    yearly = 0;\n}\n\ndaily += delta;\nmonthly += delta;\nyearly += delta;\n\n// store new context\nglobal.set(\"daily_consumption\", daily, \"file\");\nglobal.set(\"monthly_consumption\", monthly, \"file\");\nglobal.set(\"yearly_consumption\", yearly, \"file\"); \n\nmsg1 = { payload: daily }; //daily [L]\nmsg2 = { payload: monthly * 0.001 }; // monthly[m³]\nmsg3 = { payload: yearly * 0.001}; // yearly [m³]\n\n\nreturn [msg1, msg2, msg3]; //return current and delta","outputs":3,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":720,"wires":[["252675a0e8f3de06"],["685d597cdf78d39f"],["74bafc207b613f1c"]]},{"id":"bd2bd737f3f0c79d","type":"rbe","z":"ee1fe889c3402651","name":"check value changed","func":"rbe","gap":"","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":460,"y":520,"wires":[["8a998f4a059577ed"]]},{"id":"c0be886589bb769a","type":"debug","z":"ee1fe889c3402651","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":820,"y":660,"wires":[]},{"id":"0ebd354765c9ba69","type":"debug","z":"ee1fe889c3402651","name":"debug 6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":820,"y":720,"wires":[]},{"id":"e79aeb2100669d30","type":"debug","z":"ee1fe889c3402651","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":820,"y":780,"wires":[]},{"id":"79bde97499ebae61","type":"cronplus","z":"ee1fe889c3402651","name":"montly @ 0:0","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output1","defaultLocation":"","defaultLocationType":"default","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 0 1 * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":150,"y":240,"wires":[["b5476a7c13b7114d"]]},{"id":"d452227d01dd113a","type":"cronplus","z":"ee1fe889c3402651","name":"daily @ 0:0","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output1","defaultLocation":"","defaultLocationType":"default","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 0 * * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":150,"y":180,"wires":[["a620c1be70fbe6f6"]]},{"id":"26b20432d97a93fe","type":"cronplus","z":"ee1fe889c3402651","name":"yearly @ 1.1 0:0","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output1","defaultLocation":"","defaultLocationType":"default","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 0 1 1 *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":160,"y":300,"wires":[["6688afbb3406e339"]]},{"id":"a620c1be70fbe6f6","type":"function","z":"ee1fe889c3402651","name":"function 2","func":"var daily = global.get(\"daily_consumption\", \"file\"); \n\n// reset current day consumption\nglobal.set(\"daily_consumption\", 0, \"file\")\n\nmsg = { payload: daily }; //daily [L]\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":180,"wires":[["7b1b59201d027d71","db7d9f17ac31eae5"]]},{"id":"7b1b59201d027d71","type":"ha-sensor","z":"ee1fe889c3402651","name":"last day","entityConfig":"a60d036584161a8d","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":720,"y":180,"wires":[["59169edcc42f62e5"]]},{"id":"db7d9f17ac31eae5","type":"debug","z":"ee1fe889c3402651","name":"debug 8","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":80,"wires":[]},{"id":"252675a0e8f3de06","type":"ha-sensor","z":"ee1fe889c3402651","name":"current day","entityConfig":"7b2bc87937d4b369","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":610,"y":660,"wires":[["c0be886589bb769a"]]},{"id":"74bafc207b613f1c","type":"ha-sensor","z":"ee1fe889c3402651","name":"current year","entityConfig":"17071c1a91ddd27f","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":610,"y":780,"wires":[["e79aeb2100669d30"]]},{"id":"685d597cdf78d39f","type":"ha-sensor","z":"ee1fe889c3402651","name":"current month","entityConfig":"62da56ae3c8188bf","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":600,"y":720,"wires":[["0ebd354765c9ba69"]]},{"id":"b5476a7c13b7114d","type":"function","z":"ee1fe889c3402651","name":"function 3","func":"var monthly = global.get(\"monthly_consumption\", \"file\"); \n\n// reset current month consumption\nglobal.set(\"monthly_consumption\", 0, \"file\")\n\nmsg = { payload: monthly }; //daily [L]\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":240,"wires":[["7d66ea84b10148f6"]]},{"id":"6688afbb3406e339","type":"function","z":"ee1fe889c3402651","name":"function 4","func":"var yearly = global.get(\"yearly_consumption\", \"file\"); \n\n// reset current month consumption\nglobal.set(\"yearly_consumption\", 0, \"file\")\n\nmsg = { payload: yearly }; //daily [L]\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":300,"wires":[["23a31457b4e7a640"]]},{"id":"7d66ea84b10148f6","type":"ha-sensor","z":"ee1fe889c3402651","name":"last month","entityConfig":"788f4623a6e5f08a","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":730,"y":240,"wires":[["0848f4f84881399b"]]},{"id":"23a31457b4e7a640","type":"ha-sensor","z":"ee1fe889c3402651","name":"last year","entityConfig":"9ec6dafb09bf8151","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":720,"y":300,"wires":[["7c82a7090b8ab1a5"]]},{"id":"be360aa8.148b88","type":"server","name":"Home Assistant","addon":true},{"id":"a60d036584161a8d","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter last day water consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter last day water consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"L"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"7b2bc87937d4b369","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter current day consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter current day consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"L"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"17071c1a91ddd27f","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter current year consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter current year consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"62da56ae3c8188bf","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter current month consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter current month consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"788f4623a6e5f08a","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter last month water consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter last month water consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"9ec6dafb09bf8151","type":"ha-entity-config","server":"be360aa8.148b88","deviceConfig":"","name":"Watermeter last year water consumption","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Watermeter last year water consumption"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"water"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false}]
1 „Gefällt mir“

Aus meiner Sicht lassen sich mit Node Red größere Automationen besser abbilden und gestalten. Natürlich geht das auch direkt in HA. Allerdings lässt sich so ein Flow in NR meiner Meinung nach besser überblicken.

Ich würde mal sagen, jeder wie er/sie/** es möchten.
Hier gibt es kein gut/besser/schlechter!

2 „Gefällt mir“

Ich kann das nur unterstreichen - gerade wenn man Logikfehler hat - kann man schnell mal ein Kabel kappen oder mit der Debug Node was überprüfen. Ich werde komplexere Aufgaben nie wieder ohne NodeRed machen - zudem auch hier eine große Community mit einer Vielzahl von Nodes dahintersteht und sich mit den HA Integrationen hervorragend ergänzt.

Klar kann man herkömmliche textorientiere Programmierung wie Python, Yaml Dateien etc. nutzen - aber das ist lang nicht so übersichtlich und komfortable, wie mit einem grafischen Entwicklerwerkzeug.

das kann ich nur unterstreichen, auch wenn

es vielleicht nicht jedem einleuchtet - vielleicht weil er es noch nie probiert hat??? :wink:

2 „Gefällt mir“

Hallo!
Ich bin extrem neu im Thema Home Assistant und komme aus der iobroker-Welt! Auch da habe ich schon Node-Red eingesetzt und habe eigentlich gedacht, ich hätte es verstanden… nun stehe ich aber wieder wie ein Kleinkind da und muss normal leichte Dinge kommplett neu lernen… schreiben von Datenpunkten, normal einfach aber mit HA wohl doch nicht! Mein Node-Red liesst einen Sensor per MQTT Node, inkremetiert diesen und soll nun einen HA Sensor diesen neuen Wert schreiben. Wie OBEN beschreiben, nur mit der Gasuhr statt Wasseruhr :slight_smile:

Wenn ich nun HA neu starte und der Trigger des MQTT Sensors ausgelöst wird, bekomme ich immer ein NaN und nicht den Wert, den ich unter Zustände im HA sehen kann… dann kann er ihn nicht inkrementieren und schreibt “unknow” zurück in mein HA Sensor… HÖLLE :frowning:

Was mache ich falsch?

Ich teste grade eine andere Node um Werte in HA zu schreiben, kommt aber auch nix an

Ich nutze auch sehr viel Node-Red, aber für Deine Anwendung g würde sich m.M.n. auch ein Template-Sensor direkt in HA anbieten, oder?

Puh, kann ich nicht sagen … was meinst Du damit? Kannst Du mir ein Beispiel geben? KAnn man da direkt MQTT Topics abonnieren und inkrementiert speichern?

Ich denke schon, müsstest Du aber mal ausprobieren.

Einen Template-Sensor kannst Du direkt unter Einstellungen/Geräte/Helfer erstellt und vorher den Code in den Entwicklerwerkzeugen unter “Template” testen.

Ein Beispiel habe ich nicht, da ich bisher nur mit direkt in HA vorhandenen Entitäten gearbeitet habe.

Das sagt das tolle Internet dazu:

Schritt 1)

MQTT Topic erstellen:

sensor:
  -platform: 
    mqttname:"Original MQTT Value"
    state_topic:"your/mqtt/topic"

Schritt 2)

Template-Sensor einrichten

sensor:
  -platform: template
    sensors:
      increased_value:
        friendly_name:"Increased Value"
        unit_of_measurement:'units'     # Passe die Einheit nach Bedarf an
        value_template:>          
                                {% set original_value = states('sensor.original_mqtt_value') | float %}
          {{ (original_value + 0.1) | round(2) }}

Für mich sind das alles noch Bömische Dörfer … speichert der Template-Sensor nun den Wert schon ab?

:crayon:by HarryP: Code-/Logzeilen formatiert (bitte immer in </> einbinden)

Ja, sollte er machen.

Und wie bereits oben geschrieben:

dann siehst Du direkt das Ergebnis. :grinning:

Ich kann Schritt 1 schon nicht machen, direkte Yaml Einträge und gleichzeitige Integration MQTT vertragen sich nicht?

EDIT:

Ah, doch, aber auf diesem Weg:

Update:
Habs nun so mal eingebaut:

Werde es mal beobachten :slight_smile:

Update2:
Warum ist der Wert nach dem Neustart von HA wieder Null (0)? Warum persistiert er das nicht?

:crayon:by HarryP: Zusammenführung Mehrfachpost (bitte “bearbeiten” Funktion nutzen)