Im letzten Beitrag „Shelly MQTT einrichten und erste Befehle nutzen“ habe ich dir gezeigt, wie du MQTT auf einem Shelly aktivierst und den Ausgang mit einfachen MQTT-Kommandos steuerst. Für einen ersten Test eignet sich der MQTT Explorer sehr gut. Möchtest du den Shelly jedoch dauerhaft in eigene Automatisierungen einbinden, ist eine Lösung wie Node-RED deutlich komfortabler.
In diesem Beitrag zeige ich dir, wie du deinen Shelly über MQTT mit Node-RED verbindest. Mithilfe des Low-Code-/No-Code-Ansatzes erstellen wir einen einfachen Flow, mit dem sich der Ausgang des Shellys ein-, ausschalten und umschalten lässt. Außerdem empfangen wir die automatisch veröffentlichten Statusmeldungen und bereiten die enthaltenen JSON-Daten so auf, dass einzelne Werte gezielt weiterverarbeitet werden können.
Damit schaffen wir die Grundlage für spätere Automatisierungen, Visualisierungen und die Speicherung von Messwerten – ganz ohne umfangreiche Programmierung.
Das Ziel des Projekts
In diesem Projekt zeige ich dir praxisnah, wie du deinen Shelly mit Node-RED über MQTT steuerst und Statusmeldungen auswertest – vollständig lokal und ohne Shelly Cloud.
Mit Node-RED kannst du dir mit etwas Aufwand ein eigenes Smart-Home-System mit Automatisierungen, Messwerten und einer Bedienoberfläche aufbauen. Die Dashboard-Nodes bieten dafür bereits viele Möglichkeiten, die ich in einem separaten Beitrag vorgestellt habe.


Messwerte des Shelly 1PM empfangen
Da ich für dieses Projekt einen Shelly 1PM verwende, kann ich den angeschlossenen Verbraucher nicht nur schalten, sondern auch dessen Energieverbrauch erfassen. Der Shelly misst die Stromaufnahme kontinuierlich und veröffentlicht die aktualisierten Energiewerte automatisch über MQTT.
Bei meinem Gerät wird ungefähr einmal pro Minute eine neue Statusmeldung an den MQTT-Broker gesendet. Diese Nachricht enthält unter anderem den bisherigen Gesamtverbrauch sowie die Verbrauchswerte der letzten Minuten. In Node-RED können wir das entsprechende Topic abonnieren, die empfangene JSON-Nachricht auswerten und die einzelnen Messwerte anschließend weiterverarbeiten.

Voraussetzungen
Wenn du das Projekt nachbauen möchtest, benötigst du folgende Komponenten und Programme:
- einen Shelly 1PM* oder Shelly 1PM Mini*,
- eine Lampe als Verbraucher,
- geeignetes Werkzeug und Material für die Verdrahtung,
- einen MQTT-Broker, beispielsweise Eclipse Mosquitto,
- den MQTT Explorer zum Testen und Debuggen,
- eine installierte Node-RED-Umgebung.
In meinem Fall laufen Node-RED und der MQTT-Broker auf einem Dell OptiPlex 3050. Du kannst dafür aber ebenso einen Raspberry Pi, Orange Pi oder einen anderen kleinen Server verwenden.
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
⚠️ Wichtiger Sicherheitshinweis
Der Shelly 1PM wird mit 230 V Netzspannung betrieben. Arbeiten an Netzspannung sind lebensgefährlich und dürfen ausschließlich von entsprechend qualifiziertem Fachpersonal durchgeführt werden.
Beachte dabei immer die fünf Sicherheitsregeln:
- Freischalten
- Gegen Wiedereinschalten sichern
- Spannungsfreiheit feststellen
- Erden und kurzschließen
- Benachbarte, unter Spannung stehende Teile abdecken oder abschranken
Die Spannungsfreiheit muss mit einem geeigneten zweipoligen Spannungsprüfer, beispielsweise einem Duspol, festgestellt werden. Ein einpoliger Phasenprüfer, umgangssprachlich auch „Lügenstift“ genannt, ist dafür nicht geeignet.
Im Zweifel solltest du die Installation von einer Elektrofachkraft durchführen lassen.
Schaltung des Shelly 1PM
Die Verdrahtung ist recht einfach aufgebaut:
- An der Schraubklemme
Owird der geschaltete Außenleiter zur Lampe angeschlossen. - Der Neutralleiter der Lampe wird direkt mit dem Neutralleiter der Zuleitung verbunden. Dafür eignet sich beispielsweise eine dreifache WAGO-Klemme.
- Der Außenleiter der Zuleitung wird an der Klemme
Ldes Shelly angeschlossen. - Der Neutralleiter der Zuleitung wird mit der Klemme
Nverbunden.
Die Klemme O ist der geschaltete Ausgang des Shelly. Wird das Relais aktiviert, liegt dort die Netzspannung für die angeschlossene Lampe an. Die Klemme SW wird in diesem Aufbau nicht benötigt, da die Steuerung ausschließlich über MQTT und Node-RED erfolgt.
MQTT-Broker in Node-RED einrichten
Damit Node-RED mit dem Shelly kommunizieren kann, benötigen wir eine Verbindung zu einem MQTT-Broker. In meinem Fall läuft Eclipse Mosquitto in einem Docker-Container auf einem Dell OptiPlex 3050.
Wie du einen MQTT-Broker mit Docker einrichtest, zeige ich dir ausführlich in meinem Beitrag „MQTT einfach erklärt: Was ist MQTT und warum ist es perfekt für IoT?“. Die dort gezeigte Einrichtung funktioniert genauso gut auf einem Raspberry Pi oder einem anderen kleinen Linux-Server.
Ziehe anschließend eine MQTT-Out-Node in den Arbeitsbereich und öffne ihre Einstellungen. Über das Stiftsymbol neben dem Feld „Server“ legst du eine neue Broker-Verbindung an.
Trage dort die IP-Adresse oder den Hostnamen deines MQTT-Brokers sowie den verwendeten Port ein. In meinem Fall verwende ich 192.168.178.186:1883
Falls dein MQTT-Broker durch einen Benutzernamen und ein Passwort geschützt ist, hinterlegst du die Zugangsdaten ebenfalls in der Konfiguration.
Nach dem Speichern und Bereitstellen des Flows sollte unter der MQTT-Node der Status „Verbunden“ erscheinen.
Aufbau des MQTT-Topics
Damit der Shelly weiß, welcher Ausgang geschaltet werden soll, müssen wir in der MQTT-Out-Node das passende Topic eintragen. Dieses setzt sich aus dem Topic-Präfix des Shellys und dem gewünschten Befehl zusammen:
<Topic-Präfix>/command/switch:0
Bei meinem Shelly 1PM Gen3 lautet das Topic beispielsweise: shelly1pmg3-3030f9e95140/command/switch:0
Der erste Teil ist der MQTT-Topic-Präfix des Geräts. Standardmäßig entspricht dieser meist der Geräte-ID des Shellys, kann in den MQTT-Einstellungen jedoch individuell angepasst werden.
Der Abschnitt command/switch:0 legt fest, dass ein Befehl an den ersten Schaltausgang gesendet wird. Die Zahl hinter dem Doppelpunkt steht für den jeweiligen Kanal:
- switch:0 = erster Ausgang
- switch:1 = zweiter Ausgang
Beim Shelly 1PM ist nur ein Ausgang vorhanden, weshalb wir switch:0 verwenden. Ein Shelly 2PM besitzt dagegen zwei getrennte Relais. Dort werden die beiden Ausgänge mit switch:0 und switch:1 angesprochen.
Payload für den Schaltbefehl
Neben dem MQTT-Topic wird auch eine passende Payload benötigt. Sie enthält den eigentlichen Befehl für den Shelly-Ausgang.
Für die einfache Steuerung stehen unter anderem folgende Werte zur Verfügung:
| Payload | Funktion |
|---|---|
on | Ausgang einschalten |
off | Ausgang ausschalten |
toggle | Aktuellen Zustand umschalten |
on,3 | Ausgang einschalten und nach 3 Sekunden wieder ausschalten |
off,3 | Ausgang ausschalten und nach 3 Sekunden wieder einschalten |
Mit der Payload on,3 wird der Ausgang also eingeschaltet und nach drei Sekunden automatisch wieder ausgeschaltet.

Für die Steuerung mit Node-RED tragen wir diese Werte später beispielsweise in Inject-Nodes oder direkt in einem Dashboard-Schalter ein.
Shelly mit Inject-Nodes über MQTT steuern
Nachdem die Verbindung zum MQTT-Broker eingerichtet sowie Topic und Payload bekannt sind, können wir den ersten einfachen Flow erstellen.
Dafür benötigen wir mehrere Inject-Nodes und eine MQTT-Out-Node. Jede Inject-Node erhält eine andere Payload, beispielsweise on, off oder toggle. Alle Inject-Nodes werden anschließend mit derselben MQTT-Out-Node verbunden.
In der MQTT-Out-Node tragen wir das Topic des Shelly-Ausgangs ein:
shelly1pmg3-3030f9e95140/command/switch:0
Der vollständige Flow ist damit sehr einfach aufgebaut:

Nachdem du den Flow über die Schaltfläche „Übernehmen“ bereitgestellt hast, kannst du die Inject-Nodes einzeln auslösen. Der Shelly sollte den jeweiligen Befehl unmittelbar empfangen und die angeschlossene Lampe entsprechend ein-, ausschalten oder umschalten.
Unterhalb der MQTT-Out-Node sollte außerdem der Status „Verbunden“ angezeigt werden. Reagiert der Shelly nicht, solltest du zunächst die Verbindung zum MQTT-Broker, den Topic-Präfix und die eingetragene Payload überprüfen.
MQTT-Statusmeldungen in Node-RED empfangen
Nachdem wir den Shelly über eine MQTT-Out-Node steuern können, benötigen wir für den Rückweg eine MQTT-In-Node. Diese Node abonniert ein MQTT-Topic und gibt jede dort veröffentlichte Nachricht an den nachfolgenden Flow weiter.
Ziehe dazu eine MQTT-In-Node in den Arbeitsbereich und öffne ihre Einstellungen. Als Broker wählst du dieselbe Verbindung aus, die du bereits für die MQTT-Out-Node eingerichtet hast.
Topic der Statusmeldungen
Die automatischen Statusänderungen veröffentlicht der Shelly über folgendes Topic:
<Topic-Präfix>/events/rpc
Bei meinem Shelly 1PM Gen3 lautet es:
shelly1pmg3-3030f9e95140/events/rpc
Trage dieses Topic in die MQTT-In-Node ein. Die Node abonniert damit alle automatisch veröffentlichten NotifyStatus– und NotifyEvent-Nachrichten dieses Shellys.
Für einen ersten Test verbindest du die MQTT-In-Node direkt mit einer Debug-Node:

Node-RED Flow zum Importieren
[
{
"id": "93d7025accf24a2c",
"type": "tab",
"label": "einfacher Flow mit MQTT IN Node",
"disabled": false,
"info": "",
"env": []
},
{
"id": "dc21707a81d02e0e",
"type": "mqtt in",
"z": "93d7025accf24a2c",
"name": "Shelly events/rpc",
"topic": "shelly1pmg3-3030f9e95140/events/rpc",
"qos": "0",
"datatype": "auto-detect",
"broker": "mqtt_broker_shelly",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 120,
"y": 80,
"wires": [
[
"25b58994c75b18d2"
]
]
},
{
"id": "25b58994c75b18d2",
"type": "debug",
"z": "93d7025accf24a2c",
"name": "Ausgabe der Nachricht vom Shelly",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 380,
"y": 80,
"wires": []
},
{
"id": "mqtt_broker_shelly",
"type": "mqtt-broker",
"name": "Lokaler MQTT-Broker",
"broker": "192.168.178.186",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {}
}
]
Nachdem du den Flow deployed hast, sollten die vom Shelly gesendeten Nachrichten im Debug-Fenster erscheinen.



Bei meinem Test folgte rund eine Minute später eine weitere Statusmeldung mit den Energiewerten der letzten drei Minuten. Diese Meldung wurde anschließend regelmäßig aktualisiert.
Warum mehrere MQTT-Nachrichten zusammengeführt werden müssen
Beim Ein- oder Ausschalten des Shelly werden die verfügbaren Statuswerte nicht gemeinsam in einer einzigen MQTT-Nachricht übertragen. Stattdessen veröffentlicht das Gerät mehrere NotifyStatus-Meldungen, die jeweils nur die Werte enthalten, die sich gerade geändert haben.
Beim Einschalten meines Shelly 1PM wurde zunächst der neue Schaltzustand übertragen:
{
"output": true
}
Kurz danach folgte eine weitere Nachricht mit der aktuellen Leistung:
{
"apower": 5.8
}
Anschließend wurde die gemessene Stromstärke separat veröffentlicht:
{
"current": 0.055
}
Die Energiewerte unter aenergy wurden erst knapp eine Minute später übertragen. Diese Nachricht enthielt unter anderem den Gesamtverbrauch sowie die Energieverbräuche der letzten drei Minuten.
Eine einzelne MQTT-Nachricht enthält daher nicht alle Werte, die wir später gemeinsam im Dashboard anzeigen möchten. Aus diesem Grund verwenden wir eine Function-Node und den Flow-Context.
Die Function-Node prüft jede eintreffende Nachricht und aktualisiert nur die darin enthaltenen Eigenschaften. Bereits empfangene Werte bleiben im Flow-Context gespeichert, bis sie durch neue Werte ersetzt werden.
Auf diese Weise entsteht nach und nach ein gemeinsamer Datensatz mit:
- Schaltzustand,
- aktueller Leistung,
- Stromstärke,
- Gesamtverbrauch,
- Energiewerten der letzten drei Minuten,
- Zeitstempel der letzten Nachricht.
Die Werte werden nicht innerhalb eines festen Zeitfensters gesammelt. Stattdessen bleiben sie im Flow-Context erhalten und werden bei jeder neuen NotifyStatus-Meldung ergänzt oder aktualisiert.
MQTT-Werte im Flow-Context zusammenführen
Nachdem wir gesehen haben, dass der Shelly seine Werte in mehreren getrennten NotifyStatus-Nachrichten sendet, müssen wir diese Informationen in Node-RED zu einem gemeinsamen Datensatz zusammenführen.

Node-RED Flow zum Importieren
[
{
"id": "a2fdbbd67b270e6b",
"type": "tab",
"label": "Shelly MQTT-Status zusammenführen",
"disabled": false,
"info": "Empfängt NotifyStatus-Meldungen eines Shelly 1PM, führt Teilwerte im Flow-Context zusammen und gibt den gemeinsamen Datensatz aus.",
"env": []
},
{
"id": "085374c7522be9be",
"type": "mqtt in",
"z": "a2fdbbd67b270e6b",
"name": "Shelly events/rpc",
"topic": "shelly1pmg3-3030f9e95140/events/rpc",
"qos": "0",
"datatype": "auto-detect",
"broker": "mqtt_broker_local",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 510,
"y": 540,
"wires": [
[
"2e98451d7d17dbc6"
]
]
},
{
"id": "2e98451d7d17dbc6",
"type": "function",
"z": "a2fdbbd67b270e6b",
"name": "Statuswerte zusammenführen",
"func": "let data = msg.payload;\n\n// Falls die MQTT-Nachricht noch als Text vorliegt\nif (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch (error) {\n node.warn(\"Ungültige JSON-Nachricht empfangen.\");\n return null;\n }\n}\n\n// Nur relevante NotifyStatus-Nachrichten verarbeiten\nif (\n data?.method !== \"NotifyStatus\" ||\n !data?.params?.[\"switch:0\"]\n) {\n return null;\n}\n\nconst switchData = data.params[\"switch:0\"];\n\n// Bereits gespeicherte Werte laden\nconst values = flow.get(\"shellyValues\") || {};\n\n// Zeitpunkt der zuletzt empfangenen Statusmeldung\nif (typeof data.params.ts === \"number\") {\n values.timestamp = data.params.ts;\n}\n\n// Schaltzustand\nif (typeof switchData.output === \"boolean\") {\n values.output = switchData.output;\n}\n\n// Aktuelle Leistung in Watt\nif (typeof switchData.apower === \"number\") {\n values.powerW = switchData.apower;\n}\n\n// Stromstärke in Ampere\nif (typeof switchData.current === \"number\") {\n values.currentA = switchData.current;\n}\n\n// Energiewerte\nif (switchData.aenergy) {\n const energy = switchData.aenergy;\n const byMinute = Array.isArray(energy.by_minute)\n ? energy.by_minute\n : [];\n\n values.energyTotalWh = energy.total ?? null;\n values.energyByMinute = byMinute;\n values.minuteTimestamp = energy.minute_ts ?? null;\n\n values.lastMinute = byMinute[0] ?? null;\n values.twoMinutesAgo = byMinute[1] ?? null;\n values.threeMinutesAgo = byMinute[2] ?? null;\n}\n\n// Zurückgespeiste Energie\nif (switchData.ret_aenergy) {\n values.returnedEnergyTotalWh =\n switchData.ret_aenergy.total ?? null;\n}\n\n// Aktualisierten Datensatz speichern\nflow.set(\"shellyValues\", values);\n\n// Datensatz an nachfolgende Nodes ausgeben\nmsg.payload = values;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 810,
"y": 540,
"wires": [
[
"7934b43151d33dee",
"77e49c5368f0b409"
]
]
},
{
"id": "7934b43151d33dee",
"type": "debug",
"z": "a2fdbbd67b270e6b",
"name": "Zusammengeführter Datensatz",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1130,
"y": 510,
"wires": []
},
{
"id": "77e49c5368f0b409",
"type": "change",
"z": "a2fdbbd67b270e6b",
"name": "Flow-Context anzeigen",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "shellyValues",
"tot": "flow"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1100,
"y": 570,
"wires": [
[
"73f6e9ed4fb7c939"
]
]
},
{
"id": "73f6e9ed4fb7c939",
"type": "debug",
"z": "a2fdbbd67b270e6b",
"name": "Flow-Context shellyValues",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1360,
"y": 570,
"wires": []
},
{
"id": "0ea119799a56682c",
"type": "inject",
"z": "a2fdbbd67b270e6b",
"name": "Flow-Context zurücksetzen",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "str",
"x": 550,
"y": 640,
"wires": [
[
"38e619d1e40e8815"
]
]
},
{
"id": "38e619d1e40e8815",
"type": "function",
"z": "a2fdbbd67b270e6b",
"name": "shellyValues löschen",
"func": "flow.set(\"shellyValues\", {});\nmsg.payload = {};\nnode.status({\n fill: \"grey\",\n shape: \"ring\",\n text: \"Flow-Context geleert\"\n});\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 810,
"y": 640,
"wires": [
[
"d91b07d2781dc3c8"
]
]
},
{
"id": "d91b07d2781dc3c8",
"type": "debug",
"z": "a2fdbbd67b270e6b",
"name": "Zurückgesetzter Context",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1100,
"y": 640,
"wires": []
},
{
"id": "mqtt_broker_local",
"type": "mqtt-broker",
"name": "Lokaler MQTT-Broker",
"broker": "192.168.178.186",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {}
}
]
Dafür verwenden wir eine Function-Node und den Flow-Context. Der Flow-Context dient als Zwischenspeicher innerhalb des aktuellen Node-RED-Flows. Bereits empfangene Werte bleiben dort erhalten und können bei jeder neuen MQTT-Nachricht ergänzt oder aktualisiert werden.
Welche Nachrichten werden verarbeitet?
Zunächst prüfen wir, ob es sich überhaupt um eine Statusmeldung handelt. Die für dieses Projekt relevanten Nachrichten enthalten:
{
"method": "NotifyStatus"
}
Zusätzlich muss der Bereich switch:0 vorhanden sein, da sich dort die Werte des ersten Schaltausgangs befinden.
Eine eingehende Nachricht kann beispielsweise nur den Schaltzustand enthalten:
{
"output": true
}
Eine andere Nachricht liefert lediglich die aktuelle Leistung:
{
"apower": 5.8
}
Weitere Nachrichten können unter anderem current oder aenergy enthalten.
Die Function-Node muss daher immer prüfen, welche Eigenschaften in der aktuellen Nachricht tatsächlich vorhanden sind.
Vorhandene Werte laden
Zu Beginn laden wir die bereits gespeicherten Daten aus dem Flow-Context:
const values = flow.get("shellyValues") || {};
Existiert noch kein Datensatz, wird ein neues leeres Objekt angelegt.
Treffen später weitere Nachrichten ein, enthält values bereits die zuvor gespeicherten Informationen. So bleiben beispielsweise Schaltzustand und Leistung erhalten, auch wenn die aktuelle MQTT-Nachricht nur neue Energiewerte enthält.
Werte gezielt aktualisieren
Für jede mögliche Eigenschaft prüfen wir, ob sie in der aktuellen Nachricht vorhanden ist.
Beim Schaltzustand verwenden wir:
if (typeof switchData.output === "boolean") {
values.output = switchData.output;
}
Die Prüfung mit typeof ist wichtig, weil auch false ein gültiger Wert ist.
Für Leistung, Spannung und Stromstärke gehen wir ähnlich vor:
if (typeof switchData.apower === "number") {
values.powerW = switchData.apower;
}
if (typeof switchData.current === "number") {
values.currentA = switchData.current;
}
Auch der Wert 0 wird dadurch korrekt übernommen.
Energiewerte übernehmen
Enthält die Nachricht den Bereich aenergy, können wir den Gesamtverbrauch und die Werte der letzten drei Minuten speichern:
if (switchData.aenergy) {
values.energyTotalWh = switchData.aenergy.total;
values.energyByMinute = switchData.aenergy.by_minute;
values.minuteTimestamp = switchData.aenergy.minute_ts;
}
Das Array energyByMinute enthält die drei zuletzt gemeldeten Minutenwerte.
Zusätzlich kann der Shelly unter ret_aenergy eine zurückgespeiste Energiemenge liefern:
if (switchData.ret_aenergy) {
values.returnedEnergyTotalWh =
switchData.ret_aenergy.total;
}
Für einen normalen Verbraucher bleibt dieser Wert in der Regel bei 0.
Zeitstempel der letzten Nachricht speichern
Jede NotifyStatus-Nachricht enthält unter params.ts einen Zeitstempel. Diesen speichern wir als Zeitpunkt der zuletzt empfangenen Statusmeldung:
values.timestamp = data.params.ts ?? values.timestamp;
Der Wert kann später im Dashboard formatiert angezeigt werden. Dadurch lässt sich erkennen, wann zuletzt eine Meldung vom Shelly eingetroffen ist.
Vollständiger Code der Function-Node
In die Function-Node kannst du folgenden Code einfügen:
let data = msg.payload;
// Falls die MQTT-Nachricht als Text vorliegt
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (error) {
node.warn("Ungültige JSON-Nachricht empfangen.");
return null;
}
}
// Nur relevante NotifyStatus-Nachrichten verarbeiten
if (
data?.method !== "NotifyStatus" ||
!data?.params?.["switch:0"]
) {
return null;
}
const switchData = data.params["switch:0"];
// Bereits gespeicherte Werte laden
const values = flow.get("shellyValues") || {};
// Zeitpunkt der zuletzt empfangenen Statusmeldung
if (typeof data.params.ts === "number") {
values.timestamp = data.params.ts;
}
// Schaltzustand
if (typeof switchData.output === "boolean") {
values.output = switchData.output;
}
// Aktuelle Leistung in Watt
if (typeof switchData.apower === "number") {
values.powerW = switchData.apower;
}
// Stromstärke in Ampere
if (typeof switchData.current === "number") {
values.currentA = switchData.current;
}
// Energiewerte
if (switchData.aenergy) {
const energy = switchData.aenergy;
const byMinute = Array.isArray(energy.by_minute)
? energy.by_minute
: [];
values.energyTotalWh = energy.total ?? null;
values.energyByMinute = byMinute;
values.minuteTimestamp = energy.minute_ts ?? null;
values.lastMinute = byMinute[0] ?? null;
values.twoMinutesAgo = byMinute[1] ?? null;
values.threeMinutesAgo = byMinute[2] ?? null;
}
// Zurückgespeiste Energie
if (switchData.ret_aenergy) {
values.returnedEnergyTotalWh =
switchData.ret_aenergy.total ?? null;
}
// Aktualisierten Datensatz speichern
flow.set("shellyValues", values);
// Datensatz an nachfolgende Nodes ausgeben
msg.payload = values;
return msg;
Ergebnis im Flow-Context
Nach mehreren empfangenen MQTT-Nachrichten kann der gemeinsame Datensatz beispielsweise so aussehen:
{
"timestamp": 1782652080,
"output": true,
"powerW": 5.8,
"currentA": 0.055,
"energyTotalWh": 40.809,
"energyByMinute": [
0,
202.254,
202.254
],
"lastMinute": 0,
"twoMinutesAgo": 202.254,
"threeMinutesAgo": 202.254,
"returnedEnergyTotalWh": 0
}
Shelly-Dashboard mit Node-RED erstellen
Nachdem wir die MQTT-Statusmeldungen des Shelly ausgewertet und die einzelnen Messwerte im Flow-Context zusammengeführt haben, können wir die Daten in einem übersichtlichen Dashboard darstellen. Dafür verwende ich die Nodes aus dem Paket @flowfuse/node-red-dashboard.
Node-RED Flow zum Importieren
[
{
"id": "tab_shelly_dashboard_full",
"type": "tab",
"label": "Shelly 1PM Dashboard",
"disabled": false,
"info": "FlowFuse Dashboard 2.0 für Shelly 1PM: Spannung, Strom, Leistung, Schalten, letzte drei Minuten und Zeitstempel.",
"env": []
},
{
"id": "mqtt_in_events",
"type": "mqtt in",
"z": "tab_shelly_dashboard_full",
"name": "Shelly events/rpc",
"topic": "shelly1pmg3-3030f9e95140/events/rpc",
"qos": "0",
"datatype": "auto-detect",
"broker": "mqtt_broker_shelly",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 150,
"y": 100,
"wires": [
[
"fn_collect_shelly"
]
]
},
{
"id": "mqtt_in_status",
"type": "mqtt in",
"z": "tab_shelly_dashboard_full",
"name": "Shelly status/switch:0",
"topic": "shelly1pmg3-3030f9e95140/status/switch:0",
"qos": "0",
"datatype": "auto-detect",
"broker": "mqtt_broker_shelly",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 170,
"y": 160,
"wires": [
[
"fn_collect_shelly"
]
]
},
{
"id": "fn_collect_shelly",
"type": "function",
"z": "tab_shelly_dashboard_full",
"name": "Shelly-Werte zusammenführen",
"func": "let data = msg.payload;\n\nif (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch (error) {\n node.warn(\"Ungültige JSON-Nachricht empfangen\");\n return null;\n }\n}\n\nlet switchData;\nlet timestamp;\n\nif (data?.method === \"NotifyStatus\" && data?.params?.[\"switch:0\"]) {\n switchData = data.params[\"switch:0\"];\n timestamp = data.params.ts;\n} else if (typeof data === \"object\" && data !== null && (typeof data.output === \"boolean\" || typeof data.voltage === \"number\" || typeof data.current === \"number\" || typeof data.apower === \"number\")) {\n switchData = data;\n timestamp = Date.now() / 1000;\n} else {\n return null;\n}\n\nconst values = flow.get(\"shellyValues\") || {};\nvalues.timestamp = timestamp ?? values.timestamp;\nif (typeof switchData.output === \"boolean\") values.output = switchData.output;\nif (typeof switchData.apower === \"number\") values.powerW = switchData.apower;\nif (typeof switchData.voltage === \"number\") values.voltageV = switchData.voltage;\nif (typeof switchData.current === \"number\") values.currentA = switchData.current;\nif (switchData.aenergy) {\n values.energyTotalWh = switchData.aenergy.total;\n values.energyByMinute = switchData.aenergy.by_minute;\n values.minuteTimestamp = switchData.aenergy.minute_ts;\n}\nif (switchData.ret_aenergy) values.returnedEnergyTotalWh = switchData.ret_aenergy.total;\nflow.set(\"shellyValues\", values);\nmsg.payload = values;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 130,
"wires": [
[
"ui_template_shelly_dashboard",
"debug_combined"
]
]
},
{
"id": "mqtt_out_command",
"type": "mqtt out",
"z": "tab_shelly_dashboard_full",
"name": "Shelly schalten",
"topic": "shelly1pmg3-3030f9e95140/command/switch:0",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "mqtt_broker_shelly",
"x": 1100,
"y": 240,
"wires": []
},
{
"id": "inject_status_update",
"type": "inject",
"z": "tab_shelly_dashboard_full",
"name": "Status beim Start anfordern",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 1,
"topic": "",
"payload": "status_update",
"payloadType": "str",
"x": 190,
"y": 240,
"wires": [
[
"mqtt_out_command"
]
]
},
{
"id": "debug_combined",
"type": "debug",
"z": "tab_shelly_dashboard_full",
"name": "Zusammengeführte Shelly-Werte",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 760,
"y": 210,
"wires": []
},
{
"id": "ui_template_shelly_dashboard",
"type": "ui-template",
"z": "tab_shelly_dashboard_full",
"group": "ui_group_custom_dashboard",
"page": "",
"ui": "",
"name": "Styled Shelly Dashboard",
"order": 1,
"width": 12,
"height": 0,
"head": "",
"format": "\n<template>\n <div class=\"shelly-dashboard\" :class=\"{ dark: darkMode }\">\n <div class=\"dashboard-header\">\n <div>\n <p class=\"eyebrow\">SMART HOME · MQTT</p>\n <h1>Shelly 1PM Dashboard</h1>\n <p class=\"subtitle\">Lokale Steuerung und Live-Messwerte</p>\n </div>\n\n <button class=\"theme-toggle\" type=\"button\" @click=\"toggleTheme\">\n <span class=\"theme-icon\">{{ darkMode ? '☾' : '☀' }}</span>\n <span>{{ darkMode ? 'Dunkel' : 'Hell' }}</span>\n <span class=\"toggle-track\" :class=\"{ active: darkMode }\">\n <span class=\"toggle-thumb\"></span>\n </span>\n </button>\n </div>\n\n <section class=\"metric-grid\">\n <article class=\"metric-card voltage\">\n <div class=\"metric-icon\">V</div>\n <div>\n <span class=\"metric-label\">Spannung</span>\n <strong>{{ numberValue(values.voltageV, 1) }}</strong>\n <small>Volt</small>\n </div>\n </article>\n\n <article class=\"metric-card current\">\n <div class=\"metric-icon\">A</div>\n <div>\n <span class=\"metric-label\">Strom</span>\n <strong>{{ numberValue(values.currentA, 3) }}</strong>\n <small>Ampere</small>\n </div>\n </article>\n\n <article class=\"metric-card power\">\n <div class=\"metric-icon\">W</div>\n <div>\n <span class=\"metric-label\">Leistung</span>\n <strong>{{ numberValue(values.powerW, 1) }}</strong>\n <small>Watt</small>\n </div>\n </article>\n </section>\n\n <section class=\"panel control-panel\">\n <div>\n <span class=\"panel-kicker\">STEUERUNG</span>\n <h2>Verbraucher</h2>\n <p>Schaltet den Ausgang des Shelly 1PM über MQTT.</p>\n </div>\n\n <button\n class=\"power-switch\"\n :class=\"{ on: isOn }\"\n type=\"button\"\n @click=\"toggleConsumer\"\n :aria-pressed=\"isOn\"\n >\n <span class=\"power-dot\"></span>\n <span>{{ isOn ? 'EIN' : 'AUS' }}</span>\n </button>\n </section>\n\n <section class=\"panel\">\n <div class=\"section-heading\">\n <div>\n <span class=\"panel-kicker\">ENERGIEVERBRAUCH</span>\n <h2>Letzte drei Minuten</h2>\n </div>\n <span class=\"unit-badge\">mWh</span>\n </div>\n\n <div class=\"minute-grid\">\n <article class=\"minute-card minute-1\">\n <span>Letzte Minute</span>\n <strong>{{ minuteValue(0) }}</strong>\n </article>\n <article class=\"minute-card minute-2\">\n <span>Vor zwei Minuten</span>\n <strong>{{ minuteValue(1) }}</strong>\n </article>\n <article class=\"minute-card minute-3\">\n <span>Vor drei Minuten</span>\n <strong>{{ minuteValue(2) }}</strong>\n </article>\n </div>\n\n <div class=\"last-message\">\n <span class=\"status-dot\"></span>\n <span>Letzte Nachricht</span>\n <strong>{{ formattedTimestamp }}</strong>\n </div>\n </section>\n </div>\n</template>\n\n<script>\nexport default {\n data() {\n return {\n values: {},\n darkMode: false\n }\n },\n computed: {\n isOn() {\n return this.values?.output === true\n },\n formattedTimestamp() {\n const timestamp = this.values?.timestamp\n if (typeof timestamp !== 'number') {\n return 'Noch keine Nachricht empfangen'\n }\n\n const date = new Date(timestamp * 1000)\n const parts = new Intl.DateTimeFormat('de-DE', {\n timeZone: 'Europe/Berlin',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n hour12: false\n }).formatToParts(date)\n\n const get = type => parts.find(part => part.type === type)?.value ?? ''\n return `${get('hour')}:${get('minute')}:${get('second')} ${get('day')}.${get('month')}.${get('year')}`\n }\n },\n watch: {\n msg: {\n handler() {\n if (this.msg?.payload && typeof this.msg.payload === 'object') {\n this.values = { ...this.msg.payload }\n }\n },\n deep: true,\n immediate: true\n }\n },\n mounted() {\n this.darkMode = localStorage.getItem('shelly-dashboard-theme') === 'dark'\n },\n methods: {\n toggleTheme() {\n this.darkMode = !this.darkMode\n localStorage.setItem(\n 'shelly-dashboard-theme',\n this.darkMode ? 'dark' : 'light'\n )\n },\n toggleConsumer() {\n const command = this.isOn ? 'off' : 'on'\n this.send({ payload: command })\n },\n numberValue(value, digits) {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n return '–'\n }\n return value.toLocaleString('de-DE', {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits\n })\n },\n minuteValue(index) {\n const values = this.values?.energyByMinute\n if (!Array.isArray(values) || typeof values[index] !== 'number') {\n return '–'\n }\n return values[index].toLocaleString('de-DE', {\n minimumFractionDigits: 3,\n maximumFractionDigits: 3\n })\n }\n }\n}\n</script>\n\n<style>\n.shelly-dashboard {\n --bg: #eef4ff;\n --surface: rgba(255,255,255,0.88);\n --surface-solid: #ffffff;\n --text: #172033;\n --muted: #64748b;\n --border: rgba(148,163,184,0.22);\n --shadow: 0 18px 42px rgba(15,23,42,0.13);\n --shadow-soft: 0 10px 26px rgba(15,23,42,0.10);\n min-height: 100%;\n padding: 24px;\n border-radius: 26px;\n background:\n radial-gradient(circle at 0% 0%, rgba(59,130,246,0.18), transparent 34%),\n radial-gradient(circle at 100% 0%, rgba(236,72,153,0.12), transparent 30%),\n linear-gradient(135deg, #eef4ff, #f8fafc 48%, #fff7ed);\n color: var(--text);\n transition: all .25s ease;\n}\n\n.shelly-dashboard.dark {\n --bg: #07101f;\n --surface: rgba(15,23,42,0.88);\n --surface-solid: #111827;\n --text: #f8fafc;\n --muted: #cbd5e1;\n --border: rgba(148,163,184,0.18);\n --shadow: 0 22px 54px rgba(0,0,0,0.44);\n --shadow-soft: 0 14px 34px rgba(0,0,0,0.38);\n background:\n radial-gradient(circle at 0% 0%, rgba(37,99,235,0.28), transparent 34%),\n radial-gradient(circle at 100% 0%, rgba(168,85,247,0.18), transparent 30%),\n linear-gradient(135deg, #07101f, #0f172a 52%, #111827);\n}\n\n.dashboard-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n margin-bottom: 22px;\n}\n\n.eyebrow,\n.panel-kicker {\n margin: 0 0 5px;\n font-size: .74rem;\n letter-spacing: .16em;\n font-weight: 800;\n color: #3b82f6;\n}\n\n.dashboard-header h1,\n.panel h2 {\n margin: 0;\n color: var(--text);\n}\n\n.dashboard-header h1 {\n font-size: clamp(1.7rem, 3vw, 2.5rem);\n line-height: 1.05;\n}\n\n.subtitle,\n.panel p {\n margin: 7px 0 0;\n color: var(--muted);\n}\n\n.theme-toggle {\n display: flex;\n align-items: center;\n gap: 9px;\n border: 1px solid var(--border);\n color: var(--text);\n background: var(--surface);\n box-shadow: var(--shadow-soft);\n padding: 9px 12px;\n border-radius: 999px;\n cursor: pointer;\n font-weight: 700;\n}\n\n.theme-icon {\n font-size: 1.15rem;\n}\n\n.toggle-track {\n position: relative;\n width: 38px;\n height: 22px;\n border-radius: 999px;\n background: #cbd5e1;\n transition: .2s ease;\n}\n\n.toggle-track.active {\n background: #8b5cf6;\n}\n\n.toggle-thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 2px 6px rgba(0,0,0,.22);\n transition: transform .2s ease;\n}\n\n.toggle-track.active .toggle-thumb {\n transform: translateX(16px);\n}\n\n.metric-grid,\n.minute-grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 16px;\n}\n\n.metric-card {\n display: flex;\n align-items: center;\n gap: 16px;\n min-height: 132px;\n padding: 20px;\n color: #fff;\n border-radius: 22px;\n box-shadow: var(--shadow);\n transform: translateY(0);\n transition: transform .2s ease, box-shadow .2s ease;\n}\n\n.metric-card:hover,\n.minute-card:hover {\n transform: translateY(-4px);\n}\n\n.metric-card.voltage {\n background: linear-gradient(135deg, #2563eb, #06b6d4);\n}\n\n.metric-card.current {\n background: linear-gradient(135deg, #7c3aed, #d946ef);\n}\n\n.metric-card.power {\n background: linear-gradient(135deg, #ea580c, #f59e0b);\n}\n\n.metric-icon {\n display: grid;\n place-items: center;\n flex: 0 0 52px;\n width: 52px;\n height: 52px;\n border-radius: 17px;\n background: rgba(255,255,255,.18);\n border: 1px solid rgba(255,255,255,.25);\n font-size: 1.45rem;\n font-weight: 900;\n}\n\n.metric-label {\n display: block;\n opacity: .88;\n font-weight: 700;\n}\n\n.metric-card strong {\n display: block;\n margin-top: 3px;\n font-size: clamp(1.65rem, 4vw, 2.35rem);\n line-height: 1;\n}\n\n.metric-card small {\n display: block;\n margin-top: 5px;\n opacity: .78;\n}\n\n.panel {\n margin-top: 18px;\n padding: 20px;\n border-radius: 22px;\n border: 1px solid var(--border);\n background: var(--surface);\n box-shadow: var(--shadow);\n backdrop-filter: blur(12px);\n}\n\n.control-panel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n}\n\n.power-switch {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 11px;\n min-width: 130px;\n padding: 14px 22px;\n border: 0;\n border-radius: 16px;\n cursor: pointer;\n color: #fff;\n background: linear-gradient(135deg, #64748b, #334155);\n box-shadow: 0 10px 24px rgba(51,65,85,.28);\n font-size: 1rem;\n font-weight: 900;\n letter-spacing: .06em;\n transition: all .2s ease;\n}\n\n.power-switch.on {\n background: linear-gradient(135deg, #059669, #22c55e);\n box-shadow: 0 12px 28px rgba(34,197,94,.28);\n}\n\n.power-dot {\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #cbd5e1;\n box-shadow: 0 0 0 4px rgba(255,255,255,.12);\n}\n\n.power-switch.on .power-dot {\n background: #dcfce7;\n box-shadow: 0 0 14px rgba(220,252,231,.85);\n}\n\n.section-heading,\n.last-message {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n}\n\n.unit-badge {\n padding: 6px 10px;\n border-radius: 999px;\n color: #2563eb;\n background: rgba(59,130,246,.11);\n font-weight: 800;\n}\n\n.minute-grid {\n margin-top: 16px;\n}\n\n.minute-card {\n min-height: 105px;\n padding: 17px;\n border-radius: 18px;\n color: var(--text);\n box-shadow: var(--shadow-soft);\n transition: transform .2s ease;\n}\n\n.minute-card span {\n display: block;\n color: var(--muted);\n font-weight: 700;\n}\n\n.minute-card strong {\n display: block;\n margin-top: 10px;\n font-size: 1.55rem;\n}\n\n.minute-1 {\n background: linear-gradient(135deg, rgba(14,165,233,.24), rgba(59,130,246,.10));\n}\n\n.minute-2 {\n background: linear-gradient(135deg, rgba(139,92,246,.24), rgba(217,70,239,.10));\n}\n\n.minute-3 {\n background: linear-gradient(135deg, rgba(244,63,94,.22), rgba(249,115,22,.10));\n}\n\n.dark .minute-1,\n.dark .minute-2,\n.dark .minute-3 {\n background-color: rgba(15,23,42,.82);\n}\n\n.last-message {\n margin-top: 16px;\n padding: 13px 15px;\n border-radius: 15px;\n background: rgba(59,130,246,.08);\n color: var(--muted);\n}\n\n.last-message strong {\n color: var(--text);\n}\n\n.status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22c55e;\n box-shadow: 0 0 12px rgba(34,197,94,.65);\n}\n\n@media (max-width: 760px) {\n .dashboard-header,\n .control-panel,\n .section-heading,\n .last-message {\n align-items: stretch;\n flex-direction: column;\n }\n\n .metric-grid,\n .minute-grid {\n grid-template-columns: 1fr;\n }\n\n .theme-toggle,\n .power-switch {\n width: 100%;\n }\n}\n</style>\n",
"storeOutMessages": true,
"passthru": false,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 790,
"y": 120,
"wires": [
[
"mqtt_out_command"
]
]
},
{
"id": "mqtt_broker_shelly",
"type": "mqtt-broker",
"name": "Lokaler MQTT-Broker",
"broker": "192.168.178.186",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {}
},
{
"id": "ui_group_custom_dashboard",
"type": "ui-group",
"name": "Shelly Dashboard",
"page": "ui_page_shelly_full",
"width": "12",
"height": "1",
"order": 1,
"showTitle": false,
"className": "",
"visible": "true",
"disabled": "false",
"groupType": "default"
},
{
"id": "ui_page_shelly_full",
"type": "ui-page",
"name": "Shelly 1PM",
"ui": "6ab8ca9187b0f4ba",
"path": "/shelly-1pm",
"icon": "lightbulb",
"layout": "grid",
"theme": "ui_theme_shelly_full",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "3"
},
{
"name": "Tablet",
"px": "576",
"cols": "6"
},
{
"name": "Small Desktop",
"px": "768",
"cols": "9"
},
{
"name": "Desktop",
"px": "1024",
"cols": "12"
}
],
"order": 1,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "6ab8ca9187b0f4ba",
"type": "ui-base",
"name": "Mein Dashboard",
"path": "/dashboard",
"appIcon": "",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control"
],
"showPathInSidebar": false,
"headerContent": "page",
"navigationStyle": "default",
"titleBarStyle": "default",
"showReconnectNotification": true,
"notificationDisplayTime": 1,
"showDisconnectNotification": true,
"allowInstall": false
},
{
"id": "ui_theme_shelly_full",
"type": "ui-theme",
"name": "Shelly Theme",
"colors": {
"surface": "#ffffff",
"primary": "#2563eb",
"bgPage": "#eaf0fb",
"groupBg": "#ffffff",
"groupOutline": "#dbeafe"
},
"sizes": {
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "20px",
"widgetGap": "12px",
"density": "default"
}
},
{
"id": "92eb6594b361568b",
"type": "global-config",
"env": [],
"modules": {
"@flowfuse/node-red-dashboard": "1.30.2"
}
}
]
Das Dashboard zeigt oben drei farbige Kacheln mit den aktuell erfassten Messwerten:
- Spannung in Volt,
- Stromstärke in Ampere,
- aktuelle Leistung in Watt.

Darunter befindet sich ein Schalter, über den der angeschlossene Verbraucher ein- und ausgeschaltet werden kann. Beim Betätigen des Schalters sendet Node-RED die Payload on beziehungsweise off über die bereits eingerichtete MQTT-Out-Node an den Shelly.

Messwerte im Dashboard anzeigen
Die zuvor im Flow-Context gespeicherten Werte werden an das Dashboard übergeben. Dabei werden unter anderem folgende Eigenschaften verwendet:
values.voltageV values.currentA values.powerW values.output values.energyByMinute values.timestamp
Die Werte für Spannung, Strom und Leistung werden in den drei oberen Kacheln dargestellt. Ändert sich einer der Werte durch eine neue MQTT-Statusmeldung, wird die Anzeige automatisch aktualisiert.
Verbraucher über das Dashboard schalten
Der Schaltzustand des Verbrauchers wird über einen eigenen Schalter dargestellt. Ist der Ausgang des Shelly eingeschaltet, zeigt das Dashboard den Zustand EIN. Beim Ausschalten wechselt die Anzeige entsprechend auf AUS.
Der Schalter sendet abhängig vom gewählten Zustand eine der beiden Payloads: on oder: off
Die MQTT-Out-Node veröffentlicht den Befehl anschließend unter dem bekannten Topic:
shelly1pmg3-3030f9e95140/command/switch:0
Damit der angezeigte Zustand mit dem tatsächlichen Ausgang des Shelly übereinstimmt, wird auch der Wert output aus den empfangenen Statusmeldungen berücksichtigt.
Energieverbrauch der letzten drei Minuten
Unterhalb der Steuerung werden die drei Werte aus dem Array aenergy.by_minute angezeigt. Der Shelly liefert damit die Energieverbräuche der letzten drei vollständig abgeschlossenen Minuten.
Die Werte werden in Milliwattstunden dargestellt:
Letzte Minute Vor zwei Minuten Vor drei Minuten
Dadurch lässt sich bereits ohne zusätzliche Datenbank erkennen, wie sich der Verbrauch in den letzten Minuten entwickelt hat.
Zeitpunkt der letzten MQTT-Nachricht
Zusätzlich zeigt das Dashboard an, wann zuletzt eine Statusmeldung des Shelly empfangen wurde. Dafür wird der Unix-Zeitstempel aus der MQTT-Nachricht in ein lesbares Datum umgewandelt.
Das verwendete Format lautet: Stunde:Minute:Sekunde Tag.Monat.Jahr
Eine mögliche Ausgabe sieht beispielsweise so aus: 18:42:15 27.06.2026
Der Zeitstempel ist besonders hilfreich, um zu erkennen, ob weiterhin aktuelle Daten vom Shelly eintreffen. Bleibt die Anzeige über einen längeren Zeitraum unverändert, kann dies auf eine unterbrochene MQTT-Verbindung oder ein nicht erreichbares Gerät hinweisen.
Helles und dunkles Theme
Das Dashboard verfügt außerdem über einen Schalter für ein helles und ein dunkles Theme. Im hellen Modus werden die Messwerte auf einem freundlichen, kontrastreichen Hintergrund angezeigt. Das Dark Theme eignet sich besonders für dauerhaft geöffnete Anzeigen oder Wanddisplays.


Die ausgewählte Darstellung wird im Browser gespeichert. Dadurch bleibt das zuletzt verwendete Theme auch nach dem erneuten Laden des Dashboards erhalten.
Die farbigen Kacheln, Schatten und unterschiedlichen Hervorhebungen sorgen dafür, dass sich die Messwerte schnell voneinander unterscheiden lassen. Spannung, Strom und Leistung erhalten jeweils eine eigene Farbe, während der Schaltzustand und die Minutenwerte in separaten Bereichen dargestellt werden.
Das vollständige Dashboard vereint damit Steuerung und Überwachung auf einer Oberfläche: Der Verbraucher lässt sich lokal über MQTT schalten, während Node-RED gleichzeitig die aktuellen Mess- und Energiewerte des Shelly anzeigt.
Fazit und Ausblick
Mit Node-RED und MQTT lässt sich der Shelly vollständig lokal steuern und überwachen. Die einzelnen NotifyStatus-Nachrichten werden dabei im Flow-Context zu einem gemeinsamen Datensatz zusammengeführt und anschließend im Dashboard dargestellt.
Im nächsten Beitrag zeige ich dir, wie du mit einem eigenen Shelly Script individuelle MQTT-Nachrichten an deinen Broker senden kannst. Der folgende Screenshot gibt bereits einen kleinen Vorgeschmack darauf.

Letzte Aktualisierung am: 29. Juni 2026






