Le pilotage d'éclairage intelligent MQTT permet à n'importe quelle application, tableau de bord ou automatisation de faire varier une lumière, rappeler une scène ou exécuter un planning simplement en publiant un message. Dans ce tutoriel, vous allez construire un variateur PWM ESP32 pour rubans LED avec des fondus fluides, concevoir des topics adaptés à une pièce comme à un bâtiment entier, stocker les scènes sous forme de retained messages et exécuter des plannings depuis un petit service Python connecté à un broker ConnectMQ.
Choisir le matériel d'éclairage pour MQTT
Avant d'écrire le firmware, déterminez quel type d'éclairage vous pilotez. Chaque option s'intègre à MQTT d'une manière légèrement différente.
| Matériel | Connexion à MQTT | Idéal pour | Points de vigilance |
|---|---|---|---|
| Rubans LED basse tension avec driver PWM ESP32 | Firmware personnalisé, comme dans ce tutoriel | Bureaux, étagères, meubles, éclairage d'ambiance | Dimensionnement de l'alimentation et chute de tension sur les grandes longueurs |
| LED adressables (WS2812B et similaires) | Firmware personnalisé ou WLED | Effets et couleur par pixel | Consommation de courant, longueur de la ligne de données |
| Variateurs et relais secteur avec firmware MQTT | Appareils Tasmota, Shelly ou similaires | Plafonniers existants | Uniquement du matériel certifié, type de variateur adapté à la lampe |
| Éclairage DALI ou KNX | Passerelle du bus vers MQTT | Bureaux et bâtiments tertiaires | Le bus reste le contrôleur ; MQTT apporte la visibilité |
Dans les logements, les rubans PWM et les relais connectés couvrent la plupart des besoins. Dans les bâtiments tertiaires, l'éclairage est généralement déjà sur DALI ou KNX, et la bonne démarche consiste à relier ce bus à MQTT plutôt qu'à le remplacer. La structure de topics ci-dessous fonctionne pour les quatre cas, ce qui est la principale raison de la standardiser tôt.
Conception des topics pour l'éclairage
Une lumière a besoin de trois éléments sur MQTT : un topic de commande, un topic d'état et un topic de disponibilité. Les séparer évite les boucles de rétroaction et permet aux tableaux de bord d'afficher l'état réel.
<prefix>/<room>/<light>/set commands in (JSON)
<prefix>/<room>/<light>/state actual state out (retained)
<prefix>/<room>/<light>/availability "online" / "offline" (retained, last will)
<prefix>/scenes/<scene> scene definitions (retained)
<prefix>/<room>/scene/set recall a scene by name
Les commandes utilisent JSON afin de pouvoir les enrichir plus tard sans casser les anciens clients :
{"state": "ON", "brightness": 180, "transition": 2.0}
brightness va de 0 à 255 et transition est la durée du fondu en secondes. Ce format correspond au schéma JSON que l'intégration MQTT de Home Assistant comprend pour les lumières : le même variateur peut donc apparaître dans Home Assistant sans modification du firmware.
Matériel
- Une carte de développement ESP32.
- Un ruban LED monochrome 12 V ou 24 V et une alimentation adaptée.
- Un MOSFET canal N à niveau logique (par exemple IRLZ44N ou AO3400 sur module) par canal, ou une carte driver MOSFET prête à l'emploi.
- Une résistance de grille de 220 ohms et une résistance de pull-down de 10 kOhms par canal.
Reliez la grille du MOSFET à la GPIO 25 via la résistance, la source à la masse (commune avec celle de l'ESP32) et le drain au fil négatif du ruban LED. Le fil positif du ruban va au positif de l'alimentation. N'alimentez jamais un ruban depuis la broche 5 V de l'ESP32.
Pour les variateurs secteur, utilisez un produit certifié qui parle déjà MQTT, comme un appareil Tasmota ou Shelly, plutôt que de construire vous-même du matériel en tension secteur. La conception de topics de cet article reste valable ; consultez Tasmota MQTT pour le flashage et la configuration.
Le firmware du variateur ESP32
Le sketch utilise l'API LEDC du core Arduino ESP32 3.x (ledcAttach et ledcWrite), PubSubClient et ArduinoJson 7. Il applique une courbe gamma pour que la variation paraisse linéaire à l'œil, effectue des fondus fluides et publie un état retained après chaque changement.
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <math.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const int PORT = 1883;
#define PREFIX "connectmq/demo/light-8pz3/office-201/desk-strip"
const char* T_SET = PREFIX "/set";
const char* T_STATE = PREFIX "/state";
const char* T_AVAIL = PREFIX "/availability";
const int LED_PIN = 25;
const int PWM_FREQ = 5000; // Hz, au-dessus du scintillement visible
const int PWM_BITS = 12; // 0..4095
const int PWM_MAX = (1 << PWM_BITS) - 1;
WiFiClient net;
PubSubClient mqtt(net);
bool on = false;
int brightness = 255; // dernière luminosité 1..255
float current = 0, target = 0; // 0..255
float step = 0;
unsigned long lastStep = 0;
void writePwm(float level) {
float norm = level / 255.0f;
int duty = (int)(powf(norm, 2.2f) * PWM_MAX + 0.5f); // correction gamma
ledcWrite(LED_PIN, duty);
}
void publishState() {
JsonDocument doc;
doc["state"] = on ? "ON" : "OFF";
doc["brightness"] = brightness;
char buf[96];
size_t n = serializeJson(doc, buf);
mqtt.publish(T_STATE, (const uint8_t*)buf, n, true);
}
void startFade(float to, float seconds) {
target = to;
int steps = max(1, (int)(seconds * 50)); // 50 mises à jour par seconde
step = (target - current) / steps;
if (seconds <= 0) { current = target; writePwm(current); step = 0; }
}
void onMessage(char* topic, byte* payload, unsigned int len) {
JsonDocument doc;
if (deserializeJson(doc, payload, len)) return;
float transition = doc["transition"] | 0.5f;
if (doc["brightness"].is<int>()) {
brightness = constrain(doc["brightness"].as<int>(), 1, 255);
on = true;
}
if (doc["state"].is<const char*>()) {
on = strcmp(doc["state"].as<const char*>(), "ON") == 0;
}
startFade(on ? brightness : 0, transition);
publishState();
}
void connectMqtt() {
while (!mqtt.connected()) {
String id = "light-" + String((uint32_t)ESP.getEfuseMac(), HEX);
if (mqtt.connect(id.c_str(), T_AVAIL, 1, true, "offline")) {
mqtt.publish(T_AVAIL, "online", true);
mqtt.subscribe(T_SET, 1);
publishState();
} else {
delay(2000);
}
}
}
void setup() {
ledcAttach(LED_PIN, PWM_FREQ, PWM_BITS);
writePwm(0);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
mqtt.setServer(BROKER, PORT);
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (step != 0 && millis() - lastStep >= 20) {
lastStep = millis();
current += step;
if ((step > 0 && current >= target) || (step < 0 && current <= target)) {
current = target;
step = 0;
}
writePwm(current);
}
}
Notez que le fondu s'exécute dans loop() sans delay(), de sorte que l'appareil continue de répondre à MQTT pendant la variation. Le last will marque la lumière offline si elle perd l'alimentation ou le Wi-Fi.
Tester le variateur
# observer tout le trafic de cette démo
mosquitto_sub -h broker.connectmq.com -p 1883 -i light-view-8pz3 -t "connectmq/demo/light-8pz3/#" -v
# allumer à environ 70 % avec un fondu de 2 secondes
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
-t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
-m '{"state":"ON","brightness":180,"transition":2}'
# éteindre avec un fondu lent
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
-t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
-m '{"state":"OFF","transition":5}'
Le serveur MQTT public gratuit est ouvert à tous : un inconnu pourrait donc commuter votre lumière de démonstration. C'est sans conséquence sur un bureau, mais c'est précisément pourquoi un véritable éclairage doit fonctionner sur un broker privé avec identifiants.
Les scènes en retained messages
Une scène est un ensemble nommé d'états de lumières. Stocker chaque scène comme retained message sur le broker signifie que chaque contrôleur, tableau de bord ou panneau mural voit les mêmes définitions, et vous pouvez modifier une scène en publiant une nouvelle version.
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
-t "connectmq/demo/light-8pz3/scenes/focus" \
-m '{"office-201/desk-strip":{"state":"ON","brightness":230},"office-201/ceiling":{"state":"ON","brightness":200}}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
-t "connectmq/demo/light-8pz3/scenes/presentation" \
-m '{"office-201/desk-strip":{"state":"ON","brightness":40},"office-201/ceiling":{"state":"OFF"}}'
Rappel de scènes et plannings en Python
Un petit service s'abonne aux définitions de scènes et aux demandes de rappel, répercute les rappels de scènes sur chaque lumière et exécute un planning quotidien. Il utilise paho-mqtt 2.x et uniquement la bibliothèque standard.
import json
import time
import uuid
from datetime import datetime
import paho.mqtt.client as mqtt
BROKER, PORT = "broker.connectmq.com", 1883
PREFIX = "connectmq/demo/light-8pz3"
scenes = {}
SCHEDULE = [ # (HH:MM, jours de semaine 0=lun..6=dim, scène)
("08:00", range(0, 5), "focus"),
("12:30", range(0, 5), "presentation"),
("19:00", range(0, 7), "off"),
]
scenes["off"] = {"office-201/desk-strip": {"state": "OFF", "transition": 10}}
def recall(client, name):
scene = scenes.get(name)
if not scene:
print("Unknown scene", name)
return
for light, cmd in scene.items():
cmd = {"transition": 1.5, **cmd}
client.publish(f"{PREFIX}/{light}/set", json.dumps(cmd), qos=1)
print("Recalled", name)
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(f"{PREFIX}/scenes/+", qos=1)
client.subscribe(f"{PREFIX}/+/scene/set", qos=1)
def on_message(client, userdata, msg):
parts = msg.topic.split("/")
if parts[-2] == "scenes":
if msg.payload:
scenes[parts[-1]] = json.loads(msg.payload)
elif parts[-2] == "scene" and parts[-1] == "set":
recall(client, msg.payload.decode().strip())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"light-sched-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, 60)
client.loop_start()
last_run = None
while True:
now = datetime.now()
key = now.strftime("%Y-%m-%d %H:%M")
if key != last_run:
for hhmm, days, scene in SCHEDULE:
if now.strftime("%H:%M") == hhmm and now.weekday() in days:
recall(client, scene)
last_run = key
time.sleep(5)
Rappelez une scène manuellement avec :
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 \
-t "connectmq/demo/light-8pz3/office-201/scene/set" -m "focus"
Si vous préférez un éditeur visuel pour les plannings et les déclencheurs basés sur le soleil, les mêmes topics fonctionnent depuis Node-RED ou depuis les automatisations Home Assistant. Jeedom et Domoticz savent aussi communiquer en MQTT : dans une maison connectée, ils peuvent publier sur les mêmes topics set.
Étendre au blanc réglable et au RGB
La même structure de firmware s'adapte aux rubans multicanaux. Un ruban à blanc réglable possède un canal chaud et un canal froid ; un ruban RGB ou RGBW en a trois ou quatre. Associez un canal LEDC par broche de couleur et enrichissez la commande JSON de champs facultatifs, par exemple "color_temp": 300 en mireds pour le blanc réglable, ou "color": {"r": 255, "g": 140, "b": 0} pour le RGB. Gardez brightness comme champ séparé et mettez chaque canal à l'échelle avec lui, afin que luminosité et couleur puissent varier indépendamment et qu'une scène puisse définir les deux en un seul message.
Deux points pratiques évitent des ennuis par la suite. Mélangez le blanc réglable en maintenant constante la somme des deux canaux pour une luminosité donnée, sinon la lumière devient nettement plus intense au milieu de la plage. Et publiez l'état complet résultant, couleur comprise, sur le topic d'état après chaque commande, car les tableaux de bord et Home Assistant s'en servent pour positionner correctement le sélecteur de couleur.
Liste de contrôle pour la production
- Passez à un broker privé sur le port 8883 avec TLS et donnez à chaque variateur ses propres identifiants.
- Gardez les interrupteurs muraux fonctionnels en local ; MQTT doit ajouter du contrôle, pas devenir le seul moyen d'allumer une lumière.
- Combinez l'éclairage avec des capteurs d'occupation de bureau pour que les pièces vides s'éteignent automatiquement.
- Dans les bâtiments équipés de KNX ou DALI, laissez le bus existant assurer la commutation et reliez-le à MQTT, comme dans notre intégration KNX MQTT.
- Utilisez la QoS 1 pour les commandes et envoyez toujours des valeurs absolues, jamais « toggle », pour qu'une livraison en double soit sans effet.
| Piège | Symptôme | Correctif |
|---|---|---|
| Pas de correction gamma | L'essentiel du changement visible se produit en bas de la plage | Appliquer une courbe de puissance comme dans le sketch |
Fondu avec delay() |
La lumière ignore les commandes pendant les fondus | Faire progresser le fondu dans loop() |
| État non retained | Les tableaux de bord affichent « inconnu » après reconnexion | Publier l'état avec le flag retain |
| Commandes sur le topic d'état | Les lumières oscillent ou font écho | Séparer les topics set et state |
| Fréquence PWM trop basse | Scintillement visible à la caméra | Utiliser 1 kHz ou plus |
Un broker MQTT managé garde le pilotage de l'éclairage disponible sans serveur dans le bâtiment, et le guide de l'automatisation IoT montre comment l'éclairage s'articule avec les autres systèmes du bâtiment.
Faites tourner votre éclairage sur ConnectMQ
Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire. Flashez vos variateurs avec les identifiants délivrés, puis utilisez le client MQTT intégré au navigateur dans le portail ConnectMQ pour envoyer des commandes de test et rappeler des scènes pendant la mise en service de chaque pièce.
Questions fréquentes
Pourquoi utiliser des commandes JSON plutôt que de simples ON et OFF ?
JSON permet à un seul message de transporter l'état, la luminosité et la durée de transition, et vous pouvez ajouter plus tard des champs comme la température de couleur sans casser les clients existants.
Les topics d'état des lumières doivent-ils être retained ?
Oui. Un état retained permet à tout tableau de bord ou application d'afficher l'état correct dès l'abonnement, sans attendre le prochain changement.
Puis-je piloter des lumières sur secteur avec un ESP32 ?
Utilisez un variateur ou un module relais secteur certifié compatible MQTT, comme un appareil Tasmota ou Shelly. Construire soi-même du matériel en tension secteur est risqué et souvent non conforme.
Que deviennent les scènes planifiées si le planificateur est arrêté ?
Les lumières conservent leur état actuel et restent pilotables par les interrupteurs et par des commandes MQTT directes. Exécutez le planificateur comme un service avec redémarrage automatique afin que les événements manqués restent rares.



