La fonctionnalité ESP32 MQTT Last Will permet au broker d'annoncer à la place de votre appareil qu'il est passé hors ligne, même lorsque l'appareil a planté ou perdu son alimentation et n'a pas pu dire au revoir lui-même. Dans ce tutoriel, vous allez configurer un Last Will retenu, un message de naissance correspondant et un message d'arrêt propre, pour que chaque tableau de bord et chaque script voie en permanence un statut online/offline exact.
Ce que fait le Last Will and Testament
Lorsqu'un client MQTT se connecte, il peut confier au broker un message à publier plus tard : un topic, un payload, un niveau de QoS et un flag retain. C'est le Last Will and Testament (LWT). Le broker le conserve et ne le publie que si le client disparaît sans paquet DISCONNECT en bonne et due forme. Cela couvre exactement les cas qui vous intéressent :
- L'appareil a perdu son alimentation ou subi un brownout.
- Le firmware a planté, s'est figé ou a été réinitialisé par un watchdog.
- Le Wi-Fi est tombé et le minuteur de keepalive a expiré.
- Le réseau entre l'appareil et le broker a lâché.
Si le client se déconnecte proprement, le broker supprime le will. C'est pourquoi un arrêt propre nécessite sa propre publication « offline », décrite plus bas.
À lui seul, un will ne fait qu'annoncer la mort. Associez-le à un message de naissance, une publication « online » retenue envoyée juste après chaque connexion réussie, et le topic de statut contient toujours la vérité du moment : online tant que l'appareil est connecté, offline dès que le broker remarque sa disparition.
Le schéma du topic de statut
| Événement | Qui publie | Topic | Payload | Retenu |
|---|---|---|---|---|
| L'appareil se connecte | ESP32 (naissance) | connectmq/demo/f5c820/<device>/status |
online |
Oui |
| L'appareil meurt de façon inattendue | Broker (will) | identique | offline |
Oui |
| L'appareil s'arrête proprement | ESP32 | identique | offline |
Oui |
Les deux messages doivent être retenus. Sans le flag retain, un tableau de bord lancé après la mise en ligne de l'appareil n'apprendrait jamais son état, car le message de naissance a disparu. Avec lui, chaque nouvel abonné reçoit immédiatement le dernier statut.
Utilisez le même topic pour la naissance et le will, et gardez des payloads simples. De simples online et offline fonctionnent avec les topics de disponibilité de Home Assistant, les interrupteurs Node-RED et la plupart des tableaux de bord, sans aucune analyse. Si vous voulez plus de détails, utilisez du JSON pour les deux, par exemple {"state":"online","fw":"1.2.0"}.
ESP32 MQTT Last Will avec PubSubClient
PubSubClient définit le will via une surcharge de connect() :
boolean connect(const char* id,
const char* user, const char* pass,
const char* willTopic, uint8_t willQos,
boolean willRetain, const char* willMessage);
Pour les connexions anonymes, comme sur la zone publique de broker.connectmq.com, il existe une variante sans user ni pass. Voici un sketch complet qui publie le message de naissance, maintient un statut exact et s'arrête proprement lorsque vous appuyez sur le bouton 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; // bouton BOOT
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(), // topic du will
1, // QoS du will
true, // retain du will
"offline"); // payload du will
if (ok) {
mqtt.publish(statusTopic.c_str(), "online", true); // message de naissance
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(); // le broker supprime le will
WiFi.disconnect(true);
Serial.println("Clean shutdown done");
esp_deep_sleep_start(); // veille jusqu'au 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); // secondes ; le broker détecte la perte après environ 1,5 fois cette valeur
}
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());
}
}
Tester les trois cas
Abonnez-vous depuis un ordinateur à tous les topics de statut sous votre préfixe :
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-f5c820 -t 'connectmq/demo/f5c820/+/status' -v
Essayez ensuite chaque scénario :
- Démarrage normal. Alimentez la carte. Vous voyez
.../status onlineen quelques secondes. - Plantage ou coupure de courant. Débranchez le câble USB. Après environ une fois et demie l'intervalle de keepalive (20 à 25 secondes environ avec un keepalive de 15 secondes), le broker publie
.../status offline. - Arrêt propre. Rallumez la carte, attendez
online, puis appuyez sur BOOT.offlineapparaît immédiatement, publié par l'appareil lui-même.
Arrêtez mosquitto_sub puis relancez-le, quel que soit l'état de l'appareil. Comme les deux messages sont retenus, vous recevez instantanément le statut actuel. C'est le comportement dont dépendent vos tableaux de bord.
Régler le keepalive
L'intervalle de keepalive détermine la rapidité avec laquelle le will se déclenche après une défaillance silencieuse. Le broker attend environ 1,5 fois le keepalive sans aucun paquet du client avant d'abandonner la connexion.
| Keepalive | Délai approximatif avant « offline » | Compromis |
|---|---|---|
| 5 s | moins de 10 s | Détection rapide, plus de trafic de ping, faux offline avec un Wi-Fi médiocre |
| 15 s (valeur par défaut de PubSubClient) | environ 20 à 25 s | Bon choix par défaut pour les appareils alimentés sur secteur |
| 60 s | environ 90 s | Moins de trafic, détection lente |
Un keepalive court ne fonctionne que si mqtt.loop() s'exécute fréquemment. Si votre boucle bloque plus longtemps que le keepalive, le broker coupe un appareil en bonne santé et déclenche le will, et vous verrez le statut osciller entre online et offline. Les longs appels bloquants provoquent aussi des resets, comme l'explique corriger les resets du task watchdog ESP32.
Erreurs courantes
Oublier le message de naissance. Sans lui, le statut reste offline après une reconnexion, car le will de la défaillance précédente est toujours retenu sur le topic.
Ne pas retenir le will. Un will non retenu n'atteint que les abonnés connectés à ce moment-là. Tout ce qui démarre plus tard voit la dernière valeur retenue, probablement online, et croit qu'un appareil mort est vivant.
Partager un même topic de statut entre plusieurs appareils. Chaque appareil a besoin de son propre topic de statut, généralement avec le client ID ou le nom de l'appareil dans le chemin. Un topic partagé n'affiche que l'appareil qui s'est exprimé en dernier.
S'attendre au will lors d'une déconnexion propre. mqtt.disconnect() indique au broker que le client part volontairement : le will est donc supprimé. Publiez vous-même offline avant de vous déconnecter, comme le fait cleanShutdown().
Client ID en double. Si deux appareils utilisent le même client ID, le broker déconnecte l'ancienne connexion lorsque la nouvelle se connecte, ce qui déclenche le will de l'ancienne et crée des changements de statut déroutants. Dérivez l'ID de l'adresse MAC.
Appareils en deep sleep
Les nœuds sur batterie qui se réveillent, publient et se rendorment sont un cas particulier. S'ils se déconnectent proprement avant de dormir, le will ne se déclenche jamais, et un topic de statut basculerait entre online et offline à chaque cycle. Pour les appareils en veille, une meilleure approche consiste à publier un horodatage last_seen ou un compteur d'uptime retenu, et à laisser le consommateur décider qu'un appareil est hors ligne lorsque cette valeur date de plus de quelques intervalles. Le guide du capteur ESP32 sur batterie aborde ce compromis.
Délai du will MQTT 5 et statut côté broker
PubSubClient parle MQTT 3.1.1. Avec un client MQTT 5 comme esp-mqtt d'ESP-IDF, vous pouvez aussi définir un intervalle de délai du will (will delay interval), pour que le broker attende quelques secondes avant de publier le will. Si l'appareil se reconnecte dans cette fenêtre, par exemple après un bref hoquet du Wi-Fi, le will est annulé et le statut n'oscille pas. L'exemple MQTT ESP-IDF montre où le will est configuré, et les fonctionnalités de MQTT 5 expliquent cette propriété.
Côté broker, ConnectMQ publie aussi l'état de connexion de chaque client sous $SYS/broker/connection/{clientId}/state, ce qui est utile comme vérification croisée lorsque vous déboguez un offline inattendu.
Affichez la disponibilité des appareils sur ConnectMQ
Tout ce qui précède fonctionne sur le serveur MQTT public gratuit : vous pouvez donc tester les modes de défaillance sans compte. Pour une vraie flotte, un broker MQTT managé privé tient les topics de statut à l'abri des regards. L'explorateur de topics en direct de ConnectMQ affiche d'un coup d'œil le statut retenu de chaque appareil. Créez un broker avec l'offre gratuite Serverless sur app.connectmq.com, sans carte bancaire, et connectez-vous avec vos YOUR_CLIENT_ID, YOUR_USERNAME et YOUR_PASSWORD générés, en utilisant la surcharge de connect() avec identifiants. Pour le projet d'appareil complet, consultez le tutoriel IoT ESP32.
Questions fréquentes
Quand le broker publie-t-il le Last Will ?
Lorsque le client disparaît sans envoyer de DISCONNECT : coupure de courant, plantage, panne réseau ou expiration du keepalive. Un disconnect() propre annule le will.
Le message Last Will doit-il être retenu ?
Oui, pour les topics de statut. Le retenir signifie que tout abonné qui se connecte plus tard voit quand même que l'appareil est hors ligne.
Quelle QoS utiliser pour le will ?
La QoS 1 est un bon choix par défaut, car elle garantit la livraison aux abonnés connectés. L'offre gratuite ConnectMQ Serverless prend en charge les QoS 0 et 1, ce qui couvre ce schéma.
Pourquoi le statut de mon appareil oscille-t-il entre online et offline ?
Généralement, mqtt.loop() est bloquée plus longtemps que le keepalive, ou deux appareils partagent un client ID. Supprimez les longs appels bloquants et donnez à chaque appareil un ID unique.



