Mit einer MQTT-Steuerung für intelligente Beleuchtung kann jede App, jedes Dashboard und jede Automatisierung ein Licht dimmen, eine Szene abrufen oder einen Zeitplan ausführen, indem sie einfach eine Nachricht veröffentlicht. In diesem Tutorial bauen Sie einen ESP32-PWM-Dimmer für LED-Streifen mit sanften Überblendungen, entwerfen Topics, die für einen Raum ebenso funktionieren wie für ein ganzes Gebäude, speichern Szenen als retained Messages und führen Zeitpläne über einen winzigen Python-Dienst aus, der mit einem ConnectMQ-Broker verbunden ist.
Beleuchtungshardware für MQTT auswählen
Bevor Sie Firmware schreiben, legen Sie fest, welche Art von Beleuchtung Sie steuern. Jede Option passt etwas anders zu MQTT.
| Hardware | Anbindung an MQTT | Geeignet für | Worauf zu achten ist |
|---|---|---|---|
| Niedervolt-LED-Streifen mit ESP32-PWM-Treiber | Eigene Firmware, wie in diesem Tutorial | Schreibtische, Regale, Schränke, Akzentbeleuchtung | Dimensionierung des Netzteils und Spannungsabfall bei langen Strecken |
| Adressierbare LEDs (WS2812B und ähnliche) | Eigene Firmware oder WLED | Effekte und Farbe pro Pixel | Stromaufnahme, Länge der Datenleitung |
| Netzspannungsdimmer und Relais mit MQTT-Firmware | Tasmota, Shelly oder ähnliche Geräte | Vorhandene Deckenleuchten | Nur zertifizierte Hardware, passender Dimmertyp für das Leuchtmittel |
| DALI- oder KNX-Beleuchtung | Brücke vom Bus zu MQTT | Büros und Gewerbegebäude | Der Bus bleibt die Steuerung; MQTT schafft Transparenz |
Im Wohnbereich decken PWM-Streifen und smarte Relais die meisten Anforderungen ab. In Gewerbegebäuden hängt die Beleuchtung meist bereits an DALI oder KNX, und der richtige Weg ist, diesen Bus zu MQTT zu überbrücken, statt ihn zu ersetzen. Die folgende Topic-Struktur funktioniert für alle vier Varianten, und genau deshalb lohnt es sich, sie früh zu standardisieren.
Topic-Design für Leuchten
Eine Leuchte braucht auf MQTT drei Dinge: ein Befehls-Topic, ein Zustands-Topic und ein Verfügbarkeits-Topic. Getrennt gehalten verhindern sie Rückkopplungsschleifen, und Dashboards zeigen den tatsächlichen Zustand an.
<prefix>/<room>/<light>/set commands in (JSON)
<prefix>/<room>/<light>/state actual state out (retained)
<prefix>/<room>/<light>/availability "online" / "offline" (retained, last will)
<prefix>/scenes/<scene> scene definitions (retained)
<prefix>/<room>/scene/set recall a scene by name
Befehle verwenden JSON, damit Sie sie später erweitern können, ohne ältere Clients zu brechen:
{"state": "ON", "brightness": 180, "transition": 2.0}
In größeren Liegenschaften gliedern Sie <prefix> zusätzlich nach Standort, Gebäude und Etage, ähnlich wie ein Unified Namespace (UNS) in der Industrie ein Werk nach den ISA-95-Ebenen ordnet; so kann ein Facility-Dashboard eine ganze Etage mit einem einzigen Wildcard-Abonnement überwachen.
brightness reicht von 0 bis 255, und transition ist die Überblendzeit in Sekunden. Dieses Format entspricht dem JSON-Schema, das die Home-Assistant-MQTT-Integration für Leuchten versteht; derselbe Dimmer kann also ohne Firmwareänderung in Home Assistant erscheinen.
Hardware
- Ein ESP32-Entwicklungsboard.
- Ein einfarbiger LED-Streifen mit 12 V oder 24 V und ein passendes Netzteil.
- Ein Logic-Level-N-Kanal-MOSFET (zum Beispiel IRLZ44N oder AO3400 auf einem Modul) pro Kanal oder eine fertige MOSFET-Treiberplatine.
- Ein 220-Ohm-Gatewiderstand und ein 10-kOhm-Pull-down pro Kanal.
Verbinden Sie das MOSFET-Gate über den Widerstand mit GPIO 25, die Source mit Masse (gemeinsam mit der ESP32-Masse) und den Drain mit dem Minuspol des LED-Streifens. Der Pluspol des Streifens geht an den Pluspol des Netzteils. Versorgen Sie einen Streifen niemals über den 5-V-Pin des ESP32.
Für Netzspannungsdimmer verwenden Sie ein zertifiziertes Produkt, das bereits MQTT spricht, etwa ein Tasmota- oder Shelly-Gerät, statt selbst Hardware für Netzspannung zu bauen. Das Topic-Design aus diesem Artikel gilt trotzdem; Hinweise zum Flashen und zur Konfiguration finden Sie unter Tasmota MQTT.
Die ESP32-Dimmer-Firmware
Der Sketch nutzt die LEDC-API des ESP32-Arduino-Core 3.x (ledcAttach und ledcWrite), PubSubClient und ArduinoJson 7. Er wendet eine Gammakurve an, damit das Dimmen für das Auge linear wirkt, blendet sanft über und veröffentlicht nach jeder Änderung einen retained Zustand.
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <math.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const int PORT = 1883;
#define PREFIX "connectmq/demo/light-8pz3/office-201/desk-strip"
const char* T_SET = PREFIX "/set";
const char* T_STATE = PREFIX "/state";
const char* T_AVAIL = PREFIX "/availability";
const int LED_PIN = 25;
const int PWM_FREQ = 5000; // Hz, oberhalb sichtbaren Flimmerns
const int PWM_BITS = 12; // 0..4095
const int PWM_MAX = (1 << PWM_BITS) - 1;
WiFiClient net;
PubSubClient mqtt(net);
bool on = false;
int brightness = 255; // letzte Helligkeit 1..255
float current = 0, target = 0; // 0..255
float step = 0;
unsigned long lastStep = 0;
void writePwm(float level) {
float norm = level / 255.0f;
int duty = (int)(powf(norm, 2.2f) * PWM_MAX + 0.5f); // Gammakorrektur
ledcWrite(LED_PIN, duty);
}
void publishState() {
JsonDocument doc;
doc["state"] = on ? "ON" : "OFF";
doc["brightness"] = brightness;
char buf[96];
size_t n = serializeJson(doc, buf);
mqtt.publish(T_STATE, (const uint8_t*)buf, n, true);
}
void startFade(float to, float seconds) {
target = to;
int steps = max(1, (int)(seconds * 50)); // 50 Aktualisierungen pro Sekunde
step = (target - current) / steps;
if (seconds <= 0) { current = target; writePwm(current); step = 0; }
}
void onMessage(char* topic, byte* payload, unsigned int len) {
JsonDocument doc;
if (deserializeJson(doc, payload, len)) return;
float transition = doc["transition"] | 0.5f;
if (doc["brightness"].is<int>()) {
brightness = constrain(doc["brightness"].as<int>(), 1, 255);
on = true;
}
if (doc["state"].is<const char*>()) {
on = strcmp(doc["state"].as<const char*>(), "ON") == 0;
}
startFade(on ? brightness : 0, transition);
publishState();
}
void connectMqtt() {
while (!mqtt.connected()) {
String id = "light-" + String((uint32_t)ESP.getEfuseMac(), HEX);
if (mqtt.connect(id.c_str(), T_AVAIL, 1, true, "offline")) {
mqtt.publish(T_AVAIL, "online", true);
mqtt.subscribe(T_SET, 1);
publishState();
} else {
delay(2000);
}
}
}
void setup() {
ledcAttach(LED_PIN, PWM_FREQ, PWM_BITS);
writePwm(0);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
mqtt.setServer(BROKER, PORT);
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (step != 0 && millis() - lastStep >= 20) {
lastStep = millis();
current += step;
if ((step > 0 && current >= target) || (step < 0 && current <= target)) {
current = target;
step = 0;
}
writePwm(current);
}
}
Beachten Sie, dass die Überblendung in loop() ohne delay() läuft, sodass das Gerät auch während des Dimmens weiter auf MQTT reagiert. Der Last Will markiert die Leuchte als offline, wenn sie Strom oder WLAN verliert.
Den Dimmer testen
# alles für diese Demo beobachten
mosquitto_sub -h broker.connectmq.com -p 1883 -i light-view-8pz3 -t "connectmq/demo/light-8pz3/#" -v
# mit etwa 70 Prozent und 2 Sekunden Überblendung einschalten
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
-t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
-m '{"state":"ON","brightness":180,"transition":2}'
# mit langsamer Überblendung ausschalten
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
-t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
-m '{"state":"OFF","transition":5}'
Der öffentliche MQTT-Broker ist für Tests und Prototypen gedacht und steht allen offen, sodass ein Fremder Ihr Demo-Licht schalten könnte. Auf dem Schreibtisch ist das harmlos, aber genau deshalb muss echte Beleuchtung auf einem privaten Broker mit Zugangsdaten laufen.
Szenen als retained Messages
Eine Szene ist ein benannter Satz von Leuchtenzuständen. Wenn Sie jede Szene als retained Message auf dem Broker speichern, sehen alle Steuerungen, Dashboards und Wandpanels dieselben Definitionen, und Sie können eine Szene ändern, indem Sie eine neue Version veröffentlichen.
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
-t "connectmq/demo/light-8pz3/scenes/focus" \
-m '{"office-201/desk-strip":{"state":"ON","brightness":230},"office-201/ceiling":{"state":"ON","brightness":200}}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
-t "connectmq/demo/light-8pz3/scenes/presentation" \
-m '{"office-201/desk-strip":{"state":"ON","brightness":40},"office-201/ceiling":{"state":"OFF"}}'
Szenenabruf und Zeitpläne in Python
Ein kleiner Dienst abonniert Szenendefinitionen und Abrufanfragen, verteilt Szenenabrufe auf die einzelnen Leuchten und führt einen Tageszeitplan aus. Er nutzt paho-mqtt 2.x und ansonsten nur die Standardbibliothek.
import json
import time
import uuid
from datetime import datetime
import paho.mqtt.client as mqtt
BROKER, PORT = "broker.connectmq.com", 1883
PREFIX = "connectmq/demo/light-8pz3"
scenes = {}
SCHEDULE = [ # (HH:MM, Wochentage 0=Mo..6=So, Szene)
("08:00", range(0, 5), "focus"),
("12:30", range(0, 5), "presentation"),
("19:00", range(0, 7), "off"),
]
scenes["off"] = {"office-201/desk-strip": {"state": "OFF", "transition": 10}}
def recall(client, name):
scene = scenes.get(name)
if not scene:
print("Unknown scene", name)
return
for light, cmd in scene.items():
cmd = {"transition": 1.5, **cmd}
client.publish(f"{PREFIX}/{light}/set", json.dumps(cmd), qos=1)
print("Recalled", name)
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(f"{PREFIX}/scenes/+", qos=1)
client.subscribe(f"{PREFIX}/+/scene/set", qos=1)
def on_message(client, userdata, msg):
parts = msg.topic.split("/")
if parts[-2] == "scenes":
if msg.payload:
scenes[parts[-1]] = json.loads(msg.payload)
elif parts[-2] == "scene" and parts[-1] == "set":
recall(client, msg.payload.decode().strip())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"light-sched-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, 60)
client.loop_start()
last_run = None
while True:
now = datetime.now()
key = now.strftime("%Y-%m-%d %H:%M")
if key != last_run:
for hhmm, days, scene in SCHEDULE:
if now.strftime("%H:%M") == hhmm and now.weekday() in days:
recall(client, scene)
last_run = key
time.sleep(5)
Eine Szene rufen Sie manuell so ab:
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 \
-t "connectmq/demo/light-8pz3/office-201/scene/set" -m "focus"
Wenn Sie für Zeitpläne und sonnenstandsabhängige Auslöser einen visuellen Editor bevorzugen, funktionieren dieselben Topics auch aus Node-RED oder aus Home-Assistant-Automatisierungen.
Erweiterung auf Tunable White und RGB
Dieselbe Firmwarestruktur lässt sich auf mehrkanalige Streifen skalieren. Ein Tunable-White-Streifen hat einen warmen und einen kalten Kanal, ein RGB- oder RGBW-Streifen drei oder vier. Binden Sie pro Farbpin einen LEDC-Kanal an und erweitern Sie den JSON-Befehl um optionale Felder, zum Beispiel "color_temp": 300 in Mired für Tunable White oder "color": {"r": 255, "g": 140, "b": 0} für RGB. Behalten Sie brightness als eigenes Feld und skalieren Sie jeden Kanal damit, sodass sich Helligkeit und Farbe unabhängig voneinander ändern lassen und eine Szene beides in einer Nachricht setzen kann.
Zwei praktische Punkte ersparen später Ärger. Mischen Sie Tunable White so, dass die Summe beider Kanäle bei gegebener Helligkeit konstant bleibt, sonst wird das Licht in der Mitte des Bereichs merklich heller. Und veröffentlichen Sie nach jedem Befehl den vollständigen resultierenden Zustand einschließlich Farbe auf dem Zustands-Topic, denn Dashboards und Home Assistant benötigen ihn, um die Position der Farbauswahl korrekt darzustellen.
Checkliste für den Produktivbetrieb
- Wechseln Sie auf einen privaten Broker auf Port 8883 mit TLS und geben Sie jedem Dimmer eigene Zugangsdaten.
- Lassen Sie Wandschalter lokal funktionieren; MQTT soll Steuerung ergänzen und nicht der einzige Weg sein, ein Licht einzuschalten.
- Kombinieren Sie die Beleuchtung mit Präsenzsensoren im Büro, damit leere Räume automatisch abschalten.
- Lassen Sie in Gebäuden mit KNX oder DALI den vorhandenen Bus schalten und überbrücken Sie ihn zu MQTT, wie in unserer KNX-MQTT-Integration.
- Verwenden Sie QoS 1 für Befehle und senden Sie immer absolute Werte, niemals "toggle", damit eine doppelte Zustellung keinen Schaden anrichtet.
| Stolperfalle | Symptom | Lösung |
|---|---|---|
| Keine Gammakorrektur | Die meisten sichtbaren Änderungen passieren am unteren Ende des Bereichs | Eine Potenzkurve wie im Sketch anwenden |
Überblendung mit delay() |
Leuchte ignoriert Befehle während des Überblendens | Überblendung schrittweise in loop() ausführen |
| Zustand nicht retained | Dashboards zeigen nach dem Wiederverbinden "unbekannt" | Zustand mit gesetztem Retain-Flag veröffentlichen |
| Befehle auf dem Zustands-Topic | Leuchten schwingen oder erzeugen Echos | Topics set und state trennen |
| Niedrige PWM-Frequenz | Sichtbares Flimmern auf Kamerabildern | 1 kHz oder mehr verwenden |
Ein verwalteter MQTT-Broker hält die Lichtsteuerung verfügbar, ohne dass ein Server im Gebäude nötig ist, und der Leitfaden zur IoT-Automatisierung zeigt, wie sich die Beleuchtung mit anderen Gebäudesystemen verbindet.
Betreiben Sie Ihre Beleuchtung mit ConnectMQ
Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ohne Kreditkarte. Flashen Sie Ihre Dimmer mit den ausgegebenen Zugangsdaten und nutzen Sie dann den MQTT-Client im Browser im ConnectMQ-Portal, um Testbefehle zu senden und Szenen abzurufen, während Sie jeden Raum in Betrieb nehmen.
Häufig gestellte Fragen
Warum JSON-Befehle statt einfacher ON- und OFF-Werte verwenden?
Mit JSON trägt eine einzige Nachricht Zustand, Helligkeit und Übergangszeit, und Sie können später Felder wie die Farbtemperatur ergänzen, ohne bestehende Clients zu brechen.
Sollten Zustands-Topics von Leuchten retained sein?
Ja. Ein retained Zustand ermöglicht es jedem Dashboard und jeder App, den korrekten Zustand sofort beim Abonnieren anzuzeigen, ohne auf die nächste Änderung zu warten.
Kann ich Leuchten mit Netzspannung über einen ESP32 steuern?
Verwenden Sie ein zertifiziertes Dimmer- oder Relaismodul für Netzspannung mit MQTT-Unterstützung, etwa ein Tasmota- oder Shelly-Gerät. Eigene Hardware für Netzspannung zu bauen ist riskant und oft nicht normkonform.
Was passiert mit geplanten Szenen, wenn der Scheduler ausfällt?
Die Leuchten behalten ihren aktuellen Zustand und bleiben über Schalter und direkte MQTT-Befehle steuerbar. Betreiben Sie den Scheduler als Dienst mit automatischem Neustart, damit verpasste Ereignisse selten bleiben.



