Ein flexibler Benachrichtigungsbereich in Node-RED Dashboard

Vorgeschichte

Als das SmartHome immer weiter wuchs, kam ich irgendwann an den Punkt, dass ich mir eine einheitliche Vorgehensweise für Benachrichtigungen überlegen musste. Viele Informationen braucht man nur, wenn sie akut sind. Diese über das ganze Dashboard zu verteilen und abzuklappern, wäre unpraktisch, denn Benachrichtigungen entstanden an immer mehr Stellen (der Versuch einer Sammlung):

  • Unwetterwarnungen
  • Meldung von technischen Problemen bei Schnittstellen
  • Meldung von technischen Problemen bei Sensoren
  • Update-Benachrichtigungen für Geräte-Firmware
  • Batterie-Meldungen von Sensoren
  • Indoor und Outdoor Temperaturwarnungen
  • Türklingel
  • Warnung bei hoher CPU Auslastung
  • Verkehrsmeldungen aus dem ÖPNV
  • Warnmeldungen zum Warmwasservorrat
  • Müll-Entsorgungsankündigungen
  • Geöffnete Türen oder Fenster (mit KWL kommt das nicht so häufig vor)
  • tägliche Niederschlagsmenge (ab einem bestimmten Grenzwert)
Ein flexibler Benachrichtigungsbereich in Node-RED Dashboard weiterlesen

POST-HTTP Request mit Daten

Da ich mich sehr dumm angestellt habe und ewig brauchte, um mit Node-RED einen HTTP Request inkl. Daten zu versenden, dokumentiere ich hier – nur für mich (weil sich bestimmt niemand so umständlich anstellt) – die funktionierende Lösung.

Der simple Aufbau, ein Function-Node und ein HTTP-Request-Node:

Im Function-Node werden die zu übermittelnden Daten in msg.payload als JSON geschrieben. Außerdem wird
msg.headers["Content-Type"] = "application/x-www-form-urlencoded";
gesetzt.

Der HTTP-Request ist dann kaum zu konfigurieren. Methode = POST eintragen, dann noch die URL und fertig.

Kaputt gespielt – Backup und Wiederherstellung

Was soll ich sagen, irgendwie habe ich mein Raspberry Pi OS (Raspbian) kaputt gespielt. Es fing damit an, dass ich nicht per VNC auf das System kam (für deCONZ). Zeitweise ging nicht mal raspi-config. Und: jedes mal ging nach ein paar Betriebsstunden die CPU Last massiv in die Höhe – ausgelöst durch einen Fehler mit oder durch kworker. Also fiel die Entscheidung: neu aufsetzen. Ein praktischer Präzedenzfall für Backup & Restore.

In meinem Setup waren drei, oder besser vier Dinge zu sichern und zu überspielen. Da ich noch einen Raspberry Pi 4 herumliegen hatte, konnte ich die Systeme parallel laufen lassen, und musste nicht ständig zwischen alt und neu wechseln, sofern mir noch Infos fehlten. Auch konnte ich so die Vollständigkeit der Daten gut prüfen.

Zum Kopieren der Daten zwischen den Raspberrys nutze ich immer WinSCP.

1. deCONZ/Phoscon

Bei Phoscon einfach über die Weboberfläche unter „Gateway“ ein Backup ziehen und an gleicher Stelle auf dem neuen System wieder einspielen. Das Einspielen des Backups habe ich jedoch erst gemacht, als die microSD-Karte mit dem neuen System auch im Raspberry Pi mit dem Conbee II steckte, um mögliche Probleme zu vermeiden.

2. InfluxDB

Hier sollte man zwingend mit den Backup & Restore Befehlen von influxd arbeiten, ein Kopieren der Datenordner führte (zumindest bei mir) nicht zum Erfolg.

Es müssen sowohl der META-STORE als auch die Datenbank(en) gesichert werden.

$ influxd backup <speicherort-fuer-backup>
$ influxd backup -database iobroker <speicherort-fuer-backup>

Das Wiederherstellen ist ähnlich leicht.

$ influxd restore -metadir /var/lib/influxdb/meta <speicherort-des-backups>
$ influxd restore -database iobroker -datadir /var/lib/influxdb/data <speicherort-des-backups>     

Anschließend muss man ggf. noch die Benutzerrechte des Ordners anpassen.

$ sudo chown -R influxdb:influxdb /var/lib/influxdb

3. ioBroker

Bei ioBroker kann man ein komplettes Backup sehr leicht über die Kommandozeile erstellen:

$ cd /opt/iobroker
$ sudo iobroker stop
$ sudo iobroker backup
$ sudo iobroker start

Das Backup liegt dann unter /opt/iobroker/backups/.

Ebenso einfach ist das Wiederherstellen. Das Backup-Archiv muss man dazu in den gleichen Ordner auf dem neuen System kopieren.

$ sudo iobroker stop 
$ sudo iobroker restore 0
$ sudo iobroker start all
$ sudo iobroker start
$ sudo iobroker upload all

(der Befehl „sudo iobroker upload all“ lädt die Webseiten aus den Ordnern „www“ und „admin“ aus den Adaptern in den ioBroker-Dateispeicher hoch)

Nun dauert es je nach Umfang der ioBroker-Installation einige Minuten, bis automatisch wieder alle Adapter installiert sind. Über die Weboberfläche – Instanzen kann man den Prozess rudimentär beobachten.

4. Node-RED

Die Flows werden beim ioBroker-Backup (3.) mitgesichert. Allerdings werden die zusätzlich installierten Node-Module (über „Palette verwalten“) beim Wiedereinspielen nicht automatisch installiert. Hier hilft ein Screenshot der Liste oder eine kurze Notiz der installierten Node-Module. Aber: zur Not lassen sich die auch anhand der Auflistung fehlender Nodes recherchieren und rekonstruieren.

Probleme

Probleme gab es eigentlich gar nicht. Einzig der ioBroker PING Adapter ändert den Objektpfad anhand des Hostnames – wählt man hier bei der neuen Installation einen anderen, muss man sämtliche Pfadangaben des PING Adapters z.B. im Node-RED korrigieren.

Nachtrag 23.06.2021

Inzwischen (?) kann man im PING Adapter einstellen, dass der Hostname nicht im Zustandsnamen verwendet werden soll. Wenn man bei der Einrichtung darauf achtet, kann man beim Backup & Restore dieses Problem umschiffen.

NodeRed UI Design

Das Design von NodeRed Dashboard braucht meiner Meinung nach noch ein wenig „Nachhilfe“. Hier die drei wichtigsten Styles meiner „Nachhilfe“, welche ich über ein ui_template Node in die <head> Section einfüge.

<style>
body {
        font-size:0.8em;
        background-color:#E6F7FF !important;
    }
md-card {
        border-bottom: 1px solid #F2F2F2;
    }
.nr-dashboard-cardpanel > p {
        margin-top: 3px !important;
        margin-bottom: 3px !important;
        min-height: 23px !important;
    }
</style>

Das Ergebnis:

UI-Performance – Diagramme von Flot

Auch wenn ich das Node-RED Dashboard sehr mag, eines ging mir gehörig auf den Keks: die Performance. Ursache dafür waren bei mir (diesmal) die Diagramme (chart), die den Ladevorgang eines Tabs teils um 10 Sekunden verlängerten. Daher bin ich auf die Diagramme von Flot umgestiegen, welche als iframe (template Node) eingebunden werden und damit asynchron laden. Das Ergebnis: Der Seitenaufbau ist schnell, das Diagramm braucht halt vielleicht noch kurz …

Um die Flot Diagramme an das Dashboard anzupassen nutze ich folgende Einstellungen:

Die Legende ist sicher je nach Anwendungsfall nötig oder nicht nötig. Auch die Höhe des Diagramms sollte je nach Art der Werte angepasst werden.

Das Ergebnis kann sich sehen lassen.

Ladevorgang läuft …
Geladen.

Die Nutzung von Flot hat noch weitere Vorteile:

  • auch nach einem Neustart von Node-RED habe ich noch „volle“ Diagramme
  • die Farben der Linien bleiben auch nach einem Neustart gleich
  • es lassen sich einfach zusätzliche Linien/Marken in die Diagramme einzeichnen
  • man kann die Kurven falls nötig leicht glätten

Wetterradar im Dashboard

Ich wollte gern ein Wetterradar im Dashboard haben. Doch an solche grafischen Darstellungen kommt man ja nicht immer ohne weiteres (kostenlos) ran – und der DWD bietet nur eine Drei-Länder-Darstellung.

Also musste etwas CSS her, um mittels eines Node-RED template Node nur einen Bildausschnitt darzustellen.

<div style="position:absolute;width: 296px;height: 250px; background-color: #BEBEBE;overflow: hidden;">
<img src="https://www.dwd.de/DWD/wetter/radar/radfilm_thu_akt.gif" style="position: absolute;top:-250px;	overflow: hidden;" />
</div>

Ich definiere einen Container mit overflow:hidden; sodass alles was nicht reinpasst auch nicht angezeigt wird. Mittels top und left kann ich das Bild entsprechend vertikal und horizontal verschieben.

Update 2021

Inzwischen bin ich auf das Radar eines anderen Wetterdienstes umgestiegen. Da dieser bei der Fremd-Verwendung nicht so freigiebig ist, wie der DWD, erwähne ich dessen Namen hier nicht und sagen nur so viel: mit einem simuliertem Website-Zugriff (http request-Node) und der Übermittlung des Cookies und des richtigen Referer-Headers beim Abruf der Radar-Grafik ist so manche unwirksame Sperre überwindbar. Die Grafik speichere ich dabei lokal zwischen (httpStatic).

Ein change-Node zwischen simuliertem Website-Zugriff und Grafik-Abruf

Warum ich meine Shelly 2.5 nicht mehr mit MQTT steuere

Bisher habe ich die Shelly 2.5, welche ich zur Rolladensteuerung einsetze, über iobroker + MQTT gesteuert. Ich nutze bei ihnen die original Firmware, weil ich die Positionskontrolle, die Kalibierierung und die Hindernis-Erkennung sehr schätze. Darum steuere ich jetzt nicht mehr per MQTT:

Scheinbar ein Firmware Bug

Disclaimer: dieser Artikel ist schon etwas älter. Ob es ein Bug war und ob es diesen potentiellen Bug noch gibt, weiß ich nicht. Ich komme mit der Nicht-MQTT Steuerung sehr gut klar, daher habe ich dies beibehalten.

Ab und zu kommt es bei meinen Shellys dazu, dass die WLAN Verbindung abbricht. Dies liegt einfach an der Reichweite des WLANs, die in mancher Wand im Grenzbereich liegt. Dies ist normalerweise kein Problem, da sich die Shellys recht zuverlässig wieder mit dem Router verbinden. Doch manchmal öffnen sich nach der Wiederverbindung wie von Geisterhand die Rollos – also sie fahren unvermittelt nach oben. Viele Tests später ist meine Theorie, dass dies am MQTT liegt, und es hier scheinbar einen Bug in der Firmware gibt.

MQTT + iobroker Objekte

Zweiter Grund: Von NodeRED kommt die Logik meines SmartHomes. So auch die Rolladensteuerung. Über das Setzen/Ändern von iobroker Objekten, sendet man die Befehle per MQTT. Ein Problem: hat das Objekt bereits den gleichen Wert, wie den, den man setzen will, wird der Befehl nicht übertragen. Wurde also zum Beispiel zuletzt das Rollo auf 60% gestellt, das Rollo danach per Wandtaster wieder hochgefahren, kann man es nicht erneut auf 60% einstellen, weil das Befehls-Objekt noch auf 60 steht. Man muss also umständlich auslesen, welcher Wert gerade im Objekt steht, und ggf. z.B. 61 übermitteln.

Darum nutze ich jetzt die HTTP API

Umgestiegen bin ich auf die HTTP API. Über sie rufe ich peridoisch den aktuellen Status ab (tags öfter als nachts) und sende Befehle. Die Vorteile:

  • Ich habe einen Adapter weniger im iobroker, spart Arbeitsspeicher.
  • Ich umgehe den möglichen Firmware Bug und habe (bisher) keine geisterhaft öffnenden Rollos mehr.
  • Ich kann Befehle senden, ohne diese vorher mit dem aktuellen Status abgleichen zu müssen.
  • Ich bekomme über den HTTP StatusCode (200) direktes Feedback, ob ein Befehl angekommen ist.

So mache ich es

Zum einen habe ich in Node-RED einen HTTP in-Node erstellt, bei dem sich die Shellys über die I/O URL Actions melden, wenn sie stoppen – also die Rollos eine neue Position eingenommen haben. Der dann angestoßene Abruf der Position hält den Wert im iobroker aktuell.

Zum anderen habe ich einen Subflow erstellt, welcher die Befehle an die Shellys übermitteln. Er benötigt als Argument die IP des betroffenen Shellys und einen Befehl. Dabei verarbeitet der Subflow sowohl Open, Close und Stop als Textbefehle als auch Integer-Werte – zwischen 0 und 100 – für die Position des Rollos in „Prozent geöffnet“. (100% = ganz offen)

Hier das Flow:

Sommerzeit/Winterzeit

Noch gibt es sie, die Sommerzeit und die Winterzeit. Gerade bei der Rolladensteuerung habe ich deutliche Auswirkungen der Zeitumstellung festgestellt. Daher musste eine Sommerzeit/Winterzeit-Unterscheidung her.

Der Funktionsblock muss auf zwei Ausgänge gestellt werden. Oben ist die Winterzeit, unten die Sommerzeit. Unter msg.yeartime ist auch die entsprechende Zeit hinterlegt.

var today = new Date();
var monat = today.getMonth()+1;
var tag = today.getDate();
var thisYearS;
var thisYearW;
var thisYearSDays = 0;
var thisYearWDays = 0;

function isLeapYear( year ) {
	if ( ( year % 4 === 0 && year % 100 !== 0 ) || year % 400 === 0 ) return true;
	else return false;
}
function calcLeapYear( year, days ) {
	if ( isLeapYear( year ) ) return ++days;
	else return days;
}
function subToSunday( myDate, year, days, prevMonthDays ) {
	for ( i = myDate.getDay() ; i > 0 ; i-- ) {
		days--;
	}
	days -= prevMonthDays;
	days = isLeapYear( year ) ? --days : days;
	return days;
}

	year = today.getFullYear();
	thisYearS = new Date( year,  3, 0, 0, 0, 0 );
	thisYearW = new Date( year, 10, 0, 0, 0, 0 );
	thisYearSDays = nextYearSDays = 90;
	thisYearWDays = nextYearWDays = 304;

	thisYearSDays = calcLeapYear( year, thisYearSDays );
	thisYearWDays = calcLeapYear( year, thisYearWDays );

	thisYearSDays = subToSunday( thisYearS, year, thisYearSDays, 59 );
	thisYearWDays = subToSunday( thisYearW, year, thisYearWDays, 273 );
	
	node.status({fill:"grey",shape:"ring",text:thisYearSDays + ".03." + year + " - " + thisYearWDays + ".10." + year});
	
	
if((monat < 4 && tag < thisYearSDays) || monat < 3 || monat > 10 || (monat > 9 && tag > thisYearWDays)) { // Winterzeit
            msg.yeartime = "Winterzeit";
            return [msg, null];
}
else {  // Sommerzeit 
        msg.yeartime = "Sommerzeit";
        return [null, msg];
}

Das JavaScript basiert auf dem Script von https://www.kostenlose-javascripts.de/javascripts/datum-und-uhrzeit/zeitumstellung/

Fehlersuche in NodeRED

Macht man seine ersten – oder auch schon etwas intensivere – Schritte im NodeRED, geht nicht alles sofort glatt. Bei mir zumindest.

Häufige Fehler bei mir:

1. Ich baue Endlosschleifen – und nichts geht mehr.

Gerade im Zusammenhang mit NodeRED Dashboard ist es mir immer wieder passiert, dass ich Endlosschleifen gebaut habe.

Allein dieser Aufbau birgt schon das Risiko einer Endlosschleife.

Abhilfe ist einfach. Beim Laden des Variablenwertes den Modus auf „block unless value changes“ setzen.

An anderer Stelle kann auch der report-by-exception – Node (rbe) hilfreich sein. Hier wird jede msg aufgehalten, bis der payload sich unterscheidet.

2. Ich setze statt change ein switch

Eigentlich sind switch und change sehr leicht auseinanderzuhalten. Nur liegen sie in der Palette halt direkt nebeneinander, haben ähnliche Symbole, die gleich Farbe, und möchte man ein Change (ich ändere den Wert einer Variable) einsetzen und setzt stattdessen ein Switch (if then else) fällt auch das Properties Menü kaum negativ auf, sofern man nur kurz die payload verändern will. Mir schon mehrmals passiert – hier lohnt sich ein genauer Blick.

3. Datentypen

Im ioBroker-Adapter NodeRED ist standardmäßig eingestellt, dass alle aus dem ioBroker gelesen Werte in String (Text) umgewandelt werden.

Versucht man dann mit den Werten zu arbeiten, zu rechnen, true und false zu vergleichen, merkt man schnell, entweder muss die Option im Adapter umgestellt werden, oder man muss mit Datentypen jonglieren. In diesem Fall wird der JavaScript function-Node dein Freund.

Zum Beispiel zum Umwandeln einer Zahl (string) in eine „wirkliche“ Zahl (float).

msg.payload=parseFloat(msg.payload);

Wird sicher fortgesetzt …

Shelly & Tasmota OTA Update anstoßen

Ab einer gewissen Zahl von WLAN Komponenten, wird es zur Herausforderung, die Firmware der Geräte auf dem aktuellen Stand zu halten. Zu diesem Zweck habe ich mir ein Tab in meinem Node-RED Dashboard eingerichtet.

Das grüne Herz zeigt, dass der Shelly online ist.

Die Abkürzung OTA steht für „Over The Air“, auf deutsch „über die Luft“. Es handelt sich dabei um eine drahtlose Methode, Updates auf Geräte zu übertragen.

Tasmota Firmware

Die aktuelle Tasmota Version, lässt sich durch einen automatisierten Abruf des GitHub-Feeds auslesen:

https://github.com/arendst/Tasmota/releases.atom

[{"id":"83b36f5b.0b8b","type":"xml","z":"c083a756.a72f18","name":"","property":"payload","attr":"","chr":"","x":370,"y":660,"wires":[["9fef7d11.d2a7c"]]},{"id":"390fe0f3.da48","type":"http request","z":"c083a756.a72f18","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"https://github.com/arendst/Tasmota/releases.atom","tls":"","persist":false,"proxy":"","authType":"","x":710,"y":620,"wires":[["83b36f5b.0b8b"]]},{"id":"3a9bb870.4e0158","type":"inject","z":"c083a756.a72f18","name":"TASMOTA VERSION","topic":"","payload":"","payloadType":"date","repeat":"21600","crontab":"","once":true,"onceDelay":0.1,"x":400,"y":620,"wires":[["390fe0f3.da48"]]},{"id":"9fef7d11.d2a7c","type":"function","z":"c083a756.a72f18","name":"HIER Feed Version filtern","func":"msg.version = msg.payload[\"feed\"][\"entry\"][0][\"title\"][0];\nnode.status({text:msg.version});\nmsg.version = msg.version.replace(\"Tasmota v\",\"\");\nmsg.version = msg.version.replace(\"Tasmota\",\"\");\nmsg.version = msg.version.substring(0,6)\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":660,"wires":[["e8c6fe5.7a082"]]},{"id":"e8c6fe5.7a082","type":"change","z":"c083a756.a72f18","name":"","rules":[{"t":"set","p":"tasmotaversion","pt":"global","to":"version","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":660,"wires":[["18e34f71.edaca1"]]}]

Die Tasmota Version steht immer aktuell in den MQTT Daten im ioBroker. Alternativ lässt sie sich über das Tasmota Command „Status“ auslesen.

Um bei einem Tasmota Geräte den Firmware-Update-Vorgang zu starten, reicht ein simpler HTTP-Aufruf von:

http://192.168.xx.xx/cm?cmnd=upgrade%201&user=BENUTZERNAME&password=PASSWORT

Auch die OtaUrl lässt sich per simplem HTTP-Aufruf anpassen. Leerzeichen oder Sonderzeichen in der OtaURL müssen durch ihre jeweiligen ASCII-Hex-Codes ersetzt werden. Dem Hex-Code muss ein Prozentzeichen % vorangestellt werden (z. B. %3B).

http://192.168.xx.xx/cm?cmnd=OtaUrl%20URL&user=BENUTZERNAME&password=PASSWORT

Mehr Infos zu Tasmota Commands

Shelly.cloud Firmware

Bei der Original Shelly Firmware, ist es ähnlich leicht, aber ein wenig anders. Die aktuell zur Verfügung stehende Firmware Version lese ich aus der JSON-Datei der offiziellen API:

https://api.shelly.cloud/files/firmware

Hier findet man auch die Download-Urls zu den Firmwares für die Shelly Geräte. Die aktuelle Firmware, hier für den Shelly 2.5, liegt zum Beispiel hier:

https://repo.shelly.cloud/firmware/SHSW-25_build.zip

Die Shellys verraten mit folgendem Kommando ihre aktuelle Version:

http://BENUTZERNAME:PASSWORT@192.168.xx.xx/status/

Den Update-Vorgang kann man mit folgendem Kommando starten:

http://BENUTZERNAME:PASSWORT@192.168.xx.xx/ota?update=1

Möchte man eine andere Shelly-Firmware-Version flashen, lässt sich hier auch eine Firmware-URL übergeben. Soweit ich weiß ist das Flashen auf eine alternative Firmware (Tasmota) so jedoch nicht möglich.

http://BENUTZERNAME:PASSWORT@192.168.xx.xx/ota?url=https://repo.shelly.cloud/firmware/SHSW-25_build.zip

Mehr Infos zur Common HTTP API der Shellys

Update: 25.10.2021

Seit Version 10.0.0 von Tasmota braucht man natürlich beim Filtern der Feed Version natürlich ein substring(0,6) – etwas unflexibel, sollte ich mal ändern.