Die Reconnect-Logik entscheidet, ob ein ESP32-MQTT-Projekt nur als Demo auf dem Schreibtisch funktioniert oder monatelang zuverlässig im Feld läuft. In dieser Anleitung ersetzen Sie die blockierende reconnect()-Schleife, die in den meisten Beispielen steckt, durch eine nicht blockierende Zustandsmaschine, die WLAN und MQTT getrennt behandelt, mit exponentiellem Backoff und Jitter wartet, automatisch neu abonniert und Messwerte puffert, solange das Gerät offline ist. Am Ende prüfen Sie das Ganze auf ehrliche Weise: indem Sie Ihren Router neu starten, während der ESP32 mit broker.connectmq.com verbunden ist.
Warum die klassische Reconnect-Schleife scheitert
Fast jedes PubSubClient-Beispiel enthält eine Variante davon:
void reconnect() {
while (!client.connected()) {
if (client.connect("esp32")) {
client.subscribe("cmd");
} else {
delay(5000);
}
}
}
Das funktioniert, bis das Netzwerk wegfällt. Dann treten mehrere Probleme gleichzeitig auf:
| Problem | Was in der Praxis passiert |
|---|---|
Endlose while-Schleife |
Nichts anderes läuft mehr: Taster, Sensoren, Displays und Motorsteuerung frieren ein, bis der Broker wieder erreichbar ist |
delay(5000) |
Wie oben, zusätzlich kann in manchen Konfigurationen der Task Watchdog auslösen |
| Keine WLAN-Prüfung | Der Sketch versucht pausenlos MQTT-Verbindungen, obwohl das WLAN selbst ausgefallen ist, und jeder Versuch wartet auf DNS- oder TCP-Timeouts |
| Festes Wiederholungsintervall | Hunderte Geräte, die sich vom selben Ausfall erholen, verbinden sich im Gleichschritt und treffen den Broker im selben Moment |
| Fest codierte Client ID | Zwei Boards mit derselben Firmware werfen sich gegenseitig endlos aus der Verbindung |
Die Lösung: Konnektivität als Hintergrundaufgabe behandeln. loop() sollte unter allen Umständen alle paar Millisekunden laufen, und Verbindungsversuche finden nur statt, wenn sie fällig sind.
Die Statuscodes von PubSubClient verstehen
Bevor Sie etwas verbessern, protokollieren Sie bei jedem Fehlschlag client.state(). Die Codes verraten, ob das Problem beim Netzwerk, beim Broker oder bei Ihren Zugangsdaten liegt.
| Code | Konstante | Bedeutung | Typische Ursache |
|---|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT |
Keine Antwort innerhalb des Keep-Alive | Netzwerk hängt, schwaches WLAN |
| -3 | MQTT_CONNECTION_LOST |
Verbindung abgebrochen | Router-Neustart, Broker-Neustart, doppelte Client ID |
| -2 | MQTT_CONNECT_FAILED |
TCP-Verbindung fehlgeschlagen | Falscher Host oder Port, DNS-Fehler, kein Internet |
| -1 | MQTT_DISCONNECTED |
Sauber getrennt | Sie haben disconnect() aufgerufen |
| 1 | MQTT_CONNECT_BAD_PROTOCOL |
Protokoll nicht unterstützt | Selten bei 3.1.1-Brokern |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID |
Client ID abgelehnt | Leere oder ungültige ID |
| 3 | MQTT_CONNECT_UNAVAILABLE |
Server nicht verfügbar | Broker überlastet oder startet neu |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS |
Falscher Benutzername/Passwort | Tippfehler, rotiertes Passwort |
| 5 | MQTT_CONNECT_UNAUTHORIZED |
Nicht autorisiert | Zugangsdaten für diese Client ID nicht zugelassen |
Die Codes 4 und 5 sind nicht vorübergehend. Alle paar Sekunden erneut zu versuchen, behebt sie nicht, sondern erzeugt nur weitere fehlgeschlagene Logins. Viele Broker, ConnectMQ eingeschlossen, sperren IP-Adressen nach wiederholten Fehlanmeldungen, sodass sich ein Gerät, das mit falschen Zugangsdaten in einer Schleife hängt, selbst aussperren kann. Die Firmware unten wartet bei diesen Codes deutlich länger. Wenn Sie schon beim allerersten Versuch -2 erhalten, ist das gar kein Reconnect-Problem; gehen Sie dann zuerst durch, warum sich ein ESP32 nicht mit dem MQTT-Broker verbinden kann.
Aufbau eines nicht blockierenden ESP32 MQTT Reconnect
Die Firmware trennt drei Ebenen:
- WLAN. Überlassen Sie das Funkmodul der eingebauten Auto-Reconnect-Funktion des ESP32 und hören Sie auf WLAN-Events, um zu wissen, wann die Verbindung steht. Rufen Sie
WiFi.begin()nicht in einer engen Schleife auf. - MQTT. Versuchen Sie eine Verbindung nur, wenn das WLAN eine IP-Adresse hat und der Backoff-Timer abgelaufen ist. Bei Erfolg setzen Sie den Backoff zurück, veröffentlichen einen
online-Status und abonnieren neu. Bei einem Fehlschlag verdoppeln Sie die Wartezeit bis zu einer Obergrenze und addieren zufälligen Jitter. - Anwendung. Lesen Sie Sensoren aus und steuern Sie Aktoren in jedem Schleifendurchlauf, unabhängig von der Verbindung. Veröffentlichen Sie, wenn eine Verbindung besteht; andernfalls legen Sie Messwerte in einen kleinen Ringpuffer und senden sie nach dem Reconnect nach.
Exponentieller Backoff mit Jitter ist das Standardmuster für Wiederholungen gegenüber einem gemeinsam genutzten Dienst. Der Jitter ist wichtiger, als viele denken: Nach einem regionalen Ausfall verteilt er Tausende Reconnects über ein Zeitfenster statt auf eine einzige Spitze.
Der vollständige Sketch
#include <WiFi.h>
#include <PubSubClient.h>
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;
String clientId; // aus der MAC-Adresse gebildet
String base; // connectmq/demo/<clientId>
WiFiClient net;
PubSubClient mqtt(net);
// ---- Backoff-Zustand ----
const uint32_t BACKOFF_MIN = 1000; // 1 s
const uint32_t BACKOFF_MAX = 60000; // 60 s
const uint32_t BACKOFF_AUTH = 300000; // 5 min bei Zugangsdatenfehlern
uint32_t backoff = BACKOFF_MIN;
unsigned long nextAttempt = 0;
volatile bool wifiUp = false;
uint32_t reconnects = 0;
// ---- Offline-Ringpuffer ----
const int BUF_SIZE = 20;
String pending[BUF_SIZE];
int head = 0, count = 0;
void onWiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
wifiUp = true;
Serial.printf("Wi-Fi up, IP %s\n", WiFi.localIP().toString().c_str());
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
if (wifiUp) Serial.println("Wi-Fi lost");
wifiUp = false;
break;
default:
break;
}
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("cmd on %s: %.*s\n", topic, (int)length, (const char*)payload);
}
void subscribeAll() {
// Nach JEDEM erfolgreichen Connect aufrufen: Abonnements überleben
// einen Reconnect mit Clean Session nicht.
mqtt.subscribe((base + "/cmd/#").c_str(), 1);
}
void flushPending() {
while (count > 0 && mqtt.connected()) {
int tail = (head - count + BUF_SIZE) % BUF_SIZE;
if (!mqtt.publish((base + "/telemetry").c_str(), pending[tail].c_str())) break;
count--;
mqtt.loop();
}
}
void scheduleRetry(int state) {
bool authError = (state == MQTT_CONNECT_BAD_CREDENTIALS || state == MQTT_CONNECT_UNAUTHORIZED);
uint32_t wait = authError ? BACKOFF_AUTH : backoff;
uint32_t jitter = random(0, wait / 4 + 1);
nextAttempt = millis() + wait + jitter;
if (!authError) backoff = min(backoff * 2, BACKOFF_MAX);
Serial.printf("MQTT connect failed (state %d), retry in %lu ms\n", state, (unsigned long)(wait + jitter));
}
void maintainMqtt() {
if (mqtt.connected()) {
mqtt.loop();
return;
}
if (!wifiUp || (long)(millis() - nextAttempt) < 0) return;
String status = base + "/status";
bool ok = mqtt.connect(clientId.c_str(), nullptr, nullptr,
status.c_str(), 1, true, "offline");
if (!ok) {
scheduleRetry(mqtt.state());
return;
}
reconnects++;
backoff = BACKOFF_MIN;
Serial.printf("MQTT connected (#%lu)\n", (unsigned long)reconnects);
mqtt.publish(status.c_str(), "online", true);
subscribeAll();
flushPending();
}
void publishReading(const String& json) {
if (mqtt.connected() && mqtt.publish((base + "/telemetry").c_str(), json.c_str())) return;
pending[head] = json; // offline puffern
head = (head + 1) % BUF_SIZE;
if (count < BUF_SIZE) count++; // ältester Eintrag wird überschrieben, wenn voll
}
void setup() {
Serial.begin(115200);
uint8_t mac[6];
WiFi.mode(WIFI_STA);
WiFi.macAddress(mac);
char id[24];
snprintf(id, sizeof(id), "esp32-%02x%02x%02x", mac[3], mac[4], mac[5]);
clientId = id;
base = "connectmq/demo/" + clientId;
randomSeed(esp_random());
WiFi.onEvent(onWiFiEvent);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
mqtt.setSocketTimeout(5); // begrenzt, wie lange ein hängender Connect blockieren kann
}
unsigned long lastSample = 0;
void loop() {
maintainMqtt();
// Anwendungslogik läuft immer, ob verbunden oder nicht
if (millis() - lastSample >= 10000) {
lastSample = millis();
String json = "{\"uptime\":" + String(millis() / 1000) +
",\"rssi\":" + String(WiFi.RSSI()) +
",\"reconnects\":" + String(reconnects) + "}";
publishReading(json);
}
}
Anmerkungen zu den wichtigen Zeilen
- Client ID aus der MAC-Adresse. Jedes Board erhält eine stabile, eindeutige ID, ohne dass Sie den Sketch anpassen müssen. Doppelte IDs sind die häufigste Ursache für rätselhafte
-3-Schleifen, weil der Broker die ältere Session trennt, sobald sich die neuere verbindet. Mit Zugangsdaten aus dem Portal verwenden Sie stattdessen die dort vergebene Client ID, da ConnectMQ Client IDs an Zugangsdaten bindet. subscribeAll()nach jedem Connect. PubSubClient verbindet sich mit Clean Session, daher vergisst der Broker die Abonnements, sobald die Verbindung abbricht. Ein vergessenes Resubscribe ist der klassische Fehler „verbindet sich neu, empfängt aber keine Befehle mehr".- Retained
online-/offline-Status. Der Last Will teilt allen mit, dass das Gerät weg ist; das retainedonlinekorrigiert das nach der Wiederherstellung. Der Leitfaden zu ESP32 MQTT Last Will behandelt dieses Muster ausführlich. setSocketTimeout(5). Auch ein nicht blockierendes Design blockiert während des eigentlichen Connect-Aufrufs, weil DNS-Abfrage, TCP-Handshake und das Warten auf CONNACK in PubSubClient synchron ablaufen. Ein kurzer Timeout begrenzt diese Pause.- Überlaufsicherer Timer.
(long)(millis() - nextAttempt) < 0funktioniert auch dann noch, wennmillis()nach etwa 49 Tagen überläuft.
Testen: Router neu starten
Eine Reconnect-Strategie, die Sie nicht getestet haben, ist eine Hoffnung, keine Strategie. Hier ein Testplan, der zehn Minuten dauert:
- Flashen Sie den Sketch und öffnen Sie den seriellen Monitor.
- Abonnieren Sie auf Ihrem Rechner die Topics des Geräts (ersetzen Sie die ID durch die im seriellen Monitor ausgegebene):
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-test01 -v -t "connectmq/demo/esp32-a1b2c3/#"
- Prüfen Sie, dass Sie
status onlineund alle zehn Sekunden eine Telemetrie-Nachricht sehen. - Schalten Sie Ihren Router aus und wieder ein. Ihr Rechner verliert ebenfalls die Verbindung, beobachten Sie also stattdessen den seriellen Monitor.
- Nach etwa dem 1,5-Fachen des Keep-Alive (hier 45 Sekunden) veröffentlicht der Broker den Last Will. Sie sehen ihn, sobald Ihr Laptop wieder verbunden ist.
- Verfolgen Sie das serielle Log:
Wi-Fi lost, dann nichts, während der Router startet (keine fehlgeschlagenen MQTT-Versuche, weil das WLAN fehlt), dannWi-Fi upund anschließendMQTT connected (#2). - Prüfen Sie beim Subscriber, dass
status onlinezurückkehrt, gepufferte Telemetrie gesammelt eintrifft undreconnectsum eins gestiegen ist. - Senden Sie einen Befehl an
connectmq/demo/esp32-a1b2c3/cmd/testund vergewissern Sie sich, dass das Gerät ihn weiterhin empfängt. Das beweist, dass das Resubscribe funktioniert.
Wiederholen Sie den Test mit unerreichbarem Broker bei funktionierendem WLAN, zum Beispiel indem Sie MQTT_HOST vorübergehend auf einen nicht existierenden Namen ändern. Die Wiederholungsintervalle sollten von etwa einer Sekunde bis zur Obergrenze von 60 Sekunden wachsen, wegen des Jitters jedes Mal mit leicht unterschiedlichen Werten.
Reconnect-Probleme beheben
- Reconnect alle paar Sekunden mit Status -3. Ein anderes Gerät oder ein vergessener Test-Client verwendet dieselbe Client ID.
- Verbunden, aber nach einem Reconnect kommen keine Befehle mehr an. Das Resubscribe fehlt.
- Status -4 bei schwachem Signal. Erhöhen Sie das Keep-Alive moderat, verbessern Sie die Antennenposition und protokollieren Sie den RSSI wie gezeigt.
- Gerät erholt sich nach einem langen Ausfall nicht ohne Neustart. Irgendetwas blockiert dauerhaft, oft ein HTTP-Aufruf oder eine Sensorbibliothek. Ergänzen Sie den Task Watchdog, wie in ESP32 Task Watchdog mit MQTT beschrieben.
- Ein Schwall alter Nachrichten überrascht das Dashboard. Fügen Sie gepufferten Messwerten einen Zeitstempel hinzu, damit Empfänger verspätete von aktuellen Daten unterscheiden können.
Wann die Broker-Seite zählt
Gute Firmware geht davon aus, dass der Broker unerreichbar sein kann, aber wie oft das passiert, hängt davon ab, wo der Broker läuft. Eine Mosquitto-Instanz auf einem Heimserver oder einem vergessenen Industrie-PC in der Halle fällt mit dem Strom, dem Betriebssystem-Update und dem abgelaufenen Zertifikat aus. In der Fertigung, wo ein Knoten Maschinendaten für OEE-Kennzahlen oder Energiemonitoring liefert, bedeutet jede solche Lücke fehlende Werte im MES oder Dashboard; Ringpuffer und Zeitstempel in der Firmware fangen kurze Ausfälle ab, lange nicht. Ein verwalteter MQTT-Broker verlagert diese Sorgen zu einem Team, das den Dienst für Sie betreibt. ConnectMQ stellt außerdem $SYS/broker/connection/{clientId}/state bereit, sodass ein Backend den Verbindungsstatus eines Geräts überwachen kann, und mit dem Live-Topic-Explorer im Portal sehen Sie beim Testen, wie retained Status-Topics zwischen online und offline wechseln. Als Hintergrund zu Keep-Alive, Sessions und Last Will ist der Überblick, was ein MQTT-Broker macht, eine gute Ergänzung. Für diese Übung genügt der öffentliche MQTT-Broker, der für Tests und Prototypen gedacht ist; dieselbe Firmware wechselt zu privaten Zugangsdaten und TLS, indem Sie drei Konstanten und die Client-Klasse ändern, wie in ESP32 MQTT über SSL/TLS gezeigt. Für den gesamten ESP32-Weg lesen Sie das ESP32-IoT-Tutorial.
Starten Sie mit ConnectMQ
Erstellen Sie einen kostenlosen Serverless Broker unter app.connectmq.com, ohne Kreditkarte. Sie erhalten eigene Zugangsdaten, TLS auf Port 8883 und ein Portal, das anzeigt, wann sich jedes Gerät verbindet und trennt, genau das, was Sie beim Feintuning des Reconnect-Verhaltens brauchen.
Häufig gestellte Fragen
Wie oft sollte ein ESP32 eine MQTT-Verbindung erneut versuchen?
Beginnen Sie bei etwa einer Sekunde und verdoppeln Sie die Wartezeit nach jedem Fehlschlag bis zu einer Obergrenze von etwa einer Minute, jeweils mit zufälligem Jitter. Bei Zugangsdatenfehlern warten Sie deutlich länger oder brechen ab und melden das Problem, denn Wiederholungen können nicht erfolgreich sein.
Muss ich nach einem ESP32 MQTT Reconnect neu abonnieren?
Mit PubSubClient ja. Die Bibliothek nutzt eine Clean Session, daher verwirft der Broker die Abonnements, wenn die Verbindung endet. Rufen Sie Ihre Subscribe-Funktion nach jedem erfolgreichen Connect auf.
Sollte ich WiFi.begin() erneut aufrufen, wenn das WLAN abbricht?
In der Regel nicht. Mit WiFi.setAutoReconnect(true) verbindet sich der ESP32 selbstständig neu. Wiederholte Aufrufe von WiFi.begin() können eine bereits laufende Anmeldung am Access Point unterbrechen.
Warum trennt mein ESP32 ständig die Verbindung mit Status -3?
Die häufigste Ursache sind zwei Clients, die sich eine Client ID teilen; der Broker trennt den älteren jedes Mal, wenn sich der andere verbindet. Die nächsten Verdächtigen sind schwaches WLAN und ein Keep-Alive, das länger ist als der Idle-Timeout eines NAT-Routers oder einer Firewall.



