Node-RED Context erklärt: Werte speichern ohne Datenbank
In Node-RED werden Daten normalerweise von Node zu Node weitergereicht. Ein Sensorwert kommt rein, wird verarbeitet und anschließend zum Beispiel im Debug-Fenster ausgegeben, per MQTT verschickt oder in einer Datenbank gespeichert.
Aber was ist, wenn du dir einen Wert nur kurz merken möchtest?
- den letzten Sensorwert
- den letzten Schaltzustand
- einen einfachen Zähler
- den Zeitpunkt der letzten Aktion
- einen Grenzwert
- den Status eines Gerätes
Genau dafür gibt es in Node-RED den sogenannten Context.
Mit dem Context kannst du Werte speichern, ohne direkt eine Datenbank wie SQLite, InfluxDB oder MySQL zu verwenden. Das ist besonders praktisch für kleine Automationen, einfache Zustände und schnelle Logik innerhalb eines Flows.
https://youtu.be/EJaSk1J1iD4
Was ist der Node-RED Context?
Der Context ist vereinfacht gesagt ein kleiner Speicherbereich innerhalb von Node-RED.
Du kannst dort Werte ablegen und später wieder abrufen. Das funktioniert ähnlich wie eine Variable in einem Programm, nur dass der Wert nicht nur während einer einzelnen Nachricht existieren muss.
Ein Temperaturwert kommt regelmäßig in Node-RED an. Du möchtest prüfen, ob sich der Wert seit der letzten Messung geändert hat. Dafür musst du den vorherigen Wert irgendwo speichern.
Natürlich könntest du dafür eine Datenbank verwenden. Für so eine kleine Aufgabe wäre das aber übertrieben.
Hier reicht der Node-RED Context völlig aus.
Wann ist der Context sinnvoll?
Der Context ist ideal für kleine Zustände und einfache Zwischenspeicher.
- letzter Messwert eines Sensors
- vorheriger Schaltzustand eines Shelly
- Anzahl der Tastendrücke
- letzte MQTT-Nachricht
- letzter Zeitstempel
- aktueller Modus einer Automation
- Merker, ob eine Benachrichtigung bereits gesendet wurde
Ein praktisches Beispiel aus dem Smarthome:
Ein Shelly Plug S liefert regelmäßig seine aktuelle Leistung. Du möchtest aber nicht bei jedem identischen Wert eine neue Aktion auslösen. Also speicherst du den letzten Wert im Context und vergleichst ihn mit dem neuen Wert.
Nur wenn sich der Wert geändert hat, läuft der Flow weiter.
Wann sollte ich lieber eine Datenbank verwenden?
Der Context ist praktisch, aber kein Ersatz für eine richtige Datenbank.
Eine Datenbank ist besser geeignet, wenn du Daten langfristig speichern oder später auswerten möchtest.
Wenn du Messwerte dauerhaft speichern möchtest, ist SQLite eine gute und leichtgewichtige Lösung. Wie du SQLite mit Node-RED verwendest, zeige ich dir in meinem Beitrag „Node-RED mit Docker – SQLite Datenbank anbinden und erste Abfragen“.
Nutze eine Datenbank für:
- Messwerte über Tage, Wochen oder Monate
- Energieverbrauch
- Temperaturverläufe
- Diagramme
- CSV-Export
- historische Auswertungen
- viele Datensätze
- einzelne Zustände
- letzte Werte
- einfache Merker
- kleine Zähler
- temporäre Logik
Merke dir als einfache Faustregel:
Context ist für Zustände. Datenbanken sind für Historien.
Die drei Context-Arten in Node-RED
Node-RED unterscheidet zwischen drei Bereichen:
- Node Context
- Flow Context
- Global Context
Diese drei Varianten unterscheiden sich darin, wo der gespeicherte Wert sichtbar ist.
Der Node Context ist nur innerhalb einer bestimmten Node sichtbar.
Der Node Context speichert Werte nur innerhalb einer bestimmten Node. Ideal für einfache Zähler oder lokale Merker in einer Function Node.
Das bedeutet: Nur diese eine Function Node kann den Wert speichern und später wieder abrufen.
Das ist sinnvoll, wenn der Wert wirklich nur in dieser einen Node benötigt wird.
let zaehler = context.get("zaehler") || 0;
zaehler++;
context.set("zaehler", zaehler);
msg.payload = zaehler;
return msg;
Dieser Code erzeugt einen einfachen Zähler.
Beispiel für das speichern von Werten im Node Context
Flow als JSON zum Import in Node-RED
},
{
"id": "f4eb283e75ccec08",
"type": "group",
"z": "f5ee6b8ce3d7f0ea",
"name": "Flow 1",
"style": {
"label": true
},
"nodes": ,
"x": 34,
"y": 79,
"w": 572,
"h": 82
},
{
"id": "e0d1008fa6962d21",
"type": "group",
"z": "f5ee6b8ce3d7f0ea",
"name": "Flow 2",
"style": {
"label": true
},
"nodes": ,
"x": 34,
"y": 239,
"w": 572,
"h": 82
},
{
"id": "9b5afd956de9abc1",
"type": "inject",
"z": "f5ee6b8ce3d7f0ea",
"g": "f4eb283e75ccec08",
"name": "Start",
"props": ,
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 130,
"y": 120,
"wires":
]
},
{
"id": "ac6cdb52ac14f29f",
"type": "function",
"z": "f5ee6b8ce3d7f0ea",
"g": "f4eb283e75ccec08",
"name": "increment value",
"func": "let zaehler = context.get("zaehler") || 0;nnzaehler++;nncontext.set("zaehler", zaehler);nnmsg.payload = {n zaehler: zaehler,n flow: 1n};nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 300,
"y": 120,
"wires":
]
},
{
"id": "a7ee079e7b0ec0ec",
"type": "debug",
"z": "f5ee6b8ce3d7f0ea",
"g": "f4eb283e75ccec08",
"name": "print value",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 120,
"wires":
},
{
"id": "f1bd4fcfe79191df",
"type": "inject",
"z": "f5ee6b8ce3d7f0ea",
"g": "e0d1008fa6962d21",
"name": "Start",
"props": ,
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 130,
"y": 280,
"wires":
]
},
{
"id": "7f14fb5dea0406ed",
"type": "function",
"z": "f5ee6b8ce3d7f0ea",
"g": "e0d1008fa6962d21",
"name": "increment value",
"func": "let zaehler = context.get("zaehler") || 0;nnzaehler++;nncontext.set("zaehler", zaehler);nnmsg.payload = {n zaehler: zaehler,n flow: 2n};nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 300,
"y": 280,
"wires":
]
},
{
"id": "0ed25a54054903c9",
"type": "debug",
"z": "f5ee6b8ce3d7f0ea",
"g": "e0d1008fa6962d21",
"name": "print value",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 280,
"wires":
}
]
Bei jedem Durchlauf wird der gespeicherte Wert gelesen, um 1 erhöht und wieder gespeichert.
Der Flow Context ist innerhalb eines kompletten Flow-Tabs sichtbar.
Der Flow Context speichert Werte für alle Nodes innerhalb desselben Flows. So kann ein Wert gespeichert und in einem anderen Verarbeitungspfad weiterverwendet werden.
Das bedeutet: Mehrere Nodes auf derselben Arbeitsfläche können auf denselben Wert zugreifen.
Das ist in der Praxis oft die nützlichste Variante.
Beispiel zum Speichern eines Wertes:
flow.set("letzteTemperatur", msg.payload);
return msg;
Beispiel zum Abrufen eines Wertes:
let letzteTemperatur = flow.get("letzteTemperatur");
msg.payload = letzteTemperatur;
return msg;
Damit kannst du einen Wert in einer Function Node speichern und in einer anderen Function Node wieder verwenden.
Node-RED - Flow Context Beispiel
Flow als JSON zum Import in Node-RED
},
{
"id": "4a2e2396ea5e2544",
"type": "mqtt in",
"z": "989e20d562ad874b",
"name": "",
"topic": "/sensor/temp",
"qos": "0",
"datatype": "auto-detect",
"broker": "2dc0a4b70d96ebb5",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 110,
"y": 180,
"wires":
]
},
{
"id": "68282a22fe7258cd",
"type": "debug",
"z": "989e20d562ad874b",
"name": "print value from MQTT flow",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 940,
"y": 180,
"wires":
},
{
"id": "bfb362dbabd6e504",
"type": "function",
"z": "989e20d562ad874b",
"name": "save sensor value",
"func": "flow.set("letzteTemperatur", msg.payload || undefined);nnreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 450,
"y": 180,
"wires":
]
},
{
"id": "acdb440a4401a7af",
"type": "function",
"z": "989e20d562ad874b",
"name": "build debug message",
"func": "msg.payload = "neuer Sensorwert: " + flow.get("letzteTemperatur");nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 680,
"y": 180,
"wires":
]
},
{
"id": "bf7938c56e845be4",
"type": "switch",
"z": "989e20d562ad874b",
"name": "",
"property": "letzteTemperatur",
"propertyType": "flow",
"rules": ,
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 270,
"y": 180,
"wires": ,
]
},
{
"id": "4a28d1dcca1585e9",
"type": "inject",
"z": "989e20d562ad874b",
"name": "Start",
"props": ,
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"x": 110,
"y": 60,
"wires":
]
},
{
"id": "3351321a370f12ee",
"type": "function",
"z": "989e20d562ad874b",
"name": "create default value",
"func": "flow.set("letzteTemperatur", 0);nnreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 290,
"y": 60,
"wires":
]
},
{
"id": "cc074b457a60b73f",
"type": "debug",
"z": "989e20d562ad874b",
"name": "print message",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 640,
"y": 120,
"wires":
},
{
"id": "7795de29b7e93269",
"type": "function",
"z": "989e20d562ad874b",
"name": "create message",
"func": "msg.payload = "selben Sensorwert empfangen."nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 440,
"y": 120,
"wires":
]
},
{
"id": "2dc0a4b70d96ebb5",
"type": "mqtt-broker",
"name": "",
"broker": "mosquitto",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Der Global Context ist in der gesamten Node-RED-Instanz sichtbar.
Der Global Context speichert Werte für alle Flows und Tabs innerhalb derselben Node-RED-Instanz. Dadurch können zentrale Werte in mehreren Flows verwendet werden.
Das bedeutet: Alle Flows können auf diesen Wert zugreifen.
Das ist sinnvoll für Werte, die wirklich überall benötigt werden.
- zentrale Konfiguration
- IP-Adresse eines Gerätes
- API-Basisadresse
- globaler Betriebsmodus
- gemeinsam genutzte Grenzwerte
global.set("shelly_ip", "192.168.178.50");
return msg;
Abrufen kannst du den Wert dann so:
let ip = global.get("shelly_ip");
msg.url = "http://" + ip + "/rpc/Shelly.GetStatus";
return msg;
Hier wird die IP-Adresse eines Shelly-Gerätes zentral gespeichert und später für einen HTTP-Request verwendet.
Beispiel - Global Context
Beispiel Global Context - Flow #1
},
{
"id": "ca2f5066067c72ee",
"type": "inject",
"z": "fee017b8a550cd64",
"name": "Start",
"props": ,
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 530,
"y": 60,
"wires":
]
},
{
"id": "605814d58e345769",
"type": "function",
"z": "fee017b8a550cd64",
"name": "set value to global context",
"func": "global.set("name", "Stefan")nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 730,
"y": 60,
"wires":
]
}
]
Beispiel Global Context - Flow #2
},
{
"id": "be95863731cef366",
"type": "inject",
"z": "7037c6aa34965146",
"name": "Start",
"props": ,
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 370,
"y": 60,
"wires":
]
},
{
"id": "cf8196e6d58f8d00",
"type": "function",
"z": "7037c6aa34965146",
"name": "build greeting",
"func": "msg.payload = "Hallo " + (global.get("name") || "Hans");nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": ,
"x": 540,
"y": 60,
"wires":
]
},
{
"id": "b6017c014fbeb390",
"type": "debug",
"z": "7037c6aa34965146",
"name": "print message",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 740,
"y": 60,
"wires":
}
]
Context im Debug-Bereich anzeigen
Node-RED bietet in der Seitenleiste auch eine Context-Ansicht.
Dort kannst du gespeicherte Werte prüfen. Das ist beim Entwickeln sehr hilfreich, weil du sehen kannst, ob dein Wert wirklich gespeichert wurde.
Im Reiter Contextdaten zeigt Node-RED gespeicherte Werte aus dem Flow Context und Global Context an. So lässt sich prüfen, ob Werte korrekt gespeichert wurden.