Wie Du Diagrammdaten in Node-RED Dashboard sichern kannst

Mit Node-RED Dashboard lassen sich relativ schnell nützliche und ansehnliche Oberflächen für IoT-Anwendungen bauen. Nur leider sind alle Diagramme wieder leer, wenn Node-RED bzw Node.js neu gestartet werden. Ich zeige Dir, wie Du die Diagrammdaten automatisch speichern und wieder laden kannst, so dass Du bei einem Neustart nicht wieder vor leeren Charts sitzt. 

Was Du benötigst

In dieser Anleitung verwende ich Node-RED auf Bluemix, die Daten speichern wir in einer DashDB Instanz. Du kannst mein Beispiel aber auch auf ein lokal laufendes Node-RED mit lokaler Datenbank oder gar Datenablage in einer Datei übertragen. Das Muster ist dabei prinzipiell das gleiche wie das, was ich Dir zeige.

Datenbank vorbereiten

Wir brauchen nur eine ganz einfache Tabelle in unserer Datenbank, die später die Werte verschiedneer Diagramme aufnehmen kann. Unsere Tabelle nennen wir CHARTDATA und sie bekommt zwei Spalten. Die Spalte CHART darf keine leeren Werte (not null) enthalten, da dies unser Identifier für das Diagramm wird. CHARTDATA speichert die eigentlichen Diagrammdaten in einem CLOB. Das macht es uns einfach, denn der Diagramm-Knoten liefert eine große JSON-Struktur mit den Diagrammdaten zurück. Die Tabelle müssen wir noch zeilenweise organisieren, da wir sonst CLOB nicht nutzen dürfen. Damit sieht unser SQL-Statemennt zum Erstellen der Tabelle wie folgt aus:

CREATE TABLE "CHARTDATA"
(
"CHART" VARCHAR(256) NOT NULL,
"CHARTDATA" CLOB
) ORGANIZE BY ROW;

Nnu erzeugen wir einen Eintrag in dieser Tabelle (dort werden im folgenden Beispiel Winddaten einer Wetterstation abgelegt). Nimm einfach einen Inject Knoten und verbinde ihn mit einem DashDB Knoten. Wenn Node.js und DashDB an die gleiche Anwendung in Bluemix gebunden sind, kannst Du Deine DashDB-Instanz direkt im Knoten auswählen ohne weitere Konfigurationen eingeben zu müssen. Als Payload für den Inject Knoten nehmen wir die folgende Zeichenkette:

INSERT INTO CHARTDATA (CHART, CHARTDATA) VALUES ( 'Wind',  '');

SQL insert Statement mit Inject-Knoten in DashDB ausführen

Wenn Du diesen kleinen Flow nun deployed hast klicke einmal auf den Inject-Knopf und die Zeile wird in der Tabelle angelegt. Später werden wir die Zeile aktualisieren und die tatsächlichen Diagrammwerte hinein schreiben. Wenn Du für weitere Diagramm die Daten speichern willst, lege einfach mit dem Inject-Knoten weitere Zeilen an, in dem Du das SQL-Statement entsprechend veränderst.

Diagrammdaten in die Datenbank sichern

Der Knoten ui_chart besitzt bereits die nötigen Fähigkeiten, damit wir Chartdaten leicht speichern und wieder laden können. In meinem Beispiel-Flow kommen Winddaten (durchschnittliche Geschwindigkeit und Böen) von einer Wetterstation. Diese rechne ich in einem function-Knoten zunächst von meter/s in km/h um und setze gleich noch msg.topic auf Wind bzw Böen, damit die beiden Linien im Diagramm richtig beschriftet werden. Damit ich nicht zu viele Datenpunkte im Chart habe, lasse ich mit dem delay-Knoten jeweils nur einen Wert alle 5 Minuten durch, die dann ins Diagramm kommen, welches die letzten 24 Stunden anzeigt.

Node-RED Flow mit Liniendiagramm (Bildquelle: Internet-of-Things.blog)

In der function „check chartdata“ prüfe ich einfach, ob mindestens 5 Datenpunkte im Diagramm vorhanden sind. Sind es weniger, endet der Flow hier, sind es 5 oder mehr werden die Daten gespeichert. Diese Schwelle habe ich eingeführt, damit nicht versehentlich ein Datensatz in der Datenbank überschrieben wird, der bereits viele Chartdaten enthielt während beim Start von Node-RED das Diagramm nur einen zufällig gerade angekommenen Datensatz enthält bevor ich alle Datensätze aus der Datenbank zurück gelesen habe. check chartdata sieht so aus:

//only proceed if some chart values are already present
if(msg.payload[0].values.length < 5) {
    return null;
}
return msg;

Anschließend wandele ich das JSON-Object, das in msg.payload gespeichert ist in einen String um. Dieser wird dann im Knoten construct SQL statement in ein SQL-Satement verpackt:

var pl = msg.payload;
msg.payload = "UPDATE CHARTDATA SET CHARTDATA = '" + pl + "' WHERE CHART = 'Wind'";
return msg;

Und dieses SQL Satement führe ich dann in der DashDB aus. Wenn Node.js und DashDB zur gleichen Anwendung in Bluemix gebunden sind, kannst Du in dem DashDB-Knoten direkt Deine Datenbankinstanz auswählen, ohne weitere Konfigurationen eingeben zu müssen.

Diagrammdaten aus der Datenbank lesen

Der ui_chart Knoten besitzt einen zweiten Ausgang, über den ein Trigger geschickt wird, wenn ein Neuladen der Diagrammdaten nötig ist. Bei mir hat er aber z.B. nicht bei einem Neustart von Node-RED ausgelöst, so dass keine Diagrammdaten aus der Datenbank geladen wurden. Daher habe ich einen eigenen Trigger dazu gebaut, der nur einmalig beim Start von Node-RED auslöst. Dafür nimmst Du einen inject  Knoten, der einen beliebigen Wert liefert (bei mir einen Timestamp). Den Knoten konfigurierst Du wie folgt:

Konfigration des Inject-Knotens (Bildquelle: Internet-of-Things.blog)

Dann wartet der Flow 5 Sekunden (weniger würde vermutlich auch gehen, kannst Du austesten), da ich ohne eine kurze Wartezeit nicht auf die Datenbank zugreifen konnte. Da müssen wir Node-RED einfach kurz Zeit geben, die Datenbankverbindung herzustellen. Dann lesen wir unsere vorher mal abgespeicherten Diagrammwerte mit folgendem SQL Statement aus der Datenbank aus

SELECT CHARTDATA FROM CHARTDATA WHERE CHART = 'Wind';

Das Ergebnis bekommen wir in msg.payload.chartdata als String zurück. Also machen wir daraus ein JSON-Objekt und legen dieses in msg.payload ab, wo es der Diagrammknoten erwartet. Der Knoten parse chartdata enthält dafür folgenden Code:

msg.payload = JSON.parse(msg.payload.CHARTDATA);
return msg;

Wenn Du nun alles so verdrahtest wie in meinem Beispiel, kannst Du getrost Node-RED oder Node.js neu starten ohne dass Du danach vor einem leeren Diagramm sitzt.

Node-RED Liniendiagramm (Bildquelle: Internet-of-Things.blog)

Und hier noch der komplette Beispiel-Flow. Einfach kopieren und in Node-RED über import > clipboard einfügen, dann musst Du noch in den DashDB-Knoten die Konfiguration auf Deine Datenbankinstanz anpassen:

[
 {
 "id": "1463aac.751bfd5",
 "type": "function",
 "z": "4d244ff7.cda44",
 "name": "mps in kmh",
 "func": "msg.payload = Math.round(msg.payload * 3,6);\nmsg.topic = \"Böen\";\nreturn msg;",
 "outputs": 1,
 "noerr": 0,
 "x": 323.20001220703125,
 "y": 147.90000915527344,
 "wires": [
 [
 "17ab898e.5cb24e"
 ]
 ]
 },
 {
 "id": "d859540.d0528b",
 "type": "ui_chart",
 "z": "4d244ff7.cda44",
 "name": "Wind 24h",
 "group": "82d4ad11.96f7a8",
 "order": 4,
 "width": "12",
 "height": "4",
 "label": "Windgeschwindigkeit (km/h)",
 "chartType": "line",
 "legend": "true",
 "xformat": "%H:%M",
 "interpolate": "basis",
 "nodata": "",
 "ymin": "",
 "ymax": "",
 "removeOlder": 1,
 "removeOlderUnit": "86400",
 "x": 687.7000122070312,
 "y": 119.14999389648438,
 "wires": [
 [
 "53222927.1e6128"
 ],
 [
 "a196d223.48f058"
 ]
 ]
 },
 {
 "id": "53e2717c.2a2ea",
 "type": "dashDB in",
 "z": "4d244ff7.cda44",
 "dashDB": "",
 "service": "dashDB-if",
 "query": "",
 "params": "",
 "name": "",
 "x": 1409.25,
 "y": 109.34991455078125,
 "wires": [
 []
 ]
 },
 {
 "id": "659fc8c0.c1dbd",
 "type": "json",
 "z": "4d244ff7.cda44",
 "name": "",
 "x": 1019.5003051757812,
 "y": 111.15003967285156,
 "wires": [
 [
 "5d5faa4e.49d904"
 ]
 ]
 },
 {
 "id": "5d5faa4e.49d904",
 "type": "function",
 "z": "4d244ff7.cda44",
 "name": "construct SQL statement",
 "func": "var pl = msg.payload;\nmsg.payload = \"UPDATE CHARTDATA SET CHARTDATA = '\" + pl + \"' WHERE CHART = 'Wind'\";\n\n//{};\n//msg.payload.CHART = \"Luftfeuchte\";\n//msg.payload.CHARTDATA = pl;\nreturn msg;",
 "outputs": 1,
 "noerr": 0,
 "x": 1211.500244140625,
 "y": 109.35005187988281,
 "wires": [
 [
 "53e2717c.2a2ea"
 ]
 ]
 },
 {
 "id": "17ab898e.5cb24e",
 "type": "delay",
 "z": "4d244ff7.cda44",
 "name": "",
 "pauseType": "rate",
 "timeout": "5",
 "timeoutUnits": "seconds",
 "rate": "1",
 "nbRateUnits": "5",
 "rateUnits": "minute",
 "randomFirst": "1",
 "randomLast": "5",
 "randomUnits": "seconds",
 "drop": true,
 "x": 495.50006103515625,
 "y": 111.15000915527344,
 "wires": [
 [
 "d859540.d0528b"
 ]
 ]
 },
 {
 "id": "a196d223.48f058",
 "type": "dashDB in",
 "z": "4d244ff7.cda44",
 "dashDB": "",
 "service": "dashDB-if",
 "query": "SELECT CHARTDATA FROM CHARTDATA WHERE CHART = 'Wind';",
 "params": "",
 "name": "",
 "x": 695,
 "y": 189.1999969482422,
 "wires": [
 [
 "e3f01a4e.c6328"
 ]
 ]
 },
 {
 "id": "53222927.1e6128",
 "type": "function",
 "z": "4d244ff7.cda44",
 "name": "check chartdata",
 "func": "//only proceed if some chart values are already present\nif(msg.payload[0].values.length < 5) {\n return null;\n}\n\nreturn msg;",
 "outputs": 1,
 "noerr": 0,
 "x": 862.6002197265625,
 "y": 114.59998321533203,
 "wires": [
 [
 "659fc8c0.c1dbd"
 ]
 ]
 },
 {
 "id": "c586f172.035428",
 "type": "delay",
 "z": "4d244ff7.cda44",
 "name": "",
 "pauseType": "delay",
 "timeout": "5",
 "timeoutUnits": "seconds",
 "rate": "1",
 "nbRateUnits": "1",
 "rateUnits": "second",
 "randomFirst": "1",
 "randomLast": "5",
 "randomUnits": "seconds",
 "drop": false,
 "x": 529.9000457763673,
 "y": 195.99995727539067,
 "wires": [
 [
 "a196d223.48f058"
 ]
 ]
 },
 {
 "id": "e3f01a4e.c6328",
 "type": "function",
 "z": "4d244ff7.cda44",
 "name": "parse chartdata",
 "func": "msg.payload = JSON.parse(msg.payload.CHARTDATA);\nreturn msg;",
 "outputs": 1,
 "noerr": 0,
 "x": 860.4000854492188,
 "y": 184.3999481201172,
 "wires": [
 [
 "d859540.d0528b"
 ]
 ]
 },
 {
 "id": "b77c2b1d.9800b8",
 "type": "inject",
 "z": "4d244ff7.cda44",
 "name": "",
 "topic": "",
 "payload": "",
 "payloadType": "date",
 "repeat": "",
 "crontab": "",
 "once": true,
 "x": 388.2501220703125,
 "y": 198.45001983642578,
 "wires": [
 [
 "c586f172.035428"
 ]
 ]
 },
 {
 "id": "1dbc08c2.5e5707",
 "type": "function",
 "z": "4d244ff7.cda44",
 "name": "mps in kmh",
 "func": "msg.payload = Math.round(msg.payload * 3,6);\nmsg.topic = \"Wind\";\nreturn msg;",
 "outputs": 1,
 "noerr": 0,
 "x": 300.79998779296875,
 "y": 51,
 "wires": [
 [
 "c1c9bc77.aa31"
 ]
 ]
 },
 {
 "id": "c1c9bc77.aa31",
 "type": "delay",
 "z": "4d244ff7.cda44",
 "name": "",
 "pauseType": "rate",
 "timeout": "5",
 "timeoutUnits": "seconds",
 "rate": "1",
 "nbRateUnits": "5",
 "rateUnits": "minute",
 "randomFirst": "1",
 "randomLast": "5",
 "randomUnits": "seconds",
 "drop": true,
 "x": 482.29998779296875,
 "y": 70.60003662109375,
 "wires": [
 [
 "d859540.d0528b"
 ]
 ]
 },
 {
 "id": "82d4ad11.96f7a8",
 "type": "ui_group",
 "z": "",
 "name": "Wetter letzte 24 Stunden",
 "tab": "7c4a7d5b.6f0c64",
 "order": 2,
 "disp": true,
 "width": "12"
 },
 {
 "id": "7c4a7d5b.6f0c64",
 "type": "ui_tab",
 "z": "",
 "name": "Details Haus und Umgebung",
 "icon": "dashboard",
 "order": 2
 }
]
(Visited 1.131 times, 4 visits today)

Nichts mehr verpassen?

Abonniere unseren Newsletter und Du verpasst keinen Artikel mehr. Versprochen!

*Pflichtfeld

Über René Auberger 12 Artikel

René ist IT Architekt für Internet of Things, Industrie 4.0, Analytics und Cognitive Computing. In seiner Freizeit macht er Schritt für Schritt sein Haus smarter. 2017 hat René das Internet-of-Things.blog gegründet. Er schreibt auch regelmäßig auf Twitter.

Zu Renés kompletten Profil…

Ersten Kommentar schreiben

Antworten

Deine E-Mail-Adresse wird nicht veröffentlicht.


*