Ce tutoriel IoT ESP32 vous mène d'une carte tout juste déballée à un capteur connecté au cloud qui publie la température et l'humidité, accepte des commandes à distance et communique avec le cloud via TLS. Vous configurerez l'Arduino IDE, vous connecterez au Wi-Fi, lirez un DHT22, publierez via MQTT sur le serveur MQTT public gratuit, puis transférerez le même sketch vers un broker privé et chiffré pour un usage réel.
Pourquoi l'ESP32 est le microcontrôleur IoT par défaut
La famille ESP32 d'Espressif associe un processeur rapide double cœur (ou simple cœur RISC-V selon la variante), le Wi-Fi et le Bluetooth intégrés, de nombreuses GPIO, des entrées analogiques et une prise en charge matérielle de TLS, le tout dans un module qui coûte quelques euros. Elle exécute du code Arduino, MicroPython et le framework ESP-IDF d'Espressif, et bénéficie d'une immense communauté. Pour quiconque construit un capteur connecté, un contrôleur de relais ou une petite passerelle, c'est généralement la première carte à essayer.
Ce que vous allez construire dans ce tutoriel :
- Une carte qui rejoint votre réseau Wi-Fi et se reconnecte automatiquement.
- Un capteur DHT22 publiant des mesures JSON toutes les dix secondes via MQTT.
- Une LED commandée à distance, allumée ou éteinte en publiant
ONouOFFsur un topic de commande. - Un topic de statut en ligne/hors ligne utilisant la fonctionnalité Last Will de MQTT.
- Une version sécurisée utilisant TLS sur le port 8883 avec des identifiants privés.
Ce dont vous avez besoin
| Élément | Remarques |
|---|---|
| Carte de développement ESP32 | N'importe quelle carte ESP32 DevKit, ESP32-S3 ou ESP32-C3 avec USB convient |
| Capteur DHT22 (AM2302) | Un module avec résistance de tirage intégrée est le plus simple |
| Fils de connexion et breadboard | Trois fils pour le capteur |
| Câble USB | Doit être un câble de données, pas uniquement de charge |
| Ordinateur avec Arduino IDE 2.x | Windows, macOS ou Linux |
| Clients Mosquitto en ligne de commande | mosquitto_pub et mosquitto_sub pour les tests |
Pas encore de capteur ? Le sketch signale une erreur et continue de tourner : vous pouvez donc quand même suivre les parties MQTT et LED.
Étape 1 : configurer l'Arduino IDE pour l'ESP32
- Installez Arduino IDE 2.x depuis le site d'Arduino.
- Ouvrez Tools, Board, Boards Manager, recherchez esp32 et installez esp32 by Espressif Systems. S'il n'apparaît pas, ajoutez
https://espressif.github.io/arduino-esp32/package_esp32_index.jsondans File, Preferences, Additional boards manager URLs et recherchez à nouveau. Le guide d'installation officiel d'Arduino-ESP32 propose des captures d'écran pour chaque plateforme. - Ouvrez Tools, Manage Libraries et installez PubSubClient de Nick O'Leary et DHT sensor library d'Adafruit (acceptez la proposition d'installer également Adafruit Unified Sensor).
- Branchez la carte, sélectionnez votre modèle dans Tools, Board (pour un DevKit générique, choisissez ESP32 Dev Module) et sélectionnez le port série dans Tools, Port.
Testez la chaîne d'outils avec un sketch de clignotement avant d'aller plus loin :
#define LED_PIN 2 // LED intégrée sur la plupart des cartes ESP32 DevKit
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
Si le téléversement reste bloqué sur « Connecting... », maintenez le bouton BOOT de la carte jusqu'au début du téléversement. Si aucun port n'apparaît, installez le pilote USB-série correspondant à la puce de votre carte (souvent CP210x ou CH340) ou essayez un autre câble. Certaines cartes n'ont pas de LED sur la GPIO 2 ; dans ce cas, câblez une LED avec une résistance de 220 ohms sur la GPIO 2.
Étape 2 : câbler le capteur DHT22
Connectez le module DHT22 comme suit :
| Broche DHT22 | Broche ESP32 |
|---|---|
| VCC (+) | 3V3 |
| DATA (sortie) | GPIO 4 |
| GND (-) | GND |
Si vous utilisez un capteur nu à quatre broches au lieu d'un module, ajoutez une résistance de tirage de 10 kilohms entre DATA et 3V3, et laissez la troisième broche non connectée. Évitez les GPIO 6 à 11, réservées à la mémoire flash sur les modules ESP32 classiques, et évitez les broches de strapping comme les GPIO 0, 2, 12 et 15 pour les capteurs. Le DHT22 ne doit pas être lu plus d'une fois toutes les deux secondes.
Étape 3 : comprendre les éléments de MQTT
MQTT est un protocole publish/subscribe. Votre ESP32 n'envoie pas de données à une application serveur spécifique ; il publie des messages sur des topics d'un broker, et tout client abonné à ces topics les reçoit. Pour le contexte complet, lisez d'abord ce que fait un broker MQTT.
Pour ce tutoriel IoT ESP32, nous utilisons le serveur MQTT public gratuit broker.connectmq.com. Il accepte les connexions anonymes sur le port 1883 à condition que chaque client utilise un client ID unique et non vide. Tout ce qui est publié dans l'espace public est lisible par tous : n'envoyez donc jamais de mots de passe ni de données personnelles, et utilisez un préfixe de topic unique pour que vos messages n'entrent pas en collision avec ceux des autres. L'espace public autorise 64 Ko par message, 10 messages par seconde et 10 abonnements par client, bien plus que ce dont un capteur a besoin.
Nos topics, en remplaçant k3v9 par votre propre chaîne aléatoire :
connectmq/demo/esp32-k3v9/telemetry JSON readings, published every 10 s
connectmq/demo/esp32-k3v9/status "online" / "offline" (retained, Last Will)
connectmq/demo/esp32-k3v9/led/set commands: ON or OFF
connectmq/demo/esp32-k3v9/led/state current LED state (retained)
Étape 4 : le sketch IoT ESP32 complet
Créez un nouveau sketch, collez le code ci-dessous et renseignez le nom de votre Wi-Fi, son mot de passe et le préfixe de topic.
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
// ---------- configuration ----------
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* TOPIC_PREFIX = "connectmq/demo/esp32-k3v9"; // remplacez k3v9 par votre propre chaîne
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define LED_PIN 2
const unsigned long PUBLISH_INTERVAL_MS = 10000;
// -----------------------------------
DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient net;
PubSubClient mqtt(net);
String clientId;
String topicTelemetry, topicStatus, topicLedSet, topicLedState;
unsigned long lastPublish = 0;
unsigned long lastReconnectAttempt = 0;
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
Serial.printf("Connecting to Wi-Fi %s", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 20000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("\nWi-Fi connected, IP %s\n", WiFi.localIP().toString().c_str());
} else {
Serial.println("\nWi-Fi connection failed, will retry");
}
}
void setLed(bool on) {
digitalWrite(LED_PIN, on ? HIGH : LOW);
mqtt.publish(topicLedState.c_str(), on ? "ON" : "OFF", true); // état retenu
}
void onMessage(char* topic, byte* payload, unsigned int length) {
String message;
for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
message.trim();
Serial.printf("Received [%s] %s\n", topic, message.c_str());
if (topicLedSet == topic) {
if (message == "ON") setLed(true);
else if (message == "OFF") setLed(false);
}
}
bool connectMqtt() {
Serial.printf("Connecting to MQTT %s:%u as %s\n", MQTT_HOST, MQTT_PORT, clientId.c_str());
// Last Will : le broker publie "offline" (retenu) si nous disparaissons
bool ok = mqtt.connect(clientId.c_str(), topicStatus.c_str(), 1, true, "offline");
if (ok) {
Serial.println("MQTT connected");
mqtt.publish(topicStatus.c_str(), "online", true);
mqtt.subscribe(topicLedSet.c_str(), 1);
} else {
Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
}
return ok;
}
void publishReading() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) {
Serial.println("DHT22 read failed, check wiring");
mqtt.publish(topicTelemetry.c_str(), "{\"error\":\"sensor_read_failed\"}");
return;
}
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"humidity\":%.1f,\"rssi\":%d,\"uptime\":%lu}",
temperature, humidity, WiFi.RSSI(), millis() / 1000);
mqtt.publish(topicTelemetry.c_str(), payload);
Serial.printf("Published %s\n", payload);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
dht.begin();
// Client ID unique dérivé de l'adresse MAC de la puce
uint64_t mac = ESP.getEfuseMac();
char id[32];
snprintf(id, sizeof(id), "esp32-%04X%08X", (uint16_t)(mac >> 32), (uint32_t)mac);
clientId = id;
topicTelemetry = String(TOPIC_PREFIX) + "/telemetry";
topicStatus = String(TOPIC_PREFIX) + "/status";
topicLedSet = String(TOPIC_PREFIX) + "/led/set";
topicLedState = String(TOPIC_PREFIX) + "/led/state";
connectWiFi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
return;
}
if (!mqtt.connected()) {
unsigned long now = millis();
if (now - lastReconnectAttempt > 5000) { // nouvelle tentative non bloquante toutes les 5 s
lastReconnectAttempt = now;
if (connectMqtt()) lastReconnectAttempt = 0;
}
return;
}
mqtt.loop();
if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
lastPublish = millis();
publishReading();
}
}
Téléversez le sketch et ouvrez le moniteur série à 115200 bauds. Vous devriez voir la connexion Wi-Fi, la connexion MQTT et une mesure publiée toutes les dix secondes.
Comment le sketch est structuré
- Reconnexions non bloquantes. La boucle n'attend jamais dans une boucle de tentatives serrée. Si le Wi-Fi ou MQTT tombe, elle réessaie toutes les quelques secondes tandis que le reste du code reste réactif. Les longues boucles bloquantes sont une source fréquente de redémarrages par le watchdog ; le guide de reconnexion MQTT de l'ESP32 présente des stratégies plus robustes.
- Last Will et statut retenu. Le broker stocke
onlinecomme statut retenu et le remplace automatiquement parofflinesi la carte perd l'alimentation ou la connectivité sans déconnexion propre. - État de la LED retenu. Tout tableau de bord qui s'abonne plus tard apprend immédiatement si la LED est allumée.
- Des payloads compacts. Le tampon de paquets par défaut de PubSubClient fait 256 octets, ce qui suffit largement pour ce payload. Pour des documents JSON plus volumineux, appelez
mqtt.setBufferSize(1024)avant la connexion, comme expliqué dans la taille du tampon de PubSubClient.
Étape 5 : tester depuis votre ordinateur
Observez tout ce que publie votre carte :
mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-k3v9-sub -t "connectmq/demo/esp32-k3v9/#" -v
Vous devriez voir immédiatement les messages retenus status et led/state, puis la télémétrie toutes les dix secondes :
connectmq/demo/esp32-k3v9/status online
connectmq/demo/esp32-k3v9/telemetry {"temperature":23.40,"humidity":48.2,"rssi":-58,"uptime":42}
Commandez maintenant la LED depuis un second terminal :
mosquitto_pub -h broker.connectmq.com -p 1883 -i laptop-k3v9-pub -t "connectmq/demo/esp32-k3v9/led/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i laptop-k3v9-pub -t "connectmq/demo/esp32-k3v9/led/set" -m OFF
La LED change d'état et un nouveau message led/state apparaît dans l'abonné. Débranchez la carte, attendez environ 45 secondes (1,5 fois le keepalive), et le broker publie offline sur le topic de statut. Voilà votre boucle IoT complète : mesure, publication, commande à distance et détection de présence.
Ne publiez pas de commandes avec le flag retain (-r). Une commande retenue serait livrée à nouveau à chaque reconnexion de l'ESP32.
Étape 6 : passer à un broker privé avec TLS
Le serveur MQTT public gratuit est parfait pour apprendre, mais un vrai équipement a besoin de confidentialité, d'authentification et de chiffrement. Avec ConnectMQ, vous obtenez un broker privé avec des identifiants générés et un certificat Let's Encrypt sur le port 8883.
- Inscrivez-vous sur le portail ConnectMQ, choisissez le plan Serverless gratuit, créez un broker et copiez le nom d'utilisateur, le mot de passe et le client ID générés.
- Téléchargez le certificat racine Let's Encrypt (ISRG Root X1) auquel se rattache la chaîne de certificats du broker :
curl -o isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem
- Modifiez le début du sketch comme suit. Seuls les includes, la configuration, l'objet client et l'appel de connexion changent ; tout le reste est identique.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <DHT.h>
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 8883;
const char* MQTT_USERNAME = "YOUR_USERNAME";
const char* MQTT_PASSWORD = "YOUR_PASSWORD";
const char* MQTT_CLIENT_ID = "YOUR_CLIENT_ID";
const char* TOPIC_PREFIX = "home/office/esp32-01";
// Collez l'intégralité du contenu de isrgrootx1.pem entre les marqueurs
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
...paste the certificate body from isrgrootx1.pem here...
-----END CERTIFICATE-----
)EOF";
WiFiClientSecure net;
PubSubClient mqtt(net);
Dans setup(), remplacez le client ID dérivé de l'adresse MAC par celui du portail et chargez le certificat avant la connexion :
clientId = MQTT_CLIENT_ID; // doit correspondre au client ID émis avec vos identifiants
net.setCACert(ROOT_CA);
Et dans connectMqtt(), passez les identifiants en même temps que le Last Will :
bool ok = mqtt.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD,
topicStatus.c_str(), 1, true, "offline");
Téléversez à nouveau. Le moniteur série doit afficher une connexion réussie sur le port 8883. Vérifiez depuis votre ordinateur avec les mêmes identifiants, ou ouvrez le client MQTT dans le navigateur et l'explorateur de topics en direct du portail pour voir arriver les mesures. Gardez à l'esprit que ConnectMQ lie les client ID aux identifiants qu'il émet : chaque équipement a donc besoin de son propre jeu d'identifiants ; réutiliser un même jeu sur deux cartes les fait se déconnecter mutuellement. Le tutoriel ESP32 MQTT sur SSL approfondit la gestion des certificats, et explique notamment pourquoi setInsecure() ne doit jamais partir en production.
Étape 7 : préparer le capteur au monde réel
Un sketch qui tourne une après-midi sur votre bureau n'a rien à voir avec un équipement qui fonctionne sans surveillance pendant un an. Avant d'installer la carte dans un endroit difficile d'accès, parcourez cette checklist :
- Une alimentation stable. Utilisez une alimentation 5 V de qualité capable de fournir au moins 500 mA. L'émission Wi-Fi provoque des pics de courant, et les adaptateurs bon marché causent des redémarrages par baisse de tension qui ressemblent à des bugs logiciels.
- L'emplacement du capteur. Éloignez le DHT22 de l'ESP32 lui-même et des régulateurs de tension, qui chauffent et faussent les mesures d'un degré ou plus. Un câble court et un boîtier ventilé aident.
- Un jeu d'identifiants par équipement. Ne copiez jamais le même nom d'utilisateur, mot de passe et client ID sur plusieurs cartes. Émettez un jeu par équipement pour pouvoir en révoquer un sans toucher aux autres.
- De vrais horodatages. Le champ
uptimeest utile pour le débogage, mais l'analyse a besoin de l'heure réelle. Synchronisez l'horloge avec NTP au démarrage et ajoutez un horodatage epoch à chaque mesure, comme le montre les horodatages NTP de l'ESP32 pour MQTT. - Les mises à jour à distance. Monter sur une échelle pour reflasher un capteur devient vite lassant. Prévoyez dès le départ les mises à jour over-the-air avec les mises à jour OTA de l'ESP32 via MQTT.
- Une configuration sans recompilation. Des identifiants Wi-Fi codés en dur conviennent pour une carte. Pour plusieurs, utilisez une bibliothèque de portail captif afin que chaque équipement puisse être configuré sur place.
- Le suivi de santé. Continuez à publier le RSSI et l'uptime. Une puissance de signal qui baisse lentement ou des redémarrages fréquents sont des signes avant-coureurs qu'un équipement va bientôt se déconnecter.
Aucune de ces étapes ne modifie la logique MQTT que vous avez déjà écrite. Elles font la différence entre une démo et un capteur que vous pouvez oublier parce qu'il continue simplement de rapporter ses mesures.
Dépannage
Si mqtt.state() renvoie une valeur non nulle, ce tableau indique généralement la cause :
| state() | Signification | Correction typique |
|---|---|---|
| -4 | Délai de connexion dépassé | Vérifiez le réseau, le pare-feu, le numéro de port et le keepalive |
| -2 | Échec de la connexion TCP ou TLS | Mauvais hôte ou port, certificat CA manquant, Wi-Fi pas prêt |
| -3 / -1 | Connexion perdue / déconnecté | Client ID en double, Wi-Fi instable, code bloquant dans la boucle |
| 2 | Client ID rejeté | Client ID vide ou incorrect ; utilisez celui émis avec vos identifiants |
| 4 | Nom d'utilisateur ou mot de passe incorrect | Recopiez les identifiants depuis le portail, vérifiez les espaces |
| 5 | Non autorisé | Les identifiants ne correspondent pas au client ID ou au broker |
Autres problèmes courants :
- Les mesures valent
nan. Vérifiez le câblage du DHT22, la résistance de tirage et que vous ne lisez pas plus d'une fois toutes les deux secondes. - La publication échoue silencieusement. Le message dépasse le tampon de PubSubClient ; augmentez-le avec
setBufferSize(). - La carte redémarre de façon aléatoire. Alimentation USB trop faible pendant l'émission Wi-Fi ; essayez un autre port ou un hub alimenté.
- Fonctionne sur le 1883 mais pas sur le 8883. Le certificat CA est manquant ou incomplet, ou le réseau bloque le port sortant 8883.
Pour une checklist plus longue, voir l'ESP32 ne parvient pas à se connecter au broker MQTT.
Pour aller plus loin
Avec cette base, vous pouvez construire presque n'importe quel capteur ou actionneur connecté. Bonnes étapes suivantes :
- Remplacez les chaînes construites à la main par ArduinoJson, comme le montre ESP32, ArduinoJson et MQTT.
- Remplacez le DHT22 par un BME280 via MQTT, plus précis.
- Fonctionnez sur batterie avec le deep sleep de l'ESP32 et MQTT.
- Commutez de vraies charges en toute sécurité avec un relais ESP32 commandé via MQTT.
- Comparez cette approche avec le tutoriel ESP32 MQTT, plus court et centré sur la LED, et avec la voie Python dans MicroPython umqtt sur l'ESP32.
Mettez votre ESP32 en ligne avec ConnectMQ
Quand votre prototype est prêt pour de vraies données, offrez-lui un hébergement privé. Créez un compte gratuit sur app.connectmq.com avec le plan Serverless, sans carte bancaire, créez un broker et flashez les identifiants générés dans votre sketch. Le plan gratuit prend en charge jusqu'à 25 connexions, de quoi couvrir un petit parc de capteurs plus un ou deux tableaux de bord, et lorsque vous le dépassez, un plan de broker MQTT managé passe à des centaines d'équipements sans modifier votre code.
Questions fréquentes
Quelle carte ESP32 choisir pour débuter ?
Un ESP32 DevKit standard (souvent étiqueté ESP32-WROOM-32 ou ESP32 Dev Module) est le point de départ le plus simple, car presque tous les tutoriels et bibliothèques le prennent en charge. Les cartes ESP32-S3 et ESP32-C3 fonctionnent aussi avec le code de ce tutoriel.
Faut-il un compte pour suivre ce tutoriel IoT ESP32 ?
Non. La première partie utilise l'espace public de broker.connectmq.com, qui accepte les connexions anonymes avec n'importe quel client ID unique et non vide. Vous n'avez besoin d'un compte qu'au moment de passer à un broker privé avec TLS.
Pourquoi mon ESP32 se déconnecte-t-il sans cesse du broker ?
Les causes les plus fréquentes sont deux équipements utilisant le même client ID, du code bloquant qui empêche mqtt.loop() de s'exécuter, et un signal Wi-Fi faible. Vérifiez la valeur RSSI dans la télémétrie et assurez-vous que chaque carte a son propre client ID.
PubSubClient prend-il en charge MQTT 5 ?
Non. PubSubClient implémente MQTT 3.1.1, entièrement pris en charge par le broker. Si vous avez besoin des fonctionnalités de MQTT 5 sur l'ESP32, utilisez plutôt le composant esp-mqtt d'ESP-IDF.
Est-il sûr d'utiliser setInsecure() pour TLS sur l'ESP32 ?
Uniquement pour des expériences rapides. setInsecure() chiffre le trafic mais saute la vérification du serveur, si bien qu'il ne protège pas contre l'usurpation. Chargez le certificat CA racine avec setCACert() pour tout ce qui dépasse un test.



