- 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

Alle 10 sekunden liest der Node die Daten ein.
Das Wegspeichern zurück in den HA geschieht mittels Sensor Node

. 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…
- 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.
- 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.
- 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
- 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}]