Node-RED Dashboard – Dynamische Anzeige von Daten

In diesem Beitrag möchte ich Dir zeigen, wie du Daten aus einer beliebigen Anzahl an Quellen auf dem Node-RED Dashboard darstellen kannst. Die Idee für diesen Beitrag kam durch einen Kommentar auf facebook zustande.
Falls Du auch ein Thema hast, zu dem Du gerne etwas lesen würdest, irgendwelche Fragen zu einem Beitrag von uns hast oder ein interessantes Projekt mithilfe unseres Blogs realisiert hast, schreibe uns gerne auf facebook oder per eMail ([email protected]).

Ausgangslage

In diesem Beitrag wird davon ausgegangen, dass mehrere ESPs eine Temperatur messen und per HTTP-Request an eine Node-RED-Instanz schicken. Jeder ESP bekommt dabei einen eigenen Namen. Ich gehe davon aus, dass jeder ESP in einem eigenen Raum steht und in diesem Raum die Temperatur misst.

Node-RED Dashboard: Dynamisches UI - Schema der Ausgangslage
Schematische Ausgangslage

Alle ESPs schicken ihre Daten an den selben Endpunkt bzw. die selbe “http in”-Node. Eigentlich sollte man zum versenden von Daten die HTTP-Methode POST verwenden. Für diesen Beitrag werde ich allerdings die HTTP-Methode GET verwenden, weil diese einfacher im normalen Browser getestet werden kann.

Node-RED Dashboard: Dynamisches UI - Flow zum Empfangen der Daten
Einfacher Flow zum empfangen von Daten per HTTP

Der Flow zum Empfangen der Daten ist sehr einfach aufgebaut. Es gibt eine “http in”-Node mit der Methode GET und der URL “/data”. An dieser hängt eine Debug- und eine “http response”-Node.

Node-RED Dashboard: Dynamisches UI - Empfangen der Daten im Debug-Log
Über den HTTP-Request empfangene Daten

Wenn Du im Browser jetzt bspw. die URL http://<Adresse von Node-RED>/data?name=Wohnzimmer&temperature=15.0 aufrufst, wird im Debug-Fenster von Node-RED ein Objekt mit den Daten aus der URL angezeigt.

Du kannst an den Anführungszeichen erkennen, dass die Temperatur eine Zeichenkette (String) ist. Für diesen Beitrag ist das nicht wichtig, allerdings solltest Du es im Hinterkopf behalten, falls du noch mit die Daten für Berechnungen verwenden möchtest.

Sammeln der Daten

Da die Daten der ESPs nicht alle mit einem Request kommen, sondern nach und nach von allen ESPs gesendet werden, müssen die Daten zur Anzeige irgendwo zwischengespeichert werden. Hierfür bietet sich der flow-Kontext an. In einfachen Fällen kann man flow-Variablen per change-Node setzen. Die change-Node lässt sich für diesen Beitrag leider nicht nutzen, da der hier vorgestellte Anwendungsfall etwas komplexer ist.

Es ist allerdings auch möglich mit einer function-Node und etwas JavaScript eine flow-Variable zu setzen. Es kann sein, dass es auch andere Möglichkeiten gibt, da der Quellcode allerdings nicht sehr lang ist, denke ich das man mit etwas JavaScript nichts falsch macht. Erstelle zunächst eine function-Node und verbinde sie mit der “http in”-Node. Anschließend kannst du den folgenden Quellcode in den Code-Editor im Tab “Funktion” kopieren:

// Benötigte Daten aus payload auslesen
const name = msg.payload.name;
const temperature = msg.payload.temperature;

// wenn es keinen dataArray Eintrag im Flow-Context gibt
// wird er mit einem leeren Array erstellt
if (!flow.get('dataArray')) {
    flow.set('dataArray', []);
}

// Wert aus dem dataArray Eintrag im Flow-Context in eine Variable übertragen
const dataArray = flow.get('dataArray');

// Im Array nach einem Eintrag suchen, bei dem der Name
// mit dem aus unserem Request übereinstimmt
const indexOfCurrentData = dataArray.findIndex((data) => data.name === name);

// Ein index von -1 bedeutet, dass kein Eintrag gefunden wurde
if (indexOfCurrentData === -1) {
    // Es wird ein neuer Eintrag im Array erstellt,
    // der alle benötigten Daten enthält
    dataArray.push({
        name,
        temperature,
    });
} else {
    // Bei einem bereits bestehenden Eintrag: Aktualisierung der Temperatur
    dataArray[indexOfCurrentData].temperature = temperature;
}

// Speichern des Arrays (nicht unbedingt erforderlich,
// allerdings wird die Intention des Codes so eindeutiger)
flow.set('dataArray', dataArray);

// Der payload der Nachricht wird mit der Liste aller Daten ersetzt
// Dies ermöglicht eine einfachere Weiterverarbeitung der Daten
msg.payload = dataArray;

return msg;
Code-Sprache: JavaScript (javascript)

Ich werde an dieser Stelle nicht nochmal den Quellcode durchgehen und erklären, da ich Kommentare im Quellcode einfacher zu verstehen finde.

Es könnte sein, dass Du mit sehr alten Versionen von Node-RED (bzw. dem im Hintergrund verwendeten Node.JS) Probleme bei der Ausführung des Quellcodes hast. Die Version muss zwar mehrere Jahre alt sein, aber wenn das Smarthome funktioniert, möchte man es sich ja evtl. nicht durch ein Update direkt wieder zerschießen 😉

In diesem Fall müsstest du alle const durch var ersetzen und die Zeile 16 durch
var indexOfCurrentData = dataArray.findIndex(function (data) { return data.name === name; });

Überprüfung der Sammel-Logik

Zum Überprüfen der hinzugefügten Logik zum sammeln der Daten habe ich eine Debug-Node mit der function-Node verbunden. Da in Zeile 37 des Quellcodes das payload des msg-Objektes mit der Liste an Daten überschrieben wird lässt sich die Logik so einfach über das Debug-Fenster von Node-RED überprüfen.

Node-RED Dashboard: Dynamisches UI - Überprüfung der Sammlungslogik
Direkt nach der function-Node zum Sammeln der Daten wurde eine Debug-Node hinzugefügt

Wenn Du jetzt die oben angegebene URL (http://<Adresse von Node-RED>/data?name=Wohnzimmer&temperature=15.0) nochmal aufrufst, wird im Debug-Fenster von Node-RED (neben dem Log vom HTTP-Request) ein Array mit einem Eintrag angezeigt. Klappe das Array und das darin liegende Objekte auf. Du siehst jetzt den in der URL angegeben Namen und die Temperatur.

Node-RED Dashboard: Dynamisches UI - Einzelner Eintrag im flow-Context
Der erste Eintrag im flow-Context

Rufe jetzt die URL mit den Daten für ein anderes Zimmer (bspw. 20.0° im Badezimmer) auf. Die URL dafür wäre http://<Adresse von Node-RED>/data?name=Badezimmer&temperature=20.0 . Es wird im Array ein zweites Objekt angelegt mit der Temperatur für das Badezimmer angelegt.

Node-RED Dashboard: Dynamisches UI - Zweiter Eintrag im flow-Context
Der Eintrag für das Badezimmer ist jetzt auch im flow-Context

Vielleicht ist jetzt auch jemanden aufgefallen, dass die Temperatur im Wohnzimmer etwas gering ist und hat die Heizung aufgedreht. Mit der URL http://<Adresse von Node-RED>:1880/data?name=Wohnzimmer&temperature=19.0 bringen wir das Wohnzimmer auf 19.0°C. In der Debug-Konsole kannst Du sehen, dass immer noch nur 2 Einträge in dem Array vorhanden sind. Die Temperatur im Wohnzimmer hat sich allerdings auf 19.0 °C erhöht.

Node-RED Dashboard: Dynamisches UI - Aktualisierter Eintrag für das Wohnzimmer
Die Temperatur im Wohnzimmer wurde aktualisiert

Ausgabe der Daten auf dem Node-RED Dashboard

Leider ist es nicht ohne Weiteres möglich dynamisch Elemente zum Node-RED Dashboard hinzuzufügen. Allerdings lassen sich mit der template-Node eigene Elemente mittels HTML zum Node-RED Dashboard hinzufügen.

Eine Möglichkeit wäre jetzt einfach das Array in einen String mit HTML umzuwandeln. In diesem Beitrag möchte ich allerdings eine andere Möglichkeit vorstellen. Das Node-RED Dashboard setzt intern eine Bibliothek mit dem Namen Angular.JS ein. Über diese Bibliothek (bzw. genau genommen das Framework) lassen sich leicht HTML-Elemente (aus Objekten und Arrays) generieren. Auch wenn Angular.JS inzwischen etwas in die Jahre gekommen ist und nicht mehr dem neusten Stand der Technik entspricht, sollte es einstiegsfreundlicher sein als eine manuelle Serialisierung des Arrays.

Node-RED Dashboard: Dynamisches UI - Kompletter Flow
Kompletter Flow mit template-Node

Füge eine template-Node zu dem Flow hinzu und verbinde den Eingang der template-Node mit dem Ausgang der function-Node. Die template-Node kannst du, wie jede andere Node-RED Dashboard Node, einem Tab und einer Gruppe zuordnen. Füge im Feld Vorlage den folgenden Code ein:

<table>
    <tr ng-repeat="data in msg.payload">
        <td>{{data.name}}</td>
        <td>{{data.temperature}}</td>
    </tr>
</table>
Code-Sprache: HTML, XML (xml)

Der Code generiert eine Tabelle und für jeden Eintrag im msg.payload-Array eine Zeile in der Tabelle. Mit dem Attribut ng-repeat im tr-Element wird ausgedrückt, dass ein Array durchlaufen werden soll. Mit dem Wert "data in msg.payload" wird dieser Vorgang genauer beschrieben. So soll jedes Element des Arrays msg.payload in eine Variable mit dem Namen data geschrieben werden.
Das gesamte tr-Element wird hierbei als Vorlage verwendet. In der ersten Spalte der Tabelle (erstes td-Element) wird mit dem Ausdruck {{data.name}} der Name des Gerätes geschrieben. In die andere Spalte wird entsprechend die Temperatur geschrieben.

Tipp: Wenn Du andere Teile des Nachrichten Objektes als Array durchlaufen möchtest, kannst Du einfach in Zeile 2 den Teil "data in msg.payload" durch "data in msg.<Feldname>" ersetzen.

Im Node-RED Dashboard sieht das Ganze dann so aus:

Node-RED Dashboard: Dynamisches UI - Einfache Darstellung im Dashboard
Einfache Darstellung im Dashboard

Mit einer kleinen Änderung können wir die Anzeige der Temperatur jetzt noch ein wenig verbessern. Vielleicht wollen wir noch die Einheit der Temperatur (°C) hinzufügen und lieber ein Komma als Trennzeichen verwenden. Hierfür muss nur die vierte Zeile im Quellcode der template-Node angepasst werden:

<table>
    <tr ng-repeat="data in msg.payload">
        <td>{{data.name}}</td>
        <td>{{data.temperature.replace('.', ',')}} °C</td>
    </tr>
</table>
Code-Sprache: HTML, XML (xml)

Ich finde das hierdurch das Ergebnis direkt etwas besser aussieht.

Node-RED Dashboard: Dynamisches UI - Verbesserte Darstellung im Dashboard
Darstellung mit Komma als Trennzeichen und °C als Einheit der Temperatur

Dinge, die du beachten solltest

Prinzipiell kannst Du jetzt beliebig viele ESPs mit unterschiedlichen Namen hinzufügen ohne irgendwas an deiner Node-RED-Instanz ändern zu müssen. Aufgrund der Programmierung würde ich Dir allerdings nicht empfehlen eine sehr hohe Anzahl an Geräten so darzustellen. Je nach verwendeter Hardware sollten allerdings 500 Geräte ohne Probleme möglich sein.

Des Weiteren solltest du (gerade bei vielen Geräten) die Werte nicht zu oft aktualisieren. Wenn die ESPs alle paar Sekunden ihre Daten senden sollte es keine Probleme geben. Jede Sekunde könnte aber bei mehreren Geräten schon zu Problemen mit der Performance von Node-RED führen.

Node-RED Dashboard: Dynamisches UI - Kontextdaten Fenster öffnen
Anzeige der Kontextdaten öffnen

Wenn Du ein Gerät löschen möchtest, kannst Du am besten das gesamte Array aus dem flow-Kontext löschen. Die noch aktiven Geräte tragen ihre Daten ja automatisch wieder ein. Dies geht am einfachsten über das Kontextdaten-Fenster, welches du auf der rechten Seite über das Dreieck aufrufen kannst.

Vermutlich werden Dir im Bereich Flow keine Daten angezeigt. Nach einem Klick auf den Refresh-Button sollte dir der Eintrag dataArray angezeigt werden. In diesem werden alle Geräte die Daten an Node-RED senden aufgelistet. Mit einem Klick auf den Papierkorb löschst du die Daten. Wenn sich die ESPs das nächste mal bei Node-RED melden wird diese Liste wieder neu angelegt.

Geräte mit Umlaute oder Leerzeichen im Namen könnten zu Problemen führen, da diese Zeichen in der URL nicht erlaubt sind. Diese Zeichen müssen vorher URL-Encoded werden. Dafür kannst bspw. das folgende Tool oder einen beliebigen Online-Konverter nehmen. Die URL-Kodierung wird von Node-RED automatisch rückgängig gemacht, Du musst also keine Anpassungen am Node-RED Flow machen um Umlaute und Leerzeichen zu unterstützen.

Lade Widget ...

Wir hoffen, dass wir Dir dabei helfen konnten ein dynamisches Dashboard mit Node-RED Dashboard zu generieren. Neben der Ansicht als Tabelle ist es natürlich auch möglich komplett eigene Designs mit HTML, Angular.JS und CSS umzusetzen. Gerade in Verbindung mit CSS kann man sehr schicke Designs realisieren. Experimentiere einfach ein wenig mit den Möglichkeiten rum.

Dieser Beitrag benutzt Bilder von pixabay. Die verwendeten Bilder wurden von den Benutzern Comfreak, 1117826 (inaktiv) und OpenIcons kostenlos bereitgestellt.