MQTT Watchdog in Node-RED: Die Verbindung zum ioBroker absichern

Bastelecke Smarthome > ioBroker und Node-RED > MQTT Watchdog in Node-RED

Wer Node-RED über MQTT mit ioBroker verbindet, wird früher oder später über einen bekannten Bug stolpern: Der MQTT-Adapter friert ein und bleibt im Status Connecting hängen. Die Ursache ist meist eine kurzzeitige Netzunterbrechung. Der Adapter verliert die Verbindung und schafft es nicht, sich neu zu verbinden.

Um den Adapter in Node-RED neu zu starten, genügt es wenn wir in einem Flow eine minimale Änderung machen. Hier würde es schon reichen einen Node um ein Feld zur Seite zu verschieben. Hauptsache Node-RED hat irgendeine Änderung registriert. Danach klicken wir auf Deploy und damit starten wir alle Flows inklusive dem MQTT-Adapter neu.

Wenn wir Leistungsdaten summieren, wie beispielsweise die aktuelle Leistung der PV-Anlage, dann ist es ärgerlich und nervig wenn wir plötzlich bei hellem Sonnenschein null Watt angezeigt bekommen. Bei zeitkritischer Steuerung ist es inakzeptabel. Wir starten den Adapter neu und alles läuft wieder.

Doch es geht auch automatisch. Und insbesondere dann, wenn wir zeitkritische Steuerungsaufgaben in Node-RED programmieren wollen, ist eine ausfallsichere Verbindung unverzichtbar.

MQTT-Verbindung in Node-RED aktiv überwachen und automatisch neu starten

Zunächst müssen wir überprüfen ob die Verbindung noch besteht. Leider bietet Node-RED weder direkte Möglichkeiten den MQTT-Node zu überwachen, noch eine Steuerungs-API, über welche Reconnect-Befehle abgesendet werden können. Daher müssen wir hier ein paar kleine Tricks anwenden.

Um zu prüfen ob die Verbindung noch besteht, überwachen wir die verstrichene Zeit, seit Empfang der letzten MQTT-Nachricht.

Je nach Größe unserer Installation würde es hier schon ausreichen wenn wir einfach alles, was über MQTT rein kommt, auf unsere Update Zeitstempel-Funktion weiterleiten.

Um das ganze Ressourcenschonender zu gestalten, macht es jedoch Sinn sich einen Wert herauszusuchen, welcher in festen Intervallen aktualisiert wird, beispielsweise irgendein Zählerstand, etc.

Möchte man sich nicht auf die Funktion irgendeines externen Gerätes verlassen, welches streng genommen auch wieder eine Fehlerquelle darstellt, können wir uns auch im ioBroker mit Blockly ein Script basteln, welches uns im Sekundentakt einen Zeitstempel in ein Objekt schreibt.

Eben dieses Objekt, welches vom ioBroker intern im Sekundentakt aktualisiert wird, können wir nun mit dem MQTT-Node abonnieren.

Dieser feuert von nun an immer den aktuellen Wert weiter an den nächsten Funktions-Node Update Zeitstempel.

Für bessere Lesbarkeit, sowie der Möglichkeit den Code zu kopieren, werde ich ab sofort nur noch den Code für die Funktions-Nodes schreiben. Wie wir einen Funktions-Node in unseren Flow bekommen und wo der Code dann rein muss, wiederholt sich ständig: Funktions-Node von der linken Seitenleiste in den Flow ziehen – Bearbeiten mit Doppelklick – Im Reiter Funktion den Programmcode eingeben – ggf. Namen vergeben und auf Fertig klicken.

Hier also der Code, ready for copy and paste:

flow.set("lastMqttMsg", Date.now());
return null;

Dieser schreibt beim Eintreffen einer Nachricht, vom vorgeschalteten MQTT-Node, die aktuelle Systemzeit von Node-RED in den Flow-Speicher, mit dem Schlüsselwort lastMqttMsg.

Nun müssen wir in regelmäßigen Abständen prüfen, wieviel Zeit seit Eintreffen der letzten Nachricht verstrichen ist.

Im MQTT prüfen-Node ist ein Timeout von 60 Sekunden eingestellt. Diese Zeit können wir natürlich nach Belieben verändern.

Der Switch-Node prüft ob die empfangene Antwort true ist.

Wenn ja, dann ruft der HTTP-Request GET /flows alle Flows von Node-RED ab. Anschließend wird alles unverändert per HTTP-Request POST /flows wieder geschrieben. Das kommt einem Deploy gleich, woraufhin alle Flows, inklusive der MQTT-Verbindung, zurückgesetzt und neu gestartet werden.

Damit du nicht jeden Node einzeln hinzufügen und einstellen musst, hier der gesamte Code, fertig zum Import:

[
{
"id": "mqtt-watchdog-in",
"type": "inject",
"z": "1cb07c1b73866d80",
"name": "Alle 60 Sekunden",
"props": [],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "10",
"topic": "",
"x": 150,
"y": 160,
"wires": [
[
"check-mqtt-alive"
]
]
},
{
"id": "check-mqtt-alive",
"type": "function",
"z": "1cb07c1b73866d80",
"name": "MQTT prüfen",
"func": "const last = flow.get(\"lastMqttMsg\") || 0;\nconst now = Date.now();\n\n// Zeit in ms seit letzter Nachricht\nconst delta = now - last;\n\n// Wenn länger als 60 Sekunden keine Nachricht → Trigger Restart\nif (delta > 60 * 1000) {\n return { payload: true };\n} else {\n return { payload: false };\n}",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 160,
"wires": [
[
"mqtt-conditional-restart"
]
]
},
{
"id": "mqtt-conditional-restart",
"type": "switch",
"z": "1cb07c1b73866d80",
"name": "MQTT down?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 560,
"y": 160,
"wires": [
[
"d1a2b3c4d5e6f7g8"
]
]
},
{
"id": "d1a2b3c4d5e6f7g8",
"type": "http request",
"z": "1cb07c1b73866d80",
"name": "GET /flows",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "http://localhost:1880/flows",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 770,
"y": 160,
"wires": [
[
"aabbccddeeff0011"
]
]
},
{
"id": "aabbccddeeff0011",
"type": "http request",
"z": "1cb07c1b73866d80",
"name": "POST /flows",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "http://localhost:1880/flows",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [
{
"keyType": "other",
"keyValue": "Content-Type",
"valueType": "other",
"valueValue": "application/json"
}
],
"x": 970,
"y": 160,
"wires": [
[]
]
}
]

Hinzufügen kannst du das ganze mit einem Rechtsklick in deinen Flow Einfügen Import.

Zum Abschluss klicken wir nochmal oben rechts auf Übernahme (deploy).

Nun überprüft Node-RED in regelmäßigen 60-Sekunden-Abständen ob die letzte MQTT-Nachricht länger als 60 Sekunden her ist. Wenn ja, werden alle Flows, inklusive der eingefrorenen MQTT-Verbindung, neu gestartet.

MQTT-Adapter im ioBroker überwachen und automatisch neu starten

Auch der MQTT-Adapter im ioBroker selbst kann einfrieren – dann hilft auch ein Reconnect von Node-RED nichts mehr. Deshalb braucht es ein zweites Monitoring in Gegenrichtung.

Hierfür senden wir nun umgekehrt einen Zeitstempel an unseren ioBroker zurück:

Per Injekt-Node feuern wir im Sekundentakt eine Nachricht (msg.) an den nachgeschalteten MQTT-Node. Diese beinhaltet als Titel (topic) den Speicherort im ioBroker und als Inhalt die aktuelle Systemzeit in Millisekunden.

Im MQTT-Node lassen wir Topic leer. Dieser wird dann aus unserer zuvor definierten Eigenschaft msg.topic übernommen.

Auswertung im ioBroker

Nun machen wir das gleich, was wir zuvor in Node-RED gemacht haben, auch im ioBroker.

var timeout, timestamp, delta;

// Funktion welche die Sekunden seit Tagesanfang zurück gibt
async function time() {
  return (() => { const v = new Date(); return v.getHours() * 3600 + v.getMinutes() * 60 + v.getSeconds(); })();
}

// Setze das Timeout auf 60 Sekunden
timeout = 60;

// Zeitstempel bei Nachrichtenempfang setzen
on({ id: 'mqtt.2.heartbeat' /* mqtt/2/heartbeat */, change: 'ne' }, async (obj) => {
  let value = obj.state.val;
  let oldValue = obj.oldState.val;
  timestamp = await time();
});

// Zeitstempel um 0 Uhr setzen
// sonst wäre bei Tageswechsel Abweichung zu groß
schedule("0 0 * * *", async () => {
  timestamp = await time();
});

// Bei Abweichung > Timeout, Adapter neu starten
schedule("* * * * *", async () => {
  delta = await time() - timestamp;
  if (delta > timeout) {
    await restartInstanceAsync('mqtt.2');
    timestamp = await time();
  }
});

Hinweis: Passe den Objektpfad mqtt.2.heartbeat an deinen MQTT-Adapter und das eingestellte Topic an.

Ob du das ganze in Blockly zusammenbaust, oder einfach den Java-Code in ein Script übernimmst, bleibt dir überlassen. Jedoch kannst du später nur in Blockly weiter bearbeiten, wenn es auch darin gebaut wurde. Script bleibt Script und kann später auch nur im Texteditor geändert und erweitert werden.

Fazit

Die Kombination aus Node-RED und ioBroker bietet enorme Möglichkeiten für eine flexible und leistungsfähige Smart-Home-Logik – stößt aber im Zusammenspiel mit dem MQTT-Protokoll an eine kritische Schwachstelle: Die Anfälligkeit des MQTT-Adapters gegenüber Verbindungsabbrüchen. Weder Node-RED noch ioBroker stellen dafür von Haus aus eine robuste Selbstheilungsfunktion bereit.

Mit dem hier gezeigten zweistufigen Watchdog-Mechanismus kannst du dieses Problem effektiv umgehen. Node-RED überwacht dabei den eingehenden MQTT-Datenstrom und sorgt bei Verbindungsverlust für ein automatisiertes Re-Deploy der Flows, wodurch der MQTT-Client gezwungen wird, sich neu zu verbinden. Umgekehrt überwacht ioBroker den Rückkanal und setzt bei ausbleibendem Zeitstempel den MQTT-Adapter im ioBroker selbst zurück.

Damit erreichst du eine deutlich höhere Systemstabilität, insbesondere bei zeitkritischen Automatisierungen, die auf Echtzeitdaten angewiesen sind. Der Ansatz ist vollständig lokal, unabhängig von externen Diensten und funktioniert zuverlässig auch in abgeschotteten Netzwerken – ideal für produktive, störungsarme Umgebungen.

Kurz gesagt: Wer MQTT zwischen Node-RED und ioBroker produktiv nutzen will, kommt um ein solches Failover-System kaum herum. Die hier vorgestellte Lösung ist simpel, effizient und praxisbewährt.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden.

WordPress Appliance - Powered by TurnKey Linux