ESP32 und Arduino

ESP32 Deep Sleep MQTT: Batteriebetriebene Sensoren, die senden und schlafen

Bauen Sie einen batteriefreundlichen ESP32-Sensor, der per Timer aufwacht, sich schnell verbindet, einen retained Messwert veröffentlicht, prüft, ob dieser beim Broker angekommen ist, und wieder in den Deep Sleep geht.

Batteriebetriebener ESP32-Sensorknoten, der in einem ESP32-Deep-Sleep-MQTT-Zyklus zum Senden aufwacht
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

Mit ESP32 Deep Sleep MQTT holen Sie aus einem batteriebetriebenen Sensor Monate statt Tage heraus. In diesem Tutorial bauen Sie einen Knoten, der per Timer aufwacht, sich so schnell wie möglich wieder mit dem WLAN verbindet, einen retained Messwert an broker.connectmq.com veröffentlicht, sicherstellt, dass die Nachricht tatsächlich beim Broker angekommen ist, und wieder einschläft. Da der Messwert retained ist, zeigen Dashboards immer den letzten Wert, obwohl das Gerät fast die ganze Zeit offline ist.

Der Zyklus aus Aufwachen, Senden und Schlafen

Im Deep Sleep schaltet der ESP32 seine CPUs, den Großteil des RAM und das Funkmodul ab. Nur die RTC-Domäne bleibt aktiv und verbraucht einen winzigen Bruchteil des Stroms im aktiven Betrieb. Wenn der Timer auslöst, macht der Chip nicht dort weiter, wo er aufgehört hat: Er startet neu und führt setup() von vorn aus. loop() läuft in diesem Design nie.

Jeder Wachzyklus folgt denselben Schritten:

  1. Starten und den Sensor sofort auslesen, bevor das Funkmodul das Board aufheizt.
  2. Mit dem WLAN verbinden und dabei Kanal und Access Point aus dem vorherigen Zyklus wiederverwenden.
  3. Mit dem MQTT Broker verbinden und den Messwert als retained message veröffentlichen.
  4. Bestätigen, dass der Broker ihn empfangen hat.
  5. Sauber trennen, das Funkmodul abschalten und in den Deep Sleep wechseln.

Die Zeit zwischen Schritt 2 und 5 bestimmt den Energieverbrauch, weil das WLAN-Funkmodul mit Abstand den meisten Strom zieht. Jede Optimierung in diesem Artikel zielt darauf ab, dieses Zeitfenster zu verkürzen oder es zu überspringen, wenn es ohnehin scheitern würde.

Phase Relative Leistungsaufnahme So verkürzen Sie sie
Start und Sensorabfrage Niedrig bis mittel Sensor zuerst lesen, Code klein halten
WLAN-Anmeldung und DHCP Hoch Kanal und BSSID wiederverwenden, optional statische IP
MQTT-Connect und Publish Hoch Einfacher, sessionarmer Connect, ein einziger Publish
Warten auf Bestätigung Hoch Kurzer Timeout, schnell aufgeben
Deep Sleep Sehr niedrig Board-Design: Spannungsregler, LEDs, USB-Chip

Die letzte Zeile ist häufig eine Überraschung: Viele Entwicklungsboards verschwenden in Spannungsregler, Power-LED und USB-Seriell-Chip weit mehr, als der ESP32 selbst im Deep Sleep verbraucht. Für echte Batterielaufzeit messen Sie das Board, nicht den Chip. Der Begleitartikel zu batteriebetriebenen ESP32-Sensoren behandelt Hardwareauswahl und Strommessungen ausführlicher.

Warum retained messages hier unverzichtbar sind

Ein schlafender Sensor veröffentlicht einmal und verschwindet. Wenn ein Dashboard oder eine Smartphone-App eine Minute später abonniert, ist eine normale Nachricht weg. Mit gesetztem Retain-Flag speichert der Broker die letzte Nachricht auf diesem Topic und liefert sie jedem neuen Subscriber sofort aus. Das Dashboard zeigt den aktuellsten Messwert in dem Moment, in dem es geöffnet wird, egal wann das Gerät zuletzt aufgewacht ist.

Schreiben Sie einen Zeitstempel oder eine Sequenznummer in den Payload, damit Empfänger erkennen, wie alt der retained Wert ist. Ein vier Stunden alter Messwert, der aktuell aussieht, führt in die Irre, und bei einem schlafenden Gerät ist „alt" der Normalzustand. Falls retained messages neu für Sie sind, erklärt der Überblick, was ein MQTT Broker macht, wie der Broker sie speichert.

Zustellung bestätigen ohne QoS-1-Publish

PubSubClient kann nur mit QoS 0 veröffentlichen, was „gesendet" bedeutet, nicht „empfangen". Bei einem dauerhaft eingeschalteten Gerät ist das meist akzeptabel. Bei einem schlafenden Gerät ist es ein echtes Problem: Wenn Sie esp_deep_sleep_start() direkt nach publish() aufrufen, kann sich das Funkmodul abschalten, während das Paket noch im Puffer liegt, und die Nachricht verschwindet spurlos.

Der Sketch unten nutzt einen einfachen, zuverlässigen Trick. Er abonniert vor dem Veröffentlichen sein eigenes State-Topic und wartet dann, bis der Broker die neue Nachricht zurückschickt. Die eigene Nachricht als Echo zu sehen, beweist, dass der Broker sie empfangen und weitergeleitet hat. Eine im RTC-Speicher abgelegte Sequenznummer unterscheidet die frische Nachricht von der alten retained Nachricht, die der Broker beim Abonnieren ebenfalls ausliefert.

Der vollständige ESP32-Deep-Sleep-MQTT-Sketch

#include <WiFi.h>
#include <PubSubClient.h>
#include "esp_sleep.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;
const char* CLIENT_ID     = "sleepy-3e7f10";                 // eindeutig
const char* TOPIC_STATE   = "connectmq/demo/sleepy-3e7f10/state";

const uint64_t SLEEP_SECONDS   = 300;                       // 5 Minuten
const uint32_t WIFI_TIMEOUT_MS = 8000;
const uint32_t ACK_TIMEOUT_MS  = 3000;

// Überlebt den Deep Sleep (geht bei Stromverlust oder Reset verloren)
RTC_DATA_ATTR uint32_t seq = 0;
RTC_DATA_ATTR uint32_t failures = 0;
RTC_DATA_ATTR uint8_t savedBssid[6];
RTC_DATA_ATTR int32_t savedChannel = 0;
RTC_DATA_ATTR bool haveSaved = false;

WiFiClient net;
PubSubClient mqtt(net);
volatile bool acked = false;
char expected[24];

void goToSleep() {
  mqtt.disconnect();
  net.stop();
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  // Bei wiederholten Fehlern länger schlafen, damit ein totes Netz die Batterie nicht leert
  uint64_t seconds = SLEEP_SECONDS * (failures > 3 ? 4 : 1);
  Serial.printf("Sleeping %llu s (failures=%lu)\n", seconds, (unsigned long)failures);
  Serial.flush();
  esp_sleep_enable_timer_wakeup(seconds * 1000000ULL);
  esp_deep_sleep_start();
}

float readSensor() {
  // Durch Ihren Sensor ersetzen; der simulierte Wert hält das Beispiel eigenständig
  return 20.0 + (esp_random() % 500) / 100.0;
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  // Unser Payload enthält "seq":N; nach dem gerade gesendeten suchen
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  if (msg.indexOf(expected) >= 0) acked = true;
}

bool connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.persistent(false);
  if (haveSaved) {
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD, savedChannel, savedBssid, true);   // schneller Weg
  } else {
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  }
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - start > WIFI_TIMEOUT_MS) {
      haveSaved = false;              // AP hat evtl. den Kanal gewechselt: nächstes Mal voller Scan
      return false;
    }
    delay(20);
  }
  memcpy(savedBssid, WiFi.BSSID(), 6);
  savedChannel = WiFi.channel();
  haveSaved = true;
  return true;
}

void setup() {
  unsigned long t0 = millis();
  Serial.begin(115200);
  float temperature = readSensor();                 // lesen, bevor das Funkmodul das Board erwärmt
  seq++;

  if (!connectWifi()) { failures++; goToSleep(); }

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setSocketTimeout(4);
  if (!mqtt.connect(CLIENT_ID)) { failures++; goToSleep(); }

  mqtt.subscribe(TOPIC_STATE, 0);
  snprintf(expected, sizeof(expected), "\"seq\":%lu,", (unsigned long)seq);

  char payload[128];
  snprintf(payload, sizeof(payload),
           "{\"seq\":%lu,\"temperature\":%.2f,\"rssi\":%d,\"awakeMs\":%lu,\"failures\":%lu}",
           (unsigned long)seq, temperature, WiFi.RSSI(),
           millis() - t0, (unsigned long)failures);
  mqtt.publish(TOPIC_STATE, payload, true);         // retained

  unsigned long start = millis();
  while (!acked && millis() - start < ACK_TIMEOUT_MS) {
    mqtt.loop();
    delay(5);
  }
  if (acked) failures = 0; else failures++;
  Serial.printf("seq=%lu acked=%d awake=%lu ms\n", (unsigned long)seq, acked, millis() - t0);
  goToSleep();
}

void loop() {
  // Wird nie erreicht: Der ESP32 startet nach jedem Aufwachen neu in setup()
}

Was jede Optimierung bewirkt

Kanal und BSSID zwischenspeichern. Ein normales WiFi.begin() durchsucht alle Kanäle nach dem Access Point. Werden Kanal und BSSID aus dem RTC-Speicher übergeben, entfällt dieser Scan, was in der Regel die größte einzelne Einsparung ist. Wechselt der Router den Kanal, läuft der schnelle Weg in einen Timeout, der Cache wird geleert und beim nächsten Aufwachen erfolgt ein vollständiger Scan.

Sensor zuerst auslesen. Das Funkmodul erwärmt das Board spürbar, was Temperaturmessungen verfälscht. Eine Messung vor dem Verbinden vermeidet das.

Backoff bei Fehlern. Ist das Netzwerk ausgefallen, verbrennt jedes Aufwachen Energie für Versuche, die nicht gelingen können. Nach wiederholten Fehlern schläft der Knoten viermal so lange, bis wieder ein Publish erfolgreich ist.

awakeMs im Payload. Wenn das Gerät meldet, wie lange es in der vorherigen Phase wach war, erhalten Sie kostenlos einen kontinuierlichen Blick auf die Energiekosten. Steigt der Wert, hat sich etwas im Netzwerk geändert.

Kein Last Will. Ein schlafendes Gerät trennt sich in jedem Zyklus absichtlich, daher wäre ein offline-Last-Will bedeutungslos. Beurteilen Sie die Lebendigkeit stattdessen am Alter des letzten retained Messwerts.

Optional spart eine statische IP zusätzlich den DHCP-Austausch. Rufen Sie WiFi.config(ip, gateway, subnet, dns) vor WiFi.begin() auf, aber nur in Netzwerken, in denen Sie die Adressvergabe kontrollieren.

Prüfen, ob es funktioniert

Abonnieren Sie von Ihrem Rechner aus:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-3e7f10 -v -t "connectmq/demo/sleepy-3e7f10/#"

Verkürzen Sie SLEEP_SECONDS zum Testen auf 20. Sie sollten pro Zyklus eine Nachricht sehen:

connectmq/demo/sleepy-3e7f10/state {"seq":7,"temperature":22.81,"rssi":-61,"awakeMs":1180,"failures":0}

Beenden Sie nun den Subscriber und starten Sie ihn erneut, während der ESP32 schläft. Der letzte Messwert erscheint sofort, genau so, wie es ein Dashboard erlebt. Schalten Sie dann Ihren Router für einige Zyklen aus: Der serielle Monitor zeigt steigende failures, und die erste Nachricht nach der Wiederherstellung meldet, wie viele Zyklen verpasst wurden.

Der Endpunkt broker.connectmq.com ist ein kostenloser öffentlicher MQTT-Broker, der sich für solche Tests eignet. Seine Sessions laufen nach höchstens 30 Minuten ab, was hier keine Rolle spielt, weil sich der Sketch bei jedem Aufwachen mit einer frischen Session verbindet. Wenn Sie zu einem privaten ConnectMQ Broker wechseln, braucht der Sketch nur Zugangsdaten (und TLS, falls Sie es wählen); zu beachten ist lediglich das Limit von 25 Verbindungen im kostenlosen Tarif, das eine Flotte schlafender Sensoren selten erreicht, weil jeder nur einen Moment pro Zyklus verbunden ist.

Checkliste für die Batterielaufzeit

  1. Entfernen oder deaktivieren Sie die Power-LED und verwenden Sie ein Board mit Spannungsregler mit niedrigem Ruhestrom, oder versorgen Sie das Modul direkt.
  2. Messen Sie den Deep-Sleep-Strom mit einem in Reihe geschalteten Messgerät; auf einem gut entworfenen Board sollte er weit unter einem Milliampere liegen.
  3. Halten Sie das Wachfenster kurz: zwischengespeichertes WLAN, kurze Timeouts, ein Publish.
  4. Wählen Sie ein sinnvolles Intervall. Ein halbiertes Intervall verdoppelt ungefähr die Energie für das Funkmodul; die meisten Umweltmesswerte brauchen keine minutengenaue Auflösung.
  5. Versorgen Sie den Sensor über einen GPIO und schalten Sie ihn vor dem Schlafen ab, damit er die Batterie nicht im Hintergrund leert.
  6. Verzichten Sie auf TLS bei jedem Aufwachen, wenn die Batterielaufzeit entscheidend ist und das Bedrohungsmodell das in einem privaten Netzwerk zulässt; wenn Sie Verschlüsselung brauchen, planen Sie die zusätzliche Handshake-Zeit ein und folgen Sie ESP32 MQTT SSL mit WiFiClientSecure.

Fehlerbehebung

  • Nachrichten kommen nur manchmal an. Das Gerät schläft ein, bevor der Publish das Funkmodul verlassen hat. Behalten Sie die Echo-Bestätigung bei und verkürzen Sie ACK_TIMEOUT_MS nicht zu stark.
  • seq springt unerwartet auf 1 zurück. Der RTC-Speicher überlebt den Deep Sleep, aber keinen Stromausfall, keinen Brownout und keinen Druck auf Reset. Eine sinkende Batteriespannung, die Brownouts auslöst, ist eine häufige Ursache.
  • Das Gerät wacht nie auf. Prüfen Sie, dass Sie esp_sleep_enable_timer_wakeup() mit Mikrosekunden und einem 64-Bit-Wert aufrufen; ein 32-Bit-Überlauf ergibt ein viel kürzeres oder falsches Intervall.
  • Serielle Ausgabe wird abgeschnitten. Rufen Sie vor dem Schlafen Serial.flush() auf, wie es der Sketch tut.
  • Lange Wachzeiten in manchen Zyklen. Der schnelle WLAN-Weg ist gescheitert und ein vollständiger Scan lief. Stellen Sie sicher, dass Ihr Router einen festen Kanal verwendet.

Schlafende Sensoren in der Produktion betreiben

Retained Werte sind nur so verlässlich wie der Broker, der sie hält. Ein verwalteter MQTT-Broker hält diesen Endpunkt verfügbar, während Ihre Sensoren schlafen, und dieselben retained Topics bedienen Ihre Dashboards, Skripte und Apps. Mit ConnectMQ erstellen Sie im Portal Zugangsdaten pro Gerät, sehen den letzten retained Wert jedes Sensors im Live-Topic-Explorer und legen Schwellenwert-Alarmregeln für Messwerte wie die Temperatur fest. Ein Cloud-MQTT-Broker bedeutet außerdem, dass Sensoren auf dem Feld, im Keller, im Lager oder verteilt über mehrere Werkshallen ohne lokalen Server an einen einzigen Endpunkt melden können. Neu bei diesem Board? Beginnen Sie mit dem ESP32-IoT-Tutorial.

Starten Sie mit ConnectMQ

Erstellen Sie einen kostenlosen Serverless Broker unter app.connectmq.com, ohne Kreditkarte. Verbinden Sie Ihre schlafenden Sensoren mit eigenen Zugangsdaten und sehen Sie zu, wie retained Messwerte im Portal erscheinen, sobald sie aufwachen.

Häufig gestellte Fragen

Warum verliert mein ESP32 MQTT-Nachrichten vor dem Deep Sleep?

Ein Publish mit QoS 0 kehrt zurück, sobald die Daten in der Warteschlange liegen, daher kann sofortiges Einschlafen die Übertragung abschneiden. Warten Sie auf eine Bestätigung, etwa mit der Echo-Technik aus diesem Sketch, und trennen Sie dann die Verbindung, bevor das Gerät schläft.

Sollte ein Deep-Sleep-Sensor eine MQTT-Session behalten?

In der Regel nicht. Eine frische Verbindung bei jedem Aufwachen ist einfacher und funktioniert mit jedem Broker. Persistente Sessions helfen nur, wenn das Gerät Befehle empfangen muss, die während des Schlafs in die Warteschlange gestellt wurden.

Wie kann ein schlafender ESP32 Befehle empfangen?

Veröffentlichen Sie Befehle als retained messages auf einem Topic, das das Gerät beim Aufwachen abonniert. Es empfängt den neuesten Befehl sofort, führt ihn aus und löscht ihn anschließend, indem es eine leere retained message veröffentlicht.

Was ist ein gutes Sendeintervall für einen Batteriesensor?

Das hängt davon ab, wie schnell sich der gemessene Wert ändert. Umweltsensoren funktionieren oft gut mit Intervallen von fünf bis fünfzehn Minuten; kürzere Intervalle kosten entsprechend mehr Energie.

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.