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
- 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 avantpinMode. - 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. - 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. - 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.
- 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. - 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.



