ESP32 und Arduino

ESP32 MQTT Reconnect: Nicht blockierende Wiederverbindung, die Ausfälle übersteht

Ersetzen Sie die blockierende Reconnect-Schleife der meisten Beispiele durch eine nicht blockierende Zustandsmaschine für WLAN und MQTT mit exponentiellem Backoff, Jitter, Resubscribe und Offline-Puffer und beweisen Sie die Robustheit mit einem Router-Neustart.

Router und ESP32-Board stellen in einem ESP32-MQTT-Reconnect-Test die Verbindung wieder her
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

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:

  1. 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.
  2. 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.
  3. 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 retained online korrigiert 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) < 0 funktioniert auch dann noch, wenn millis() 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:

  1. Flashen Sie den Sketch und öffnen Sie den seriellen Monitor.
  2. 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/#"
  1. Prüfen Sie, dass Sie status online und alle zehn Sekunden eine Telemetrie-Nachricht sehen.
  2. Schalten Sie Ihren Router aus und wieder ein. Ihr Rechner verliert ebenfalls die Verbindung, beobachten Sie also stattdessen den seriellen Monitor.
  3. 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.
  4. Verfolgen Sie das serielle Log: Wi-Fi lost, dann nichts, während der Router startet (keine fehlgeschlagenen MQTT-Versuche, weil das WLAN fehlt), dann Wi-Fi up und anschließend MQTT connected (#2).
  5. Prüfen Sie beim Subscriber, dass status online zurückkehrt, gepufferte Telemetrie gesammelt eintrifft und reconnects um eins gestiegen ist.
  6. Senden Sie einen Befehl an connectmq/demo/esp32-a1b2c3/cmd/test und 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.

Verwandte Artikel

ESP32 NTP MQTT Zeitstempel: genaue Uhrzeit in jeder Nachricht

Synchronisieren Sie die ESP32-Uhr per NTP, formatieren Sie Zeitstempel in ISO 8601 und Epoch-Millisekunden und hängen Sie sie an MQTT-Payloads, damit Messwerte ihren echten Erfassungszeitpunkt behalten, auch wenn sie verspätet veröffentlicht werden.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.