ESP32 deep sleep MQTT es la forma de conseguir meses en lugar de días de un sensor alimentado por batería. En este tutorial construirás un nodo que despierta con un temporizador, se reconecta al Wi-Fi lo más rápido posible, publica una lectura retenida en broker.connectmq.com, confirma que el mensaje llegó realmente al broker y vuelve a dormir. Como la lectura es retenida, los dashboards siempre muestran el último valor, aunque el dispositivo esté offline casi todo el tiempo.
El ciclo despertar, publicar, dormir
En deep sleep el ESP32 apaga sus CPU, la mayor parte de la RAM y la radio. Solo el dominio RTC permanece activo, consumiendo una fracción mínima de la corriente en activo. Cuando salta el temporizador, el chip no continúa donde se quedó: se reinicia y ejecuta setup() desde el principio. En este diseño loop() nunca se ejecuta.
Cada ciclo de despertar sigue los mismos pasos:
- Arrancar y leer el sensor inmediatamente, antes de que la radio caliente la placa.
- Conectarse al Wi-Fi reutilizando el canal y el punto de acceso guardados en el ciclo anterior.
- Conectarse al broker MQTT y publicar la lectura como mensaje retenido.
- Confirmar que el broker la recibió.
- Desconectarse limpiamente, apagar la radio y entrar en deep sleep.
El tiempo que transcurre entre los pasos 2 y 5 domina el consumo de energía, porque la radio Wi-Fi es, con diferencia, lo que más corriente consume. Todas las optimizaciones de este artículo buscan acortar esa ventana o saltársela cuando de todos modos fallaría.
| Fase | Consumo relativo | Cómo acortarla |
|---|---|---|
| Arranque y lectura del sensor | Bajo a moderado | Leer primero el sensor, mantener el código pequeño |
| Asociación Wi-Fi y DHCP | Alto | Reutilizar canal y BSSID, IP estática opcional |
| Conexión MQTT y publicación | Alto | Conexión simple o con sesión ligera, una sola publicación |
| Espera de confirmación | Alto | Timeout corto, rendirse rápido |
| Deep sleep | Muy bajo | Diseño de la placa: regulador, LED, chip USB |
La última fila suele sorprender: muchas placas de desarrollo desperdician mucho más en su regulador de tensión, su LED de alimentación y su chip USB-serie de lo que consume el propio ESP32 en deep sleep. Para una autonomía real, mide la placa, no el chip. El artículo complementario sobre sensores ESP32 alimentados por batería cubre con más detalle la elección de hardware y las mediciones de corriente.
Por qué los mensajes retenidos son imprescindibles aquí
Un sensor dormilón publica una vez y desaparece. Si un dashboard o una app móvil se suscribe un minuto después, un mensaje normal ya se ha perdido. Con el flag retain activado, el broker guarda el último mensaje de ese topic y se lo entrega inmediatamente a cada nuevo suscriptor. El dashboard muestra la lectura más reciente en cuanto se abre, sin importar cuándo despertó el dispositivo por última vez.
Incluye una marca de tiempo o un número de secuencia en el payload para que los consumidores sepan cuán antiguo es el valor retenido. Una lectura de hace cuatro horas que parece actual es engañosa, y para un dispositivo que duerme lo "antiguo" es el estado normal. Si los mensajes retenidos te resultan nuevos, la introducción a qué hace un broker MQTT explica cómo los almacena el broker.
Confirmar la entrega sin publicar con QoS 1
PubSubClient solo puede publicar con QoS 0, lo que significa "enviado", no "recibido". En un dispositivo siempre encendido eso suele ser aceptable. En uno que duerme es un problema real: si llamas a esp_deep_sleep_start() justo después de publish(), la radio puede apagarse mientras el paquete sigue en un búfer y el mensaje desaparece sin dejar rastro.
El sketch de abajo usa un truco sencillo y fiable. Se suscribe a su propio topic de estado antes de publicar y luego espera a que el broker le devuelva el mensaje nuevo. Ver tu propio mensaje de vuelta demuestra que el broker lo recibió y lo enrutó. Un número de secuencia guardado en la memoria RTC distingue el mensaje nuevo del antiguo retenido, que el broker también entrega al suscribirse.
El sketch completo de ESP32 deep sleep MQTT
#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"; // único
const char* TOPIC_STATE = "connectmq/demo/sleepy-3e7f10/state";
const uint64_t SLEEP_SECONDS = 300; // 5 minutos
const uint32_t WIFI_TIMEOUT_MS = 8000;
const uint32_t ACK_TIMEOUT_MS = 3000;
// Sobrevive al deep sleep (se pierde al cortar la alimentación o con reset)
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);
// Espaciar los intentos cuando todo falla, para que una red caída no agote la batería
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() {
// Sustitúyelo por tu sensor; el valor simulado mantiene el ejemplo autocontenido
return 20.0 + (esp_random() % 500) / 100.0;
}
void onMessage(char* topic, byte* payload, unsigned int length) {
// Nuestro payload contiene "seq":N; buscamos el que acabamos de enviar
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); // vía rápida
} else {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - start > WIFI_TIMEOUT_MS) {
haveSaved = false; // el AP puede haber cambiado de canal: escaneo completo la próxima vez
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(); // leer antes de que la radio caliente la placa
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); // retenido
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() {
// Nunca se alcanza: el ESP32 se reinicia en setup() tras cada despertar
}
Qué hace cada optimización
Caché de canal y BSSID. Un WiFi.begin() normal escanea todos los canales para encontrar el punto de acceso. Pasar el canal y el BSSID guardados en la memoria RTC evita ese escaneo, que suele ser el mayor ahorro individual. Si el router cambia de canal, la vía rápida agota el tiempo, la caché se borra y el siguiente despertar realiza un escaneo completo.
Leer primero el sensor. La radio calienta la placa de forma apreciable, lo que distorsiona las lecturas de temperatura. Tomar la muestra antes de conectar lo evita.
Backoff ante fallos. Cuando la red está caída, cada despertar gasta energía en intentos que no pueden tener éxito. Tras fallos repetidos, el nodo duerme cuatro veces más hasta que una publicación vuelve a tener éxito.
awakeMs en el payload. Informar de cuánto tiempo estuvo despierto el dispositivo te da una visión continua y gratuita del coste energético. Si empieza a subir, algo cambió en la red.
Sin Last Will. Un dispositivo dormilón se desconecta a propósito en cada ciclo, así que un Last Will offline no tendría sentido. Juzga si está vivo por la antigüedad de la última lectura retenida.
Opcionalmente, una IP estática ahorra también el intercambio DHCP. Llama a WiFi.config(ip, gateway, subnet, dns) antes de WiFi.begin(), pero solo en redes donde controles la asignación de direcciones.
Comprueba que funciona
Suscríbete desde tu equipo:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-3e7f10 -v -t "connectmq/demo/sleepy-3e7f10/#"
Reduce SLEEP_SECONDS a 20 para las pruebas. Deberías ver un mensaje por ciclo:
connectmq/demo/sleepy-3e7f10/state {"seq":7,"temperature":22.81,"rssi":-61,"awakeMs":1180,"failures":0}
Ahora detén el suscriptor y vuelve a iniciarlo mientras el ESP32 duerme. La última lectura aparece inmediatamente, que es exactamente lo que experimenta un dashboard. Después apaga el router durante unos cuantos ciclos: el monitor serie muestra cómo crece failures, y el primer mensaje tras la recuperación indica cuántos ciclos se perdieron.
El área pública de broker.connectmq.com es un servidor MQTT público gratuito adecuado para este tipo de prueba. Sus sesiones caducan como máximo a los 30 minutos, lo que aquí no importa porque el sketch se conecta con una sesión nueva en cada despertar. Cuando pases a un broker privado de ConnectMQ, el sketch solo necesita credenciales (y TLS si lo eliges); lo único a vigilar es el límite de 25 conexiones del plan gratuito, que una flota de sensores dormilones rara vez alcanza porque cada uno solo está conectado un instante por ciclo.
Lista de comprobación para la autonomía
- Quita o desactiva el LED de alimentación y usa una placa con un regulador de baja corriente de reposo, o alimenta el módulo directamente.
- Mide la corriente en deep sleep con un medidor en serie; en una placa bien diseñada debería estar muy por debajo de un miliamperio.
- Mantén corta la ventana en la que está despierto: Wi-Fi en caché, timeouts cortos, una sola publicación.
- Elige un intervalo razonable. Reducir el intervalo a la mitad duplica aproximadamente la energía con la radio encendida; la mayoría de las lecturas ambientales no necesitan resolución de minutos.
- Alimenta el sensor desde un GPIO y apágalo antes de dormir, para que no drene la batería en segundo plano.
- Evita TLS en cada despertar si la autonomía es crítica y el modelo de amenazas lo permite en una red privada; cuando necesites cifrado, presupuesta el tiempo extra del handshake y sigue ESP32 MQTT SSL con WiFiClientSecure.
Solución de problemas
- Los mensajes solo llegan a veces. Estás durmiendo antes de que la publicación salga por la radio. Mantén la confirmación por eco y no acortes demasiado
ACK_TIMEOUT_MS. seqvuelve a 1 de forma inesperada. La memoria RTC sobrevive al deep sleep, pero no a un corte de alimentación, a un brownout ni a pulsar reset. Una tensión de batería en descenso que provoca brownouts es una causa habitual.- El dispositivo nunca despierta. Comprueba que llamas a
esp_sleep_enable_timer_wakeup()con microsegundos y un valor de 64 bits; un desbordamiento de 32 bits produce un intervalo mucho más corto o erróneo. - La salida serie se corta. Llama a
Serial.flush()antes de dormir, como hace el sketch. - Tiempos despierto largos en algunos ciclos. Falló la vía rápida del Wi-Fi y se ejecutó un escaneo completo. Comprueba que tu router usa un canal fijo.
Sensores dormilones en producción
Los valores retenidos son tan fiables como el broker que los guarda. Un broker MQTT gestionado mantiene ese endpoint disponible mientras tus sensores duermen, y los mismos topics retenidos sirven a tus dashboards, scripts y apps. Con ConnectMQ creas credenciales por dispositivo en el portal, ves el último valor retenido de cada sensor en el explorador de topics en vivo y configuras reglas de alerta por umbral sobre lecturas como la temperatura. Un broker MQTT en la nube también permite que sensores en un campo, una bodega y un almacén informen a un mismo endpoint sin ningún servidor local. ¿Eres nuevo con la placa? Empieza por el tutorial de IoT con ESP32.
Empieza con ConnectMQ
Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito. Apunta tus sensores dormilones a él con sus propias credenciales y observa en el portal cómo aparecen las lecturas retenidas a medida que despiertan.
Preguntas frecuentes
¿Por qué mi ESP32 pierde mensajes MQTT antes del deep sleep?
Una publicación QoS 0 retorna en cuanto los datos se ponen en cola, así que dormir inmediatamente puede cortarla. Espera una confirmación, como la técnica de eco de este sketch, y después desconecta y duerme.
¿Debe un sensor en deep sleep mantener una sesión MQTT?
Normalmente no. Una conexión nueva en cada despertar es más sencilla y funciona con cualquier broker. Las sesiones persistentes solo ayudan si el dispositivo debe recibir comandos puestos en cola mientras dormía.
¿Cómo puede recibir comandos un ESP32 que duerme?
Publica los comandos como mensajes retenidos en un topic al que el dispositivo se suscriba al despertar. Recibe el último comando inmediatamente, actúa y luego lo borra publicando un mensaje retenido vacío.
¿Cuál es un buen intervalo de publicación para un sensor con batería?
Depende de lo rápido que cambie el valor medido. Los sensores ambientales suelen funcionar bien con intervalos de cinco a quince minutos; los intervalos más cortos cuestan proporcionalmente más energía.



