Skip to content
Learni
View all tutorials
Systèmes Embarqués

Comment implémenter un serveur IoT sur ESP32 en 2026

Introduction

L'ESP32, microcontrôleur puissant d'Espressif avec WiFi/Bluetooth intégré, domine les projets IoT en 2026 grâce à son dual-core, ses 520 Ko SRAM et son support FreeRTOS. Ce tutoriel avancé vous guide pour créer un nœud IoT complet : connexion WiFi sécurisée, lecture capteur DHT22 (température/humidité), serveur web asynchrone pour dashboard en temps réel, publication MQTT vers broker distant, et mises à jour OTA sans câble. Contrairement aux approches basiques, nous utilisons ESPAsyncWebServer pour une réactivité optimale (non-bloquant), gestion d'erreurs robuste et optimisation mémoire. Idéal pour domotique, monitoring industriel ou prototypes scalables. À la fin, vous aurez un firmware production-ready, bookmarkable par tout embedded dev. Durée estimée : 2h pour implémenter et tester.

Prérequis

  • Arduino IDE 2.3.2+ installé (téléchargez sur arduino.cc)
  • Pack ESP32 boards 3.0.5+ (via Boards Manager)
  • Bibliothèques : DHT sensor library by Adafruit v1.4.6, ESPAsyncWebServer v3.1.0, AsyncTCP v1.1.1, PubSubClient v2.8
  • Matériel : ESP32 DevKit V1, capteur DHT22, résistances 4.7kΩ (pull-up), breadboard, câbles jumper
  • Broker MQTT gratuit (ex: broker.hivemq.com:1883 pour tests)
  • Connaissances : C++, multitâche basique, réseaux TCP/IP

Installation du support ESP32

terminal
cd ~
# Ouvrir Arduino IDE > File > Preferences
# Ajouter URL dans Additional Boards Manager: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

# Dans Tools > Board > Boards Manager, chercher "esp32" et installer "esp32 by Espressif Systems" v3.0.5

echo "Boards Manager URL ajoutée. Redémarrez IDE et sélectionnez: Tools > Board > ESP32 Arduino > ESP32 Dev Module"

# Vérifier ports: Tools > Port > /dev/cu.usbserial-* (Mac) ou COM* (Win)
# Upload speed: 921600, Flash freq: 80MHz, Flash mode: QIO

Ces commandes et étapes configurent l'IDE pour compiler/flasher l'ESP32. Utilisez la dernière version 3.0+ pour compatibilité PSRAM et sécurité WiFi renforcée (WPA3). Piège : sans cette URL, les boards n'apparaissent pas ; testez toujours avec un sketch Blink post-install.

Sketch basique : LED Blink

blink.ino
#include "Arduino.h"

#define LED_PIN 2

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  Serial.println("ESP32 Blink démarré");
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  Serial.println("LED ON");
  delay(1000);
  digitalWrite(LED_PIN, LOW);
  Serial.println("LED OFF");
  delay(1000);
}

Ce sketch minimal valide l'installation : LED intégrée (GPIO2) clignote toutes les secondes, logs Serial pour debug. Compilez (Ctrl+R), uploadez (Ctrl+U). Avancé : delay() est bloquant ; en IoT réel, préférez millis() pour non-blocking. Vérifiez monitor série à 115200 bauds.

Connexion WiFi et serveur web basique

Maintenant, connectons l'ESP32 à votre réseau WiFi avec authentification WPA2/3. Nous introduisons ESPAsyncWebServer pour un serveur HTTP asynchrone : contrairement à WebServer synchrone, il gère 10x plus de requêtes sans freezer le loop(). Analogie : comme un barman multitâche vs un qui sert un par un. Configurez vos SSID/password dans le code suivant.

WiFi + Serveur web async

wifi_webserver.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>

const char* ssid = "VOTRE_SSID";
const char* password = "VOTRE_PASSWORD";

AsyncWebServer server(80);

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connexion WiFi...");
  }
  Serial.println("IP: " + WiFi.localIP().toString());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/plain", "Hello ESP32 IoT Server!");
  });

  server.begin();
  Serial.println("Serveur démarré");
}

void loop() {
  // Non-bloquant !
}

Ce code connecte WiFi (timeout auto), démarre serveur port 80. Accédez à l'IP via navigateur : "Hello". Avantages async : loop() libre pour tâches futures. Piège : oubliez WiFi.mode(WIFI_STA); par défaut ok, mais vérifiez firewall/routeur bloque pas 80.

Intégration capteur DHT22

dht_webserver.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <DHT.h>

#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

const char* ssid = "VOTRE_SSID";
const char* password = "VOTRE_PASSWORD";
AsyncWebServer server(80);

String processor(const String& var) {
  if (var == "TEMPERATURE") return String(dht.readTemperature());
  if (var == "HUMIDITY") return String(dht.readHumidity());
  return String();
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(1000);
  Serial.println("IP: " + WiFi.localIP().toString());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", R"raw(<!DOCTYPE html><html><body><h1>IoT Dashboard</h1><p>Temp: TEMPERATURE °C</p><p>Hum: HUMIDITY %</p></body></html>)raw", processor);
  });

  server.begin();
}

void loop() {}

Ajoute DHT22 sur GPIO4 (pull-up 4.7kΩ requis). Dashboard HTML dynamique via processor() : lit capteur à chaque GET /. Non-bloquant grâce à async. Piège : dht.read() timeout 2s si bus défaillant ; ajoutez isNan() checks en prod (ex: if(isnan(temp)) return "N/A";).

Publication MQTT et monitoring distant

MQTT permet push data vers dashboards (Node-RED, Home Assistant). Ici, publie temp/hum toutes 30s sur topic "esp32/sensor". Utilisez broker public pour tests, puis votre propre (Mosquitto). Avancé : QoS 1 pour fiabilité.

Serveur IoT complet avec MQTT

iot_mqtt_complete.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <DHT.h>
#include <PubSubClient.h>

#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

const char* ssid = "VOTRE_SSID";
const char* password = "VOTRE_PASSWORD";
const char* mqtt_server = "broker.hivemq.com";

WiFiClient espClient;
PubSubClient client(espClient);
AsyncWebServer server(80);
unsigned long lastMsg = 0;

String processor(const String& var) {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  if (var == "TEMPERATURE") return isnan(t) ? "N/A" : String(t);
  if (var == "HUMIDITY") return isnan(h) ? "N/A" : String(h);
  return String();
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("IP: " + WiFi.localIP().toString());

  client.setServer(mqtt_server, 1883);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", R"raw(<!DOCTYPE html><html><body><h1>ESP32 IoT</h1><p>Temp: TEMPERATURE °C</p><p>Hum: HUMIDITY %</p></body></html>)raw", processor);
  });
  server.begin();
}

void loop() {
  if (!client.connected()) {
    while (!client.connect("ESP32Client")) { delay(5000); }
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastMsg > 30000) {
    lastMsg = now;
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    if (!isnan(t) && !isnan(h)) {
      client.publish("esp32/sensor", ("T:" + String(t) + " H:" + String(h)).c_str());
    }
  }
}

Firmware complet : web dashboard + MQTT pub toutes 30s (millis() non-bloquant). Reconnect auto MQTT. Testez : MQTT Explorer sur "esp32/sensor". Piège : String() fuit mémoire si abusé ; limitez à cron-like. QoS=0 pour perf.

Ajout OTA pour mises à jour wireless

iot_ota_final.ino
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <DHT.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>

#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

const char* ssid = "VOTRE_SSID";
const char* password = "VOTRE_PASSWORD";
const char* mqtt_server = "broker.hivemq.com";
const char* ota_password = "admin";

WiFiClient espClient;
PubSubClient client(espClient);
AsyncWebServer server(80);
unsigned long lastMsg = 0;

String processor(const String& var);

void setup() {
  Serial.begin(115200);
  dht.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("IP: " + WiFi.localIP().toString());

  ArduinoOTA.setHostname("esp32-iot");
  ArduinoOTA.setPassword(ota_password);
  ArduinoOTA.onStart([]() { Serial.println("OTA Start"); });
  ArduinoOTA.onEnd([]() { Serial.println("OTA End"); });
  ArduinoOTA.onError([](ota_error_t error) { Serial.printf("OTA Error[%u]: ", error); });
  ArduinoOTA.begin();

  client.setServer(mqtt_server, 1883);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", R"raw(<!DOCTYPE html><html><body><h1>ESP32 IoT OTA</h1><p>Temp: TEMPERATURE °C</p><p>Hum: HUMIDITY %</p></body></html>)raw", processor);
  });
  server.begin();
}

void loop() {
  ArduinoOTA.handle();
  if (!client.connected()) {
    while (!client.connect("ESP32Client")) { delay(5000); }
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastMsg > 30000) {
    lastMsg = now;
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    if (!isnan(t) && !isnan(h)) {
      client.publish("esp32/sensor", ("T:" + String(t) + " H:" + String(h)).c_str());
    }
  }
}

String processor(const String& var) {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  if (var == "TEMPERATURE") return isnan(t) ? "N/A" : String(t);
  if (var == "HUMIDITY") return isnan(h) ? "N/A" : String(h);
  return String();
}

Version finale avec OTA : mettez à jour via IDE (Tools > Port > esp32-iot at IP). Password protège. handle() dans loop(). Piège : partition scheme "Default 4MB with OTA" dans Tools ; sans, brick possible. Prod : HTTPS + auth.

Bonnes pratiques

  • Non-blocking everywhere : millis() > delay(), tasks FreeRTOS pour heavy ops (ex: xTaskCreatePinnedToCore).
  • Gestion mémoire : Évitez String concat ; utilisez char buffers. Monitorez heap via Serial.println(ESP.getFreeHeap()).
  • Sécurité : WPA3, MQTT TLS (WiFiClientSecure), API keys, firewall port 80->HTTPS (ESPmDNS).
  • Debug robuste : NTP pour timestamps (NTPClient lib), crash dumps (ESP.getResetReason()).
  • Power optim : deep sleep si battery (esp_sleep_enable_timer_wakeup()).

Erreurs courantes à éviter

  • Watchdog reset : loop() bloqué par delay() long ; forcez yield() ou vTaskDelay().
  • WiFi reconnect infini : Ajoutez timeout (WiFi.reconnect() + max 10 tries).
  • DHT22 NaN : Pull-up manquant ou câble >20cm ; debounce avec 3 reads moyennes.
  • OTA fail : Flash size mismatch ou password vide ; toujours testez sur breadboard d'abord.

Pour aller plus loin