L'ESP8266 reste l'un des moyens les moins chers de mettre un capteur sur le Wi-Fi, et PubSubClient est la bibliothèque MQTT utilisée par la plupart des sketches ESP8266. Ce tutoriel vous donne un sketch complet pour NodeMCU ou Wemos D1 mini qui se connecte à un broker MQTT, publie de la télémétrie, reçoit des commandes, se reconnecte sans bloquer et s'annonce avec un Last Will — puis passe la connexion en TLS avec l'empreinte (fingerprint) du certificat et explique quand préférer une CA racine.
Configuration testée : Arduino IDE 2.x ou arduino-cli, core ESP8266 3.1.x, PubSubClient 2.8.
Installer la chaîne d'outils
- Dans l'Arduino IDE, ouvrez Préférences et ajoutez cette URL du gestionnaire de cartes :
https://arduino.esp8266.com/stable/package_esp8266com_index.json. - Gestionnaire de cartes → installez esp8266 by ESP8266 Community.
- Gestionnaire de bibliothèques → installez PubSubClient de Nick O'Leary.
- Choisissez NodeMCU 1.0 (ESP-12E Module) ou LOLIN(WEMOS) D1 R2 & mini comme carte.
Avec arduino-cli, les mêmes étapes donnent :
arduino-cli config add board_manager.additional_urls \
https://arduino.esp8266.com/stable/package_esp8266com_index.json
arduino-cli core update-index
arduino-cli core install esp8266:esp8266
arduino-cli lib install PubSubClientL'ESP8266 ne gère que le Wi-Fi 2,4 GHz. Si votre routeur diffuse un SSID unique pour les deux bandes, cela fonctionne en général ; un réseau uniquement 5 GHz ne fonctionnera jamais.
Le sketch complet
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";
WiFiClient net;
PubSubClient mqtt(net);
String clientId, baseTopic;
unsigned long lastAttempt = 0, retryDelay = 1000, lastPublish = 0;
void onMessage(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
Serial.printf("[%s] %s\n", topic, msg.c_str());
if (msg == "on") digitalWrite(LED_BUILTIN, LOW); // la LED intégrée est active à l'état bas
if (msg == "off") digitalWrite(LED_BUILTIN, HIGH);
}
bool connectMqtt() {
String status = baseTopic + "/status";
// connect(id, user, pass, willTopic, willQos, willRetain, willMessage)
if (!mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS,
status.c_str(), 1, true, "offline")) {
Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
return false;
}
mqtt.publish(status.c_str(), "online", true); // retenu
mqtt.subscribe((baseTopic + "/led").c_str(), 1);
Serial.println("MQTT connected as " + clientId);
return true;
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
WiFi.persistent(false); // ne pas réécrire la flash à chaque démarrage
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
clientId = "esp8266-" + String(ESP.getChipId(), HEX);
baseTopic = "testmqtt/esp8266/" + clientId;
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512); // 256 octets par défaut, topic et en-tête compris
mqtt.setKeepAlive(30);
mqtt.setSocketTimeout(5); // un connect raté bloque au maximum ~5 s
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
delay(10); // le core reconnecte le Wi-Fi tout seul
return;
}
if (!mqtt.connected()) {
unsigned long now = millis();
if (now - lastAttempt >= retryDelay) {
lastAttempt = now;
retryDelay = connectMqtt() ? 1000 : min(retryDelay * 2, 60000UL);
}
return;
}
mqtt.loop(); // doit tourner souvent : reçoit les messages, envoie les pings
if (millis() - lastPublish >= 10000) {
lastPublish = millis();
char payload[96];
snprintf(payload, sizeof(payload),
"{\"rssi\":%d,\"heap\":%u,\"uptime\":%lu}",
WiFi.RSSI(), ESP.getFreeHeap(), millis() / 1000);
mqtt.publish((baseTopic + "/telemetry").c_str(), payload);
}
}Pourquoi il est écrit ainsi
- Reconnexion non bloquante. L'exemple classique utilise
while (!client.connected()) { ...; delay(5000); }, qui fige tout le reste — boutons, capteurs, et même la pile Wi-Fi — tant que le broker est injoignable. Ici,loop()rend la main immédiatement et réessaie avec un backoff exponentiel (1 s, 2 s, 4 s… jusqu'à 60 s), pour qu'une flotte ne martèle pas un broker en train de redémarrer. - Client ID stable dérivé de
ESP.getChipId(). Deux cartes avec le même ID s'éjectent l'une l'autre en boucle ; voir les bonnes pratiques pour le client ID. - Last Will + statut retenu. Si la carte perd son alimentation, le broker publie
offlinesur.../statusà l'expiration du keep-alive (1,5 × 30 s ici). Tout abonné arrivant plus tard voit immédiatement l'état actuel. - S'abonner après chaque connexion. PubSubClient utilise une clean session : les abonnements ne survivent pas à une reconnexion. Les endpoints de test partagés comme
broker.connectmq.comne conservent de toute façon pas les sessions, c'est pourquoi le sketch se réabonne toujours dansconnectMqtt(). setBufferSize(512): le buffer par défaut de PubSubClient fait 256 octets pour le paquet entier, topic compris. Un topic long plus un payload JSON le dépassent vite, etpublish()renvoie alorsfalsesans autre indice. Le calculateur de taille de paquet montre la taille réelle de votre paquet.setSocketTimeout(5):connect()est lui-même bloquant. Le timeout par défaut de 15 secondes suffit à déclencher des resets du watchdog ou une interface figée.
Tester le sketch
Flashez la carte et ouvrez le moniteur série à 115200 bauds. Vous devriez voir MQTT connected as esp8266-xxxxxx. Si state=-2 ou un autre code apparaît à la place, reportez-vous au tableau des erreurs plus bas.
Ouvrez ensuite le client MQTT en ligne, connectez-vous à broker.connectmq.com et abonnez-vous à testmqtt/esp8266/#. Le statut online retenu apparaît d'abord, puis un JSON de télémétrie toutes les 10 secondes. Publiez on sur testmqtt/esp8266/<client-id>/led et la LED s'allume.
Voir votre ESP8266 dans le navigateur
Pour tester le Last Will, débranchez la carte et attendez environ 45 secondes : le topic de statut passe à offline. Rebranchez-la et il revient à online : votre chemin de reconnexion fonctionne.
Ajouter TLS avec une empreinte
Le port 1883 transmet le mot de passe MQTT en clair. Le core ESP8266 inclut BearSSL, donc TLS sur le port 8883 ne demande qu'une autre classe de client. La façon la plus légère de vérifier le serveur est l'épinglage du certificat par empreinte SHA-1.
Récupérez l'empreinte du certificat actuel du broker depuis votre ordinateur :
openssl s_client -connect broker.connectmq.com:8883 \
-servername broker.connectmq.com </dev/null 2>/dev/null \
| openssl x509 -noout -fingerprint -sha1 \
| sed 's/.*=//; s/:/ /g'Puis modifiez trois choses dans le sketch :
#include <WiFiClientSecure.h>
const uint16_t MQTT_PORT = 8883;
const char* FINGERPRINT = "AA BB CC DD EE FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD";
BearSSL::WiFiClientSecure net; // remplace WiFiClient
PubSubClient mqtt(net);
// dans setup(), avant mqtt.setServer() :
net.setFingerprint(FINGERPRINT);
mqtt.setSocketTimeout(10); // le handshake TLS est lent sur une puce à 80 MHzLe handshake prend nettement plus de temps qu'un connect TCP simple et mobilise une bonne partie de la RAM limitée de l'ESP8266. Surveillez ESP.getFreeHeap() dans la télémétrie ; si cela devient juste, BearSSL peut utiliser des buffers plus petits avec net.setBufferSizes(1024, 1024), mais seulement si le broker prend en charge l'extension TLS max fragment length — vérifiez d'abord avec BearSSL::WiFiClientSecure::probeMaxFragmentLength(MQTT_HOST, 8883, 1024).
Empreinte ou CA racine
| Méthode | Comment | Compromis |
|---|---|---|
setFingerprint() | Épingle le SHA-1 du certificat final | Le plus léger et le plus rapide, mais casse à chaque renouvellement du certificat |
setTrustAnchors() | Épingle la CA racine (BearSSL::X509List) | Survit aux renouvellements ; nécessite l'heure exacte via configTime() |
setInsecure() | Aucune vérification | Chiffré mais accepte n'importe quel serveur : débogage uniquement |
Les certificats des CA publiques sont renouvelés régulièrement, souvent tous les quelques mois. Une flotte avec empreinte épinglée cesse de se connecter le jour du renouvellement avec state=-2, et il faut reflasher chaque appareil. Pour tout ce qui doit durer, épinglez plutôt la CA racine :
#include <time.h>
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...certificat racine de la CA qui signe le certificat de votre broker...
-----END CERTIFICATE-----
)EOF";
BearSSL::X509List trustAnchor(ROOT_CA);
// dans setup(), une fois le Wi-Fi connecté :
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
while (time(nullptr) < 1700000000) delay(200); // attendre NTP
net.setTrustAnchors(&trustAnchor);Sans horloge valide, BearSSL considère tout certificat comme expiré ou pas encore valide. Plus de détails sur TLS, y compris les certificats client, dans le guide TLS pour ESP32 : l'API BearSSL diffère, mais les concepts sont les mêmes.
Codes d'erreur
mqtt.state() renvoie ces valeurs :
| state | Constante | Cause typique |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | Pas de réponse au ping dans le délai keep-alive ; Wi-Fi faible |
| -3 | MQTT_CONNECTION_LOST | Réseau perdu ou socket fermé par le broker |
| -2 | MQTT_CONNECT_FAILED | Échec TCP/TLS : mauvais hôte ou port, DNS, pare-feu, empreinte périmée |
| -1 | MQTT_DISCONNECTED | Déconnexion propre, ou jamais connecté |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | Le broker refuse MQTT 3.1.1 |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | Client ID rejeté |
| 3 | MQTT_CONNECT_UNAVAILABLE | Broker indisponible |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | Nom d'utilisateur ou mot de passe incorrect |
| 5 | MQTT_CONNECT_UNAUTHORIZED | Authentifié mais pas autorisé à se connecter |
Les valeurs positives sont des codes de retour CONNACK du broker — consultez-les dans l'outil des reason codes. Les valeurs négatives viennent de la bibliothèque elle-même et signalent presque toujours un problème réseau. La méthode pas à pas de tester la connexion d'un ESP32 au broker s'applique de la même façon à un ESP8266.
Pour aller plus loin
- Vous passez à une puce plus récente ? Le tutoriel ESP32 avec Arduino utilise la même API PubSubClient.
- Besoin de publier en QoS 1 ou d'un autre style d'API ? Comparez avec la bibliothèque arduino-mqtt.
- Des payloads plus gros ? ArduinoJson avec MQTT montre comment dimensionner correctement les buffers.
Un nœud MQTT fiable sur ESP8266 tient à quelques habitudes : ne jamais bloquer loop(), se reconnecter avec backoff, se réabonner à chaque connexion, dimensionner le buffer pour vos vrais paquets et épingler une CA racine plutôt qu'une empreinte sur les appareils qui doivent continuer à fonctionner après le prochain renouvellement de certificat.
Questions fréquentes
Pourquoi la connexion TLS de mon ESP8266 cesse-t-elle de fonctionner au bout de quelques semaines ?
Si vous avez épinglé le certificat avec setFingerprint(), l'empreinte change à chaque renouvellement du certificat du broker, et le handshake échoue alors avec state -2. Épinglez plutôt la CA racine avec setTrustAnchors() : elle survit aux renouvellements tant que la même CA signe le nouveau certificat.
PubSubClient peut-il publier en QoS 1 sur l'ESP8266 ?
Non. PubSubClient ne publie qu'en QoS 0. Il peut s'abonner en QoS 0 ou 1, donc les messages entrants peuvent être acquittés, mais les messages sortants sont en fire-and-forget. Si vous avez besoin de publier en QoS 1, utilisez une bibliothèque comme arduino-mqtt ou ajoutez un acquittement applicatif.
Que signifie le state -2 de PubSubClient ?
MQTT_CONNECT_FAILED : la connexion TCP ou TLS vers le broker n'a pas pu être ouverte. Le problème se situe sous MQTT : Wi-Fi, DNS, mauvais port, pare-feu ou échec du handshake TLS, par exemple une empreinte périmée ou un WiFiClient simple sur le port 8883.


