Mit einer ESP32-Relais-MQTT-Steuerung schalten Sie Lampen, Pumpen, Lüfter oder Schütze aus jeder App, jedem Skript oder Dashboard, das eine Nachricht veröffentlichen kann. In diesem Tutorial verdrahten Sie ein Relaismodul mit einem ESP32, abonnieren ein Befehls-Topic, veröffentlichen den Relaiszustand als Retained Message und stellen nach einem Neustart den zuletzt bekannten Zustand wieder her. Alles wird zunächst gegen den kostenlosen öffentlichen MQTT-Broker broker.connectmq.com getestet und dann für den Produktivbetrieb auf Zugangsdaten umgestellt.
So funktioniert das ESP32-Relais-MQTT-Design
Ein Relaisknoten ist der einfachste nützliche Aktor im IoT, aber genau hier gehen viele Projekte schief. Der übliche Fehler: MQTT wird wie ein Fernbedienungsknopf behandelt. Irgendetwas veröffentlicht ON, der ESP32 schaltet das Relais, und niemand weiß je, ob der Befehl wirklich angekommen ist. Ein besseres Design trennt, was jemand möchte, von dem, was das Gerät tatsächlich tut.
| Topic | Richtung | Retained | Payload |
|---|---|---|---|
connectmq/demo/<id>/relay/1/set |
App zum ESP32 | Nein | ON, OFF, TOGGLE |
connectmq/demo/<id>/relay/1/state |
ESP32 zu den Apps | Ja | ON oder OFF |
connectmq/demo/<id>/status |
ESP32 zu den Apps | Ja | online oder offline (Last Will) |
Das Befehls-Topic ist nicht retained. Wäre es das, würde jeder Reconnect einen alten Befehl erneut abspielen, und ein Relais, das jemand manuell ausgeschaltet hat, könnte plötzlich wieder angehen. Das State-Topic ist retained, sodass ein Dashboard, das eine Minute später geöffnet wird, sofort den aktuellen Zustand erhält, ohne auf die nächste Änderung zu warten. Das status-Topic nutzt den Last Will von MQTT, damit Subscriber erfahren, wenn der Knoten aus dem Netz fällt.
Sind Retained Messages und Last Will neu für Sie, erklärt der Überblick was ein MQTT-Broker macht beides in wenigen Minuten.
Hardware und Verdrahtung
Sie brauchen:
- Ein beliebiges ESP32-Entwicklungsboard (DevKitC, NodeMCU-32S, WROOM- oder WROVER-Module funktionieren alle).
- Ein Relaismodul mit 1, 2 oder 4 Kanälen und Optokoppler-Eingang. Für 5 V angebotene Module mit Jumpern für "High/Low Level Trigger" sind am flexibelsten.
- Jumperkabel und ein USB-Kabel. Für alles, was am Netz hängt, ein geeignetes Gehäuse und Reihenklemmen.
Typische Verdrahtung eines 5-V-Moduls:
| Pin am Relaismodul | ESP32-Pin | Hinweis |
|---|---|---|
| VCC | 5V (VIN) | Versorgt die Relaisspule |
| GND | GND | Gemeinsame Masse |
| IN1 | GPIO 26 | Steuersignal für Kanal 1 |
| IN2 | GPIO 27 | Steuersignal für Kanal 2 (optional) |
Die meisten günstigen Relaisboards sind active-low: Das Relais zieht an, wenn der Eingang auf GND gezogen wird. Im Sketch unten kehrt eine einzige Konstante diese Logik um. Meiden Sie für Relais GPIO 0, 2, 12 und 15, denn das sind Strapping-Pins, und ein Relais, das beim Booten klickt, ist lästig und bei manchen Lasten gefährlich. GPIO 34 bis 39 sind reine Eingänge und funktionieren nicht.
Ein Sicherheitshinweis: Wenn Sie Netzspannung schalten, muss das Relaismodul für die Last ausgelegt, die Hochvoltseite eingehaust und die Arbeit gemäß den örtlichen Elektrovorschriften ausgeführt sein. Schalten Sie während der Firmware-Entwicklung einen Niedervolt-LED-Streifen oder gar nichts und horchen Sie auf das Klicken. In Maschinen und Anlagen gilt zusätzlich: Ein per MQTT geschaltetes Relais ersetzt keine SPS und keine sicherheitsgerichtete Steuerung. Es eignet sich für Hilfsfunktionen wie Beleuchtung, Lüfter oder Signalleuchten, nicht für sicherheitsrelevante Abschaltungen.
Die Bibliotheken installieren
Installieren Sie in der Arduino IDE 2.x das Board-Paket esp32 von Espressif über den Boardverwalter und die Bibliothek PubSubClient von Nick O'Leary über den Bibliotheksverwalter. Weitere Bibliotheken sind nicht nötig. Haben Sie noch keinen MQTT-Sketch auf dem ESP32 gebaut, führt das ESP32-IoT-Tutorial ausführlicher durch die Board-Einrichtung und Ihren ersten Publish.
Der vollständige ESP32-Relais-MQTT-Sketch
Der folgende Sketch unterstützt beliebig viele Relais, behandelt Reconnects, ohne die Schleife zu blockieren, und veröffentlicht nach jeder Änderung den Zustand retained.
#include <WiFi.h>
#include <PubSubClient.h>
// ---------- Konfiguration ----------
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* DEVICE_ID = "relay-7f3a91"; // eindeutig machen
const String BASE = String("connectmq/demo/") + DEVICE_ID;
const bool ACTIVE_LOW = true; // die meisten Relaisboards
const uint8_t RELAY_PINS[] = {26, 27};
const uint8_t RELAY_COUNT = sizeof(RELAY_PINS);
bool relayState[RELAY_COUNT] = {false};
bool restoreWindow = true; // retained Zustand einmal beim Booten übernehmen
unsigned long connectedAt = 0;
WiFiClient net;
PubSubClient mqtt(net);
unsigned long lastAttempt = 0;
void applyRelay(uint8_t i, bool on) {
relayState[i] = on;
digitalWrite(RELAY_PINS[i], (on ^ ACTIVE_LOW) ? HIGH : LOW);
}
void publishState(uint8_t i) {
String topic = BASE + "/relay/" + String(i + 1) + "/state";
mqtt.publish(topic.c_str(), relayState[i] ? "ON" : "OFF", true);
}
void onMessage(char* topic, byte* payload, unsigned int length) {
String t(topic);
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
msg.toUpperCase();
// Erwartet: <BASE>/relay/<n>/set oder <BASE>/relay/<n>/state
int start = BASE.length() + 7; // "/relay/" überspringen
int slash = t.indexOf('/', start);
if (slash < 0) return;
int n = t.substring(start, slash).toInt();
if (n < 1 || n > RELAY_COUNT) return;
uint8_t i = n - 1;
String leaf = t.substring(slash + 1);
if (leaf == "state") {
// Retained Zustand von vor dem Neustart: einmal wiederherstellen, dann ignorieren
if (restoreWindow) applyRelay(i, msg == "ON");
return;
}
if (leaf != "set") return;
if (msg == "ON") applyRelay(i, true);
else if (msg == "OFF") applyRelay(i, false);
else if (msg == "TOGGLE") applyRelay(i, !relayState[i]);
else return; // unbekannte Befehle ignorieren
publishState(i);
Serial.printf("Relay %d -> %s\n", n, relayState[i] ? "ON" : "OFF");
}
bool connectMqtt() {
String willTopic = BASE + "/status";
bool ok = mqtt.connect(DEVICE_ID, nullptr, nullptr,
willTopic.c_str(), 1, true, "offline");
if (!ok) {
Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
return false;
}
mqtt.publish(willTopic.c_str(), "online", true);
for (uint8_t i = 0; i < RELAY_COUNT; i++) {
String base = BASE + "/relay/" + String(i + 1);
mqtt.subscribe((base + "/set").c_str(), 1);
if (restoreWindow) mqtt.subscribe((base + "/state").c_str(), 1);
}
connectedAt = millis();
Serial.println("MQTT connected");
return true;
}
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < RELAY_COUNT; i++) {
pinMode(RELAY_PINS[i], OUTPUT);
applyRelay(i, false); // sicherer Standard vor dem WLAN
}
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
mqtt.setSocketTimeout(5);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) return;
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) {
lastAttempt = millis();
connectMqtt();
}
return;
}
mqtt.loop();
// Das Wiederherstellungsfenster zwei Sekunden nach der ersten Verbindung schließen
if (restoreWindow && millis() - connectedAt > 2000) {
restoreWindow = false;
for (uint8_t i = 0; i < RELAY_COUNT; i++) {
String s = BASE + "/relay/" + String(i + 1) + "/state";
mqtt.unsubscribe(s.c_str());
publishState(i); // tatsächlichen Zustand bestätigen
}
}
}
Was das Wiederherstellungsfenster bewirkt
Startet der ESP32 nach einem Stromausfall neu, beginnen die Relais in der sicheren Stellung OFF. Bei der ersten Verbindung abonniert der Sketch kurz seine eigenen retained state-Topics. Der Broker liefert sofort den zuletzt gespeicherten Zustand, der Sketch übernimmt ihn, kündigt nach zwei Sekunden das Abonnement und veröffentlicht den tatsächlichen Zustand erneut. Sollen die Relais immer aus starten, setzen Sie restoreWindow auf false. Bei kritischen Lasten wie Heizungen ist "nach Neustart immer aus" meist die sicherere Strategie.
Bedenken Sie, dass dies voraussetzt, dass der Broker die Retained Message noch vorhält. Retained Messages leben so lange, wie der Broker sie aufbewahrt. Betrachten Sie die Wiederherstellung daher als Komfortfunktion und nicht als Garantie, und legen Sie die Last so aus, dass OFF immer ein akzeptabler Zustand ist.
Das Relais vom Computer aus testen
Öffnen Sie ein Terminal und beobachten Sie alles unter Ihrem Geräte-Präfix:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-7f3a91 -v -t "connectmq/demo/relay-7f3a91/#"
Senden Sie in einem zweiten Terminal Befehle:
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m TOGGLE
Sie sollten das Klicken hören und den retained Zustand als Echo sehen:
connectmq/demo/relay-7f3a91/status online
connectmq/demo/relay-7f3a91/relay/1/state ON
connectmq/demo/relay-7f3a91/relay/1/state OFF
Ziehen Sie nun den Stecker des ESP32. Innerhalb von etwa anderthalb Keep-Alive-Intervallen veröffentlicht der Broker den Last Will, und der Subscriber gibt status offline aus. Genau dieses Signal braucht ein Dashboard, um den Schalter auszugrauen. Da broker.connectmq.com ein öffentlicher MQTT-Broker für Tests und Prototypen ist, kann jeder, der Ihr Topic errät, Ihr Relais schalten. Lassen Sie daher nie eine echte Last am öffentlichen MQTT-Broker angeschlossen.
Checkliste zur Fehlerbehebung
- Relais klickt beim Booten. Sie nutzen einen Strapping-Pin, oder das Modul ist active-low und der Pin floatet, bevor
setup()läuft. Wechseln Sie auf GPIO 26/27/32/33 und setzen Sie den Pegel nach Möglichkeit vorpinMode. - Relais schaltet nie, obwohl die LED auf dem Modul leuchtet. Viele Module brauchen 5 V an VCC für die Spule; 3,3 V lassen die LED leuchten, reichen aber nicht, um die Kontakte anzuziehen. Manche Boards benötigen außerdem den gesteckten
JD-VCC-Jumper. - Befehle funktionieren einmal, dann nicht mehr. Prüfen Sie das serielle Log auf
state=-3(Verbindung verloren). Dieselbe Client ID auf zwei Geräten führt dazu, dass sie sich immer wieder gegenseitig hinauswerfen. - ESP32 startet neu, wenn das Relais schaltet. Induktive Lasten verursachen Spannungseinbrüche und Störungen. Versorgen Sie die Relaisspule aus einem separaten 5-V-Netzteil mit gemeinsamer Masse und setzen Sie auf der Lastseite ein Snubber-Glied oder eine Freilaufdiode ein.
- State-Topic zeigt nach dem Neustart den falschen Wert. Etwas anderes veröffentlicht Retained Messages auf dem
state-Topic. Nur das Gerät selbst sollte dort jemals veröffentlichen. - Es kommt nichts an. Topic-Namen unterscheiden Groß- und Kleinschreibung, und ein überzähliger Schrägstrich am Ende erzeugt ein anderes Topic.
Mit Zugangsdaten und TLS in den Produktivbetrieb
Für eine echte Installation legen Sie im ConnectMQ-Portal einen Broker an und verwenden die generierten Werte für Benutzername, Passwort und Client ID. Wechseln Sie von WiFiClient zu WiFiClientSecure, verbinden Sie sich mit Port 8883 und übergeben Sie die Zugangsdaten an connect():
#include <WiFiClientSecure.h>
WiFiClientSecure net;
// in setup(): net.setCACert(ROOT_CA_PEM); // ISRG Root X1 (Let's Encrypt)
mqtt.setServer("broker.connectmq.com", 8883);
mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD",
willTopic.c_str(), 1, true, "offline");
Da Client IDs an die ausgestellten Zugangsdaten gebunden sind, verwenden Sie die vom Portal generierte Client ID statt einer selbst ausgedachten. Die vollständige Zertifikatseinrichtung einschließlich Zeitsynchronisation und Speicheraspekten behandelt ESP32 MQTT über SSL/TLS. Muss Ihr Relaisknoten instabiles WLAN überstehen, lohnt sich zusätzlich die Backoff-Strategie aus ESP32-MQTT-Reconnect-Logik.
Ein verwalteter MQTT-Broker ist gerade bei Aktoren entscheidend: Ein Relais, das Sie nicht erreichen, ist schlimmer als ein Sensor, den Sie nicht auslesen können. Mit ConnectMQ erhalten Sie TLS auf 8883, Zugangsdaten pro Gerät und ein Portal, in dem Sie relay/+/state im Live-Topic-Explorer beobachten oder einen Testbefehl über den Browser-Client senden können, ohne ein Terminal zu öffnen.
Ideen zur Erweiterung des Relaisknotens
- Timer. Akzeptieren Sie
{"state":"ON","seconds":600}als JSON und schalten Sie nach zehn Minuten automatisch ab. Halten Sie den Timer auf dem Gerät, damit die Last auch bei Netzwerkausfall abschaltet. - Lokaler Taster. Ergänzen Sie einen Taster, der das Relais umschaltet und den neuen Zustand veröffentlicht, damit physischer Schalter und App nie voneinander abweichen.
- Verriegelungen. Bei Motorwendeschaltungen oder Zwei-Wege-Ventilen verhindern Sie in der Firmware, nicht in der App, dass beide Kanäle gleichzeitig eingeschaltet werden.
- Servo oder LEDs als Geschwister. Dasselbe Befehls-/Zustandsmuster funktioniert für die Servosteuerung per MQTT und adressierbare LED-Streifen.
Starten Sie mit ConnectMQ
Erstellen Sie einen kostenlosen Serverless Broker unter app.connectmq.com, ohne Kreditkarte. In weniger als einer Minute haben Sie Zugangsdaten, TLS ab Werk und einen Live-Topic-Explorer, in dem Sie Ihre Relais schalten sehen. Wachsen Sie über den kostenlosen Plan hinaus, zieht dieselbe Firmware auf einen kostenpflichtigen oder dedizierten Plan um, ohne dass Sie mehr als die Zugangsdaten ändern müssen.
Häufig gestellte Fragen
Sollte das Befehls-Topic für das Relais retained sein?
Nein. Setzen Sie Retain beim State-Topic, nicht beim Befehls-Topic. Ein retained Befehl würde bei jedem Reconnect erneut abgespielt und kann eine Last unerwartet schalten.
Welche QoS sollte ich für Relaisbefehle verwenden?
QoS 1 ist die praktische Wahl: Der Befehl wird mindestens einmal zugestellt, und da ON und OFF idempotent sind, richtet ein Duplikat keinen Schaden an. PubSubClient kann mit QoS 0 oder 1 abonnieren.
Kann ich mehrere Relais mit einem ESP32 steuern?
Ja. Ergänzen Sie die Pins in RELAY_PINS, und jeder erhält eigene Topics relay/<n>/set und relay/<n>/state. Ein einzelnes Wildcard-Abonnement auf relay/+/set funktioniert ebenfalls, wenn Sie die Kanalnummer aus dem Topic auslesen.
Woran erkenne ich, dass der ESP32 offline ist?
Abonnieren Sie das retained status-Topic. Der ESP32 veröffentlicht beim Verbinden online, und der Broker veröffentlicht die Last-Will-Nachricht offline, wenn die Verbindung ohne sauberes Trennen abbricht.



