ESP32 et Arduino

Relais ESP32 MQTT : commuter des charges secteur avec des commandes et un état retenu

Câblez un module relais à un ESP32, commandez-le depuis un topic MQTT et publiez un état retenu pour que chaque tableau de bord et chaque application sache toujours si la charge est allumée ou éteinte.

Carte ESP32 câblée à un module relais deux canaux dans un projet de relais ESP32 MQTT
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un contrôleur de relais ESP32 MQTT vous permet de commuter des lampes, des pompes, des ventilateurs ou des contacteurs depuis n'importe quelle application, n'importe quel script ou tableau de bord capable de publier un message. Dans ce tutoriel, vous allez câbler un module relais à un ESP32, vous abonner à un topic de commande, publier l'état du relais sous forme de retained message et restaurer le dernier état connu après un redémarrage. Tout est testé sur le serveur MQTT public gratuit broker.connectmq.com, puis migré vers des identifiants pour la production.

Comment fonctionne la conception du relais ESP32 MQTT

Un nœud relais est l'actionneur utile le plus simple de l'IoT, mais c'est aussi là que de nombreux projets dérapent. L'erreur habituelle consiste à traiter MQTT comme un bouton à distance : quelque chose publie ON, l'ESP32 commute le relais, et personne ne sait jamais si la commande est réellement arrivée. Une meilleure conception sépare ce que quelqu'un souhaite de ce que fait réellement l'appareil.

Topic Sens Retained Payload
connectmq/demo/<id>/relay/1/set Application vers ESP32 Non ON, OFF, TOGGLE
connectmq/demo/<id>/relay/1/state ESP32 vers applications Oui ON ou OFF
connectmq/demo/<id>/status ESP32 vers applications Oui online ou offline (Last Will)

Le topic de commande n'est pas retenu. S'il l'était, chaque reconnexion rejouerait une ancienne commande, et un relais éteint manuellement pourrait soudain se rallumer. Le topic d'état, lui, est retenu : un tableau de bord ouvert une minute plus tard reçoit immédiatement l'état actuel sans attendre le prochain changement. Le topic status utilise le Last Will de MQTT pour que les abonnés sachent quand le nœud disparaît du réseau.

Si les retained messages et le Last Will sont nouveaux pour vous, la présentation de ce que fait un broker MQTT les explique en quelques minutes.

Matériel et câblage

Il vous faut :

  • N'importe quelle carte de développement ESP32 (DevKitC, NodeMCU-32S, modules WROOM ou WROVER : tous conviennent).
  • Un module relais à 1, 2 ou 4 canaux avec une entrée optocouplée. Les modules 5 V dotés de cavaliers « high/low level trigger » sont les plus polyvalents.
  • Des fils de liaison et un câble USB. Pour tout ce qui est relié au secteur, un vrai boîtier et des borniers.

Câblage type pour un module 5 V :

Broche du module relais Broche ESP32 Remarque
VCC 5V (VIN) Alimente la bobine du relais
GND GND Masse commune
IN1 GPIO 26 Signal de commande du canal 1
IN2 GPIO 27 Signal de commande du canal 2 (facultatif)

La plupart des cartes relais bon marché sont actives à l'état bas : le relais s'enclenche quand l'entrée est tirée vers GND. Le sketch ci-dessous comporte une seule constante pour inverser cette logique. Évitez les GPIO 0, 2, 12 et 15 pour les relais, car ce sont des broches de configuration au démarrage (strapping pins), et un relais qui claque au démarrage est à la fois agaçant et, avec certaines charges, dangereux. Les GPIO 34 à 39 sont uniquement des entrées et ne fonctionneront pas.

Une remarque de sécurité : si vous commutez la tension secteur, le module relais doit être dimensionné pour la charge, la partie haute tension doit être enfermée dans un boîtier, et le travail doit respecter les normes électriques locales. Pendant le développement du firmware, commutez un ruban LED basse tension ou rien du tout, et écoutez le clic.

Installer les bibliothèques

Dans l'Arduino IDE 2.x, installez le paquet de cartes esp32 d'Espressif via le gestionnaire de cartes et la bibliothèque PubSubClient de Nick O'Leary via le gestionnaire de bibliothèques. Aucune autre bibliothèque n'est nécessaire. Si vous n'avez jamais écrit de sketch MQTT sur ESP32, le tutoriel IoT ESP32 détaille la configuration de la carte et votre première publication.

Le sketch complet de relais ESP32 MQTT

Le sketch ci-dessous prend en charge un nombre quelconque de relais, gère les reconnexions sans bloquer la boucle et publie un état retenu après chaque changement.

#include <WiFi.h>
#include <PubSubClient.h>

// ---------- Configuration ----------
const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* DEVICE_ID = "relay-7f3a91";          // rendez cet ID unique
const String BASE = String("connectmq/demo/") + DEVICE_ID;

const bool ACTIVE_LOW = true;                     // la plupart des cartes relais
const uint8_t RELAY_PINS[] = {26, 27};
const uint8_t RELAY_COUNT = sizeof(RELAY_PINS);
bool relayState[RELAY_COUNT] = {false};
bool restoreWindow = true;                        // accepter l'état retenu une fois au démarrage
unsigned long connectedAt = 0;

WiFiClient net;
PubSubClient mqtt(net);
unsigned long lastAttempt = 0;

void applyRelay(uint8_t i, bool on) {
  relayState[i] = on;
  digitalWrite(RELAY_PINS[i], (on ^ ACTIVE_LOW) ? HIGH : LOW);
}

void publishState(uint8_t i) {
  String topic = BASE + "/relay/" + String(i + 1) + "/state";
  mqtt.publish(topic.c_str(), relayState[i] ? "ON" : "OFF", true);
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  String t(topic);
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  msg.trim();
  msg.toUpperCase();

  // Attendu : <BASE>/relay/<n>/set ou <BASE>/relay/<n>/state
  int start = BASE.length() + 7;                  // sauter "/relay/"
  int slash = t.indexOf('/', start);
  if (slash < 0) return;
  int n = t.substring(start, slash).toInt();
  if (n < 1 || n > RELAY_COUNT) return;
  uint8_t i = n - 1;
  String leaf = t.substring(slash + 1);

  if (leaf == "state") {
    // État retenu d'avant le redémarrage : le restaurer une fois, puis l'ignorer
    if (restoreWindow) applyRelay(i, msg == "ON");
    return;
  }
  if (leaf != "set") return;

  if (msg == "ON")          applyRelay(i, true);
  else if (msg == "OFF")    applyRelay(i, false);
  else if (msg == "TOGGLE") applyRelay(i, !relayState[i]);
  else return;                                    // ignorer les commandes inconnues

  publishState(i);
  Serial.printf("Relay %d -> %s\n", n, relayState[i] ? "ON" : "OFF");
}

bool connectMqtt() {
  String willTopic = BASE + "/status";
  bool ok = mqtt.connect(DEVICE_ID, nullptr, nullptr,
                         willTopic.c_str(), 1, true, "offline");
  if (!ok) {
    Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
    return false;
  }
  mqtt.publish(willTopic.c_str(), "online", true);
  for (uint8_t i = 0; i < RELAY_COUNT; i++) {
    String base = BASE + "/relay/" + String(i + 1);
    mqtt.subscribe((base + "/set").c_str(), 1);
    if (restoreWindow) mqtt.subscribe((base + "/state").c_str(), 1);
  }
  connectedAt = millis();
  Serial.println("MQTT connected");
  return true;
}

void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < RELAY_COUNT; i++) {
    pinMode(RELAY_PINS[i], OUTPUT);
    applyRelay(i, false);                         // état sûr par défaut avant le Wi-Fi
  }
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
  mqtt.setSocketTimeout(5);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) return;

  if (!mqtt.connected()) {
    if (millis() - lastAttempt > 5000) {
      lastAttempt = millis();
      connectMqtt();
    }
    return;
  }
  mqtt.loop();

  // Fermer la fenêtre de restauration deux secondes après la première connexion
  if (restoreWindow && millis() - connectedAt > 2000) {
    restoreWindow = false;
    for (uint8_t i = 0; i < RELAY_COUNT; i++) {
      String s = BASE + "/relay/" + String(i + 1) + "/state";
      mqtt.unsubscribe(s.c_str());
      publishState(i);                            // confirmer l'état réel
    }
  }
}

Le rôle de la fenêtre de restauration

Lorsque l'ESP32 redémarre après une coupure de courant, les relais démarrent en position sûre OFF. Lors de la première connexion, le sketch s'abonne brièvement à ses propres topics state retenus. Le broker délivre immédiatement le dernier état qu'il conserve, le sketch l'applique, puis au bout de deux secondes il se désabonne et republie l'état réel. Si vous préférez que les relais démarrent toujours éteints, réglez restoreWindow sur false. Pour les charges critiques comme les radiateurs, « toujours éteint après un redémarrage » est généralement la politique la plus sûre.

Gardez à l'esprit que cela suppose que le broker conserve encore le retained message. Les retained messages vivent aussi longtemps que le broker les garde : considérez donc la restauration comme une commodité plutôt qu'une garantie, et concevez la charge de sorte que OFF soit toujours un état acceptable.

Tester le relais depuis votre ordinateur

Ouvrez un terminal et observez tout ce qui se trouve sous le préfixe de votre appareil :

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-7f3a91 -v -t "connectmq/demo/relay-7f3a91/#"

Dans un second terminal, envoyez des commandes :

mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m TOGGLE

Vous devriez entendre le clic et voir l'état retenu renvoyé en écho :

connectmq/demo/relay-7f3a91/status online
connectmq/demo/relay-7f3a91/relay/1/state ON
connectmq/demo/relay-7f3a91/relay/1/state OFF

Débranchez maintenant l'ESP32. En environ une fois et demie l'intervalle de keep alive, le broker publie le Last Will et l'abonné affiche status offline. C'est exactement le signal dont un tableau de bord a besoin pour griser l'interrupteur. Comme broker.connectmq.com est un serveur MQTT public gratuit, quiconque devine votre topic peut commuter votre relais : ne laissez donc jamais une vraie charge reliée à la zone publique.

Liste de vérification pour le dépannage

  1. Le relais claque au démarrage. Vous utilisez une broche de configuration au démarrage, ou le module est actif à l'état bas et la broche flotte avant l'exécution de setup(). Passez sur GPIO 26/27/32/33 et fixez si possible le niveau de la broche avant pinMode.
  2. Le relais ne commute jamais alors que la LED du module s'allume. Beaucoup de modules ont besoin de 5 V sur VCC pour la bobine ; 3,3 V allume la LED mais ne suffit pas à fermer les contacts. Certaines cartes nécessitent aussi le cavalier JD-VCC.
  3. Les commandes fonctionnent une fois, puis plus du tout. Cherchez state=-3 (connexion perdue) dans le journal série. Utiliser le même client ID sur deux appareils les fait se déconnecter mutuellement en boucle.
  4. L'ESP32 redémarre quand le relais commute. Les charges inductives provoquent des chutes de tension et du bruit. Alimentez la bobine du relais avec une alimentation 5 V séparée à masse commune, et utilisez un snubber ou une protection de roue libre côté charge.
  5. Le topic d'état affiche une mauvaise valeur après un redémarrage. Un autre client publie des retained messages sur le topic state. Seul l'appareil lui-même doit y publier.
  6. Rien n'arrive. Les noms de topics sont sensibles à la casse, et une barre oblique finale parasite crée un topic différent.

Passer en production avec des identifiants et le TLS

Pour une vraie installation, créez un broker dans le portail ConnectMQ et utilisez le nom d'utilisateur, le mot de passe et le client ID générés. Remplacez WiFiClient par WiFiClientSecure, connectez-vous au port 8883 et passez les identifiants à connect() :

#include <WiFiClientSecure.h>
WiFiClientSecure net;
// dans setup() : net.setCACert(ROOT_CA_PEM);   // ISRG Root X1 (Let's Encrypt)
mqtt.setServer("broker.connectmq.com", 8883);
mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD",
             willTopic.c_str(), 1, true, "offline");

Comme les client ID sont liés aux identifiants émis, utilisez le client ID généré par le portail plutôt qu'un ID inventé. La configuration complète des certificats, y compris la synchronisation de l'heure et les questions de mémoire, est traitée dans ESP32 MQTT sur SSL/TLS. Si votre nœud relais doit résister à un Wi-Fi instable, la stratégie d'attente progressive décrite dans la logique de reconnexion MQTT sur ESP32 mérite aussi d'être ajoutée.

Un broker MQTT managé compte surtout pour les actionneurs : un relais injoignable est pire qu'un capteur illisible. Avec ConnectMQ, vous bénéficiez du TLS sur 8883, d'identifiants par appareil et d'un portail où vous pouvez suivre relay/+/state dans l'explorateur de topics en direct ou publier une commande de test depuis le client intégré au navigateur, sans toucher à un terminal.

Idées pour enrichir le nœud relais

  • Minuteries. Acceptez {"state":"ON","seconds":600} en JSON et éteignez automatiquement après dix minutes. Gardez la minuterie sur l'appareil, pour qu'il s'éteigne même si le réseau tombe.
  • Bouton local. Ajoutez un bouton-poussoir qui bascule le relais et publie le nouvel état, afin que l'interrupteur physique et l'application ne soient jamais en désaccord.
  • Verrouillages. Pour l'inversion de sens d'un moteur ou les vannes deux voies, refusez d'activer les deux canaux simultanément dans le firmware, pas dans l'application.
  • Servos ou LED. Le même schéma commande/état fonctionne pour le pilotage d'un servomoteur via MQTT et les rubans LED adressables.
  • Intégration domotique. Dans une maison connectée, Home Assistant peut piloter ce relais avec une entité switch MQTT reliée aux topics de commande et d'état. Jeedom et Domoticz savent aussi communiquer en MQTT.

Démarrer avec ConnectMQ

Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire. Vous obtenez des identifiants en moins d'une minute, le TLS prêt à l'emploi et un explorateur de topics en direct pour voir vos relais commuter. Quand l'offre gratuite ne suffit plus, le même firmware passe à une offre payante ou dédiée sans rien changer d'autre que les identifiants.

Questions fréquentes

Le topic de commande du relais doit-il être retenu ?

Non. Retenez le topic d'état, pas le topic de commande. Une commande retenue serait rejouée à chaque reconnexion et pourrait commuter une charge de manière inattendue.

Quel QoS utiliser pour les commandes de relais ?

Le QoS 1 est le choix pratique : la commande est livrée au moins une fois, et comme ON et OFF sont idempotents, un doublon ne fait aucun mal. PubSubClient peut s'abonner en QoS 0 ou 1.

Puis-je piloter plusieurs relais avec un seul ESP32 ?

Oui. Ajoutez les broches à RELAY_PINS et chacune obtient ses propres topics relay/<n>/set et relay/<n>/state. Un seul abonnement avec wildcard à relay/+/set fonctionne aussi si vous extrayez le numéro de canal du topic.

Comment savoir si l'ESP32 est hors ligne ?

Abonnez-vous au topic status retenu. L'ESP32 publie online à la connexion, et le broker publie le message Last Will offline si la connexion tombe sans déconnexion propre.

Articles liés

Exemple MQTT ESP-IDF : construire un client esp-mqtt à partir de zéro

Construisez un projet ESP-IDF à partir d'un dossier vide avec le composant esp-mqtt : configuration du Wi-Fi en mode station, gestionnaire d'événements MQTT, publication et abonnement, propriétés MQTT 5 et TLS avec le bundle de certificats.

9 min de lecture

Horodatage NTP ESP32 MQTT : ajouter une heure précise à chaque message

Synchronisez l'horloge de l'ESP32 avec NTP, formatez des horodatages ISO 8601 et epoch en millisecondes, et joignez-les aux payloads MQTT pour que les mesures conservent leur véritable heure de capture même lorsqu'elles sont publiées en retard.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.