O recurso ESP32 MQTT Last Will permite que o broker anuncie em nome do seu dispositivo que ele ficou offline, mesmo quando o dispositivo travou ou perdeu energia e não conseguiu se despedir por conta própria. Neste tutorial você vai configurar um Last Will retido, uma mensagem de nascimento correspondente e uma mensagem de desligamento limpo, para que todo dashboard e script veja o status online/offline correto o tempo todo.
O que o Last Will and Testament faz
Quando um cliente MQTT se conecta, ele pode entregar ao broker uma mensagem para publicar mais tarde: um tópico, um payload, um nível de QoS e uma flag de retain. Esse é o Last Will and Testament (LWT). O broker o guarda e só o publica se o cliente desaparecer sem um pacote DISCONNECT adequado. Isso cobre exatamente os casos que importam:
- O dispositivo perdeu energia ou sofreu um brownout.
- O firmware travou, congelou ou foi reiniciado por um watchdog.
- O Wi-Fi caiu e o temporizador de keepalive expirou.
- A rede entre o dispositivo e o broker falhou.
Se o cliente se desconectar de forma limpa, o broker descarta o will. É por isso que um desligamento limpo precisa de sua própria publicação "offline", descrita mais adiante.
Sozinho, um will só anuncia a morte. Combine-o com uma mensagem de nascimento, uma publicação "online" retida enviada logo após cada conexão bem-sucedida, e o tópico de status sempre conterá a verdade atual: online enquanto o dispositivo está conectado e offline a partir do momento em que o broker percebe que ele sumiu.
O padrão de tópico de status
| Evento | Quem publica | Tópico | Payload | Retido |
|---|---|---|---|---|
| Dispositivo conecta | ESP32 (nascimento) | connectmq/demo/f5c820/<device>/status |
online |
Sim |
| Dispositivo morre inesperadamente | Broker (will) | o mesmo | offline |
Sim |
| Dispositivo desliga de forma limpa | ESP32 | o mesmo | offline |
Sim |
As duas mensagens precisam ser retidas. Sem a flag de retain, um dashboard que iniciasse depois de o dispositivo ficar online nunca saberia o estado dele, porque a mensagem de nascimento já teria passado. Com ela, todo novo assinante recebe o status mais recente imediatamente.
Use o mesmo tópico para nascimento e will e mantenha os payloads simples. online e offline puros funcionam com os tópicos de disponibilidade do Home Assistant, com switches do Node-RED e com a maioria dos dashboards sem nenhuma interpretação. Se quiser mais detalhes, use JSON nos dois, por exemplo {"state":"online","fw":"1.2.0"}.
ESP32 MQTT Last Will com PubSubClient
O PubSubClient define o will por meio de uma sobrecarga de connect():
boolean connect(const char* id,
const char* user, const char* pass,
const char* willTopic, uint8_t willQos,
boolean willRetain, const char* willMessage);
Para conexões anônimas, como na área pública do broker.connectmq.com, existe uma variante sem user e pass. Aqui está um sketch completo que publica a mensagem de nascimento, mantém o status correto e desliga de forma limpa quando você aperta o botão BOOT:
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const int SHUTDOWN_BUTTON = 0; // BOOT button
WiFiClient net;
PubSubClient mqtt(net);
String deviceId, statusTopic, telemetryTopic;
unsigned long lastPublish = 0;
void connectMqtt() {
while (!mqtt.connected()) {
bool ok = mqtt.connect(deviceId.c_str(),
statusTopic.c_str(), // will topic
1, // will QoS
true, // will retain
"offline"); // will payload
if (ok) {
mqtt.publish(statusTopic.c_str(), "online", true); // birth message
Serial.println("Connected, status=online");
} else {
Serial.printf("Connect failed, state=%d\n", mqtt.state());
delay(3000);
}
}
}
void cleanShutdown() {
mqtt.publish(statusTopic.c_str(), "offline", true);
mqtt.loop();
delay(100);
mqtt.disconnect(); // the broker discards the will
WiFi.disconnect(true);
Serial.println("Clean shutdown done");
esp_deep_sleep_start(); // sleep until reset
}
void setup() {
Serial.begin(115200);
pinMode(SHUTDOWN_BUTTON, INPUT_PULLUP);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
deviceId = "esp32-lwt-" + String((uint32_t)ESP.getEfuseMac(), HEX);
statusTopic = "connectmq/demo/f5c820/" + deviceId + "/status";
telemetryTopic = "connectmq/demo/f5c820/" + deviceId + "/telemetry";
mqtt.setServer("broker.connectmq.com", 1883);
mqtt.setKeepAlive(15); // seconds; the broker detects loss after about 1.5x this
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (digitalRead(SHUTDOWN_BUTTON) == LOW) cleanShutdown();
if (millis() - lastPublish > 10000) {
lastPublish = millis();
String payload = "{\"uptime\":" + String(millis() / 1000) + "}";
mqtt.publish(telemetryTopic.c_str(), payload.c_str());
}
}
Testando os três casos
Inscreva-se em todos os tópicos de status sob o seu prefixo a partir de um computador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-f5c820 -t 'connectmq/demo/f5c820/+/status' -v
Depois experimente cada cenário:
- Inicialização normal. Ligue a placa. Você vê
.../status onlineem poucos segundos. - Travamento ou queda de energia. Puxe o cabo USB. Depois de aproximadamente um intervalo e meio de keepalive (cerca de 20–25 segundos com keepalive de 15 segundos), o broker publica
.../status offline. - Desligamento limpo. Ligue de novo, espere o
onlinee aperte o BOOT. Oofflineaparece imediatamente, publicado pelo próprio dispositivo.
Pare o mosquitto_sub e inicie-o de novo com o dispositivo em qualquer estado. Como as duas mensagens são retidas, você recebe na hora o status atual. Esse é o comportamento do qual seus dashboards dependem.
Ajustando o keepalive
O intervalo de keepalive define a rapidez com que o will dispara após uma falha silenciosa. O broker espera cerca de 1,5 vez o keepalive sem nenhum pacote do cliente antes de desistir da conexão.
| Keepalive | Tempo aproximado até "offline" | Contrapartida |
|---|---|---|
| 5 s | menos de 10 s | Detecção rápida, mais tráfego de ping, falsos offline com Wi-Fi ruim |
| 15 s (padrão do PubSubClient) | cerca de 20–25 s | Bom padrão para dispositivos ligados na tomada |
| 60 s | cerca de 90 s | Menos tráfego, detecção lenta |
Um keepalive curto só funciona se mqtt.loop() rodar com frequência. Se o seu loop bloquear por mais tempo que o keepalive, o broker derruba um dispositivo saudável e dispara o will, e você verá o status alternando entre online e offline. Chamadas bloqueantes longas também levam a resets, como explicado em como corrigir resets do task watchdog do ESP32.
Erros comuns
Esquecer a mensagem de nascimento. Sem ela, o status continua offline após uma reconexão, porque o will da falha anterior ainda está retido no tópico.
Não reter o will. Um will não retido só chega aos assinantes conectados naquele momento. Qualquer coisa que inicie depois vê o último valor retido, provavelmente online, e acredita que um dispositivo morto está vivo.
Compartilhar um tópico de status entre dispositivos. Cada dispositivo precisa do próprio tópico de status, normalmente com o client ID ou o nome do dispositivo no caminho. Um tópico compartilhado só mostra o dispositivo que falou por último.
Esperar o will em uma desconexão limpa. mqtt.disconnect() avisa o broker de que o cliente está saindo de propósito, então o will é descartado. Publique o offline você mesmo antes de desconectar, como faz o cleanShutdown().
Client IDs duplicados. Se dois dispositivos usam o mesmo client ID, o broker desconecta a conexão mais antiga quando a mais nova se conecta, o que dispara o will da antiga e gera mudanças de status confusas. Derive o ID do endereço MAC.
Dispositivos em deep sleep
Nós a bateria que acordam, publicam e dormem são um caso especial. Se eles se desconectam de forma limpa antes de dormir, o will nunca dispara, e um tópico de status alternaria entre online e offline a cada ciclo. Uma abordagem melhor para dispositivos que dormem é publicar um timestamp last_seen ou um contador de uptime retido e deixar que o consumidor considere o dispositivo offline quando esse valor for mais antigo que alguns intervalos. O guia de sensor ESP32 alimentado por bateria discute essa troca.
Will delay do MQTT 5 e status no lado do broker
O PubSubClient fala MQTT 3.1.1. Com um cliente MQTT 5, como o esp-mqtt do ESP-IDF, você também pode definir um will delay interval, para que o broker espere alguns segundos antes de publicar o will. Se o dispositivo reconectar nessa janela, por exemplo após uma breve instabilidade do Wi-Fi, o will é cancelado e o status não oscila. O exemplo de MQTT com ESP-IDF mostra onde o will é configurado, e recursos do MQTT 5 explica a propriedade.
No lado do broker, o ConnectMQ também publica o estado de conexão de cada cliente em $SYS/broker/connection/{clientId}/state, o que é útil como verificação cruzada quando você depura um offline inesperado.
Mostre a disponibilidade dos dispositivos no ConnectMQ
Tudo acima roda no servidor MQTT público (gratuito), então você pode testar os modos de falha sem uma conta. Para uma frota real, um broker MQTT gerenciado privado mantém os tópicos de status longe da visão pública. O explorador de tópicos ao vivo do ConnectMQ mostra o status retido de cada dispositivo de relance. Crie um broker no plano Serverless gratuito em app.connectmq.com, sem cartão de crédito, e conecte com os seus YOUR_CLIENT_ID, YOUR_USERNAME e YOUR_PASSWORD gerados, usando a sobrecarga de connect() com credenciais. Para o projeto completo de dispositivo, veja o tutorial de IoT com ESP32.
Perguntas frequentes
Quando o broker publica o Last Will?
Quando o cliente desaparece sem enviar DISCONNECT: queda de energia, travamento, falha de rede ou timeout de keepalive. Um disconnect() limpo cancela o will.
A mensagem de Last Will deve ser retida?
Sim, em tópicos de status. Retê-la significa que qualquer assinante que se conecte depois ainda verá que o dispositivo está offline.
Qual QoS devo usar para o will?
QoS 1 é um bom padrão, porque garante a entrega aos assinantes conectados. O plano Serverless gratuito do ConnectMQ suporta QoS 0 e 1, o que cobre esse padrão.
Por que o status do meu dispositivo fica oscilando entre online e offline?
Normalmente o mqtt.loop() fica bloqueado por mais tempo que o keepalive, ou dois dispositivos compartilham um client ID. Remova chamadas bloqueantes longas e dê a cada dispositivo um ID único.



