Avec les paramètres ESP32 WiFiManager MQTT, un appareil que vous confiez à quelqu'un d'autre se configure depuis un téléphone : il ouvre un portail captif, l'utilisateur choisit un réseau Wi-Fi et renseigne les paramètres du broker, et l'appareil mémorise le tout d'un redémarrage à l'autre. Ce tutoriel construit ce parcours avec la bibliothèque WiFiManager et Preferences, avec broker.connectmq.com par défaut pour que tout fonctionne dès la sortie de la boîte.
Le problème des paramètres codés en dur
La plupart des exemples MQTT commencent par des lignes comme const char* WIFI_SSID = "..." et const char* MQTT_PASS = "...". Sur votre bureau, c'est parfait, mais chaque nouveau réseau ou changement d'identifiants implique de recompiler et de reflasher. Cela signifie aussi que le binaire du firmware contient des secrets, et que vous ne pouvez pas donner le même binaire à deux clients.
Un portail de provisioning résout ce problème. Au premier démarrage, ou lorsque l'utilisateur maintient un bouton, l'ESP32 lance son propre point d'accès. En s'y connectant avec un téléphone, l'utilisateur ouvre une page web où il saisit :
- Le réseau Wi-Fi et son mot de passe.
- L'hôte et le port du broker MQTT.
- Le client ID, le nom d'utilisateur et le mot de passe MQTT.
- Un préfixe de topic pour cet appareil.
L'appareil enregistre les valeurs dans une mémoire non volatile, rejoint le réseau Wi-Fi et se connecte au broker. Le même binaire convient à tout le monde.
Bibliothèques et matériel
- WiFiManager de tzapu (2.x), installable depuis le gestionnaire de bibliothèques Arduino ou dans PlatformIO sous le nom
tzapu/WiFiManager. - PubSubClient pour MQTT.
- Preferences, fourni avec le core Arduino-ESP32, qui stocke des paires clé–valeur dans la flash NVS.
- N'importe quelle carte ESP32. L'exemple utilise le bouton BOOT (GPIO 0) présent sur la plupart des DevKits pour rouvrir le portail.
Comment fonctionne le parcours ESP32 WiFiManager MQTT
| Situation | Ce qui se passe |
|---|---|
| Premier démarrage, aucun Wi-Fi enregistré | Le portail démarre sous forme de point d'accès Sensor-Setup-xxxx |
| Wi-Fi enregistré joignable | L'appareil se connecte en silence et charge les paramètres MQTT depuis Preferences |
| Wi-Fi enregistré injoignable | Le portail se rouvre après l'échec de la tentative de connexion |
| Bouton BOOT maintenu 3 s au démarrage | Le portail s'ouvre à la demande pour que l'utilisateur modifie les paramètres |
| Portail inutilisé pendant 3 minutes | L'appareil redémarre et réessaie, pour ne jamais rester bloqué |
WiFiManager stocke lui-même les identifiants Wi-Fi. Les champs MQTT sont des paramètres personnalisés que vous ajoutez au portail et que vous persistez vous-même.
Le sketch complet
#include <WiFi.h>
#include <WiFiManager.h>
#include <Preferences.h>
#include <PubSubClient.h>
const int CONFIG_BUTTON = 0; // bouton BOOT sur la plupart des DevKits ESP32
Preferences prefs;
WiFiClient net;
PubSubClient mqtt(net);
char mqttHost[64] = "broker.connectmq.com";
char mqttPort[6] = "1883";
char mqttClient[48] = "";
char mqttUser[48] = "";
char mqttPass[64] = "";
char topicPrefix[64] = "connectmq/demo/d93f2a";
bool shouldSave = false;
unsigned long lastPublish = 0;
void loadSettings() {
prefs.begin("mqtt", true);
prefs.getString("host", mqttHost, sizeof(mqttHost));
prefs.getString("port", mqttPort, sizeof(mqttPort));
prefs.getString("client", mqttClient, sizeof(mqttClient));
prefs.getString("user", mqttUser, sizeof(mqttUser));
prefs.getString("pass", mqttPass, sizeof(mqttPass));
prefs.getString("prefix", topicPrefix, sizeof(topicPrefix));
prefs.end();
if (strlen(mqttClient) == 0) { // ne jamais se connecter avec un client ID vide
snprintf(mqttClient, sizeof(mqttClient), "esp32-%08x", (uint32_t)ESP.getEfuseMac());
}
}
void saveSettings() {
prefs.begin("mqtt", false);
prefs.putString("host", mqttHost);
prefs.putString("port", mqttPort);
prefs.putString("client", mqttClient);
prefs.putString("user", mqttUser);
prefs.putString("pass", mqttPass);
prefs.putString("prefix", topicPrefix);
prefs.end();
}
void runWifiManager(bool forcePortal) {
WiFiManager wm;
WiFiManagerParameter pHost("host", "MQTT host", mqttHost, sizeof(mqttHost) - 1);
WiFiManagerParameter pPort("port", "MQTT port (1883 or 8883)", mqttPort, sizeof(mqttPort) - 1);
WiFiManagerParameter pClient("client", "MQTT client ID", mqttClient, sizeof(mqttClient) - 1);
WiFiManagerParameter pUser("user", "MQTT username (optional)", mqttUser, sizeof(mqttUser) - 1);
WiFiManagerParameter pPass("pass", "MQTT password (optional)", mqttPass, sizeof(mqttPass) - 1,
"type='password'");
WiFiManagerParameter pPrefix("prefix", "Topic prefix", topicPrefix, sizeof(topicPrefix) - 1);
wm.addParameter(&pHost);
wm.addParameter(&pPort);
wm.addParameter(&pClient);
wm.addParameter(&pUser);
wm.addParameter(&pPass);
wm.addParameter(&pPrefix);
wm.setSaveParamsCallback([]() { shouldSave = true; });
wm.setConfigPortalTimeout(180);
char apName[32];
snprintf(apName, sizeof(apName), "Sensor-Setup-%04x", (uint16_t)ESP.getEfuseMac());
bool connected = forcePortal ? wm.startConfigPortal(apName)
: wm.autoConnect(apName);
if (!connected) {
ESP.restart(); // le portail a expiré : on recommence de zéro
}
if (shouldSave) {
strlcpy(mqttHost, pHost.getValue(), sizeof(mqttHost));
strlcpy(mqttPort, pPort.getValue(), sizeof(mqttPort));
strlcpy(mqttClient, pClient.getValue(), sizeof(mqttClient));
strlcpy(mqttUser, pUser.getValue(), sizeof(mqttUser));
strlcpy(mqttPass, pPass.getValue(), sizeof(mqttPass));
strlcpy(topicPrefix, pPrefix.getValue(), sizeof(topicPrefix));
saveSettings();
loadSettings(); // réapplique le client ID de secours
}
}
bool buttonHeld(unsigned long ms) {
pinMode(CONFIG_BUTTON, INPUT_PULLUP);
unsigned long start = millis();
while (digitalRead(CONFIG_BUTTON) == LOW) {
if (millis() - start > ms) return true;
delay(10);
}
return false;
}
void connectMqtt() {
while (!mqtt.connected()) {
bool ok = strlen(mqttUser) > 0
? mqtt.connect(mqttClient, mqttUser, mqttPass)
: mqtt.connect(mqttClient);
if (ok) {
String cmd = String(topicPrefix) + "/cmd";
mqtt.subscribe(cmd.c_str());
} else {
Serial.printf("MQTT failed state=%d, retrying\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
loadSettings();
delay(200);
runWifiManager(buttonHeld(3000));
mqtt.setServer(mqttHost, atoi(mqttPort));
Serial.printf("Broker %s:%s as %s\n", mqttHost, mqttPort, mqttClient);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish > 10000) {
lastPublish = millis();
String topic = String(topicPrefix) + "/telemetry";
String payload = "{\"uptime\":" + String(millis() / 1000) + "}";
mqtt.publish(topic.c_str(), payload.c_str());
}
}
Parcourir le portail
- Flashez le sketch et ouvrez le moniteur série.
- Sur votre téléphone, rejoignez le réseau Wi-Fi nommé
Sensor-Setup-xxxx. La plupart des téléphones ouvrent automatiquement le portail captif ; sinon, rendez-vous sur192.168.4.1. - Touchez Configure WiFi, choisissez votre réseau et saisissez son mot de passe.
- Laissez les valeurs par défaut pour utiliser la zone publique de broker.connectmq.com, ou saisissez vos propres paramètres, puis enregistrez.
- L'appareil rejoint votre réseau, se connecte au broker et commence à publier sur
<prefix>/telemetry.
Vérifiez depuis un ordinateur :
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-d93f2a -t 'connectmq/demo/d93f2a/#' -v
Chaque appareil qui conserve le préfixe par défaut publie sur le même topic : si vous en configurez plusieurs, donnez-leur chacun leur propre préfixe dans le portail.
Les détails qui rendent le tout fiable
Chargez avant de construire le portail. Les paramètres personnalisés sont créés avec les valeurs actuelles comme valeurs par défaut : le portail affiche donc ce qui est enregistré, et non un formulaire vide. Un utilisateur qui modifie un champ ne perd pas les autres.
Ne vous connectez jamais avec un client ID vide. Si l'utilisateur vide le champ, loadSettings() se rabat sur un ID dérivé de l'adresse MAC de la puce. Un client ID vide ou en double est l'une des principales raisons pour lesquelles les appareils n'arrivent pas à se connecter ou s'éjectent mutuellement ; consultez pourquoi un ESP32 ne se connecte pas à un broker MQTT pour la liste complète.
Utilisez le timeout du portail. Sans setConfigPortalTimeout(), un appareil dont le routeur est temporairement hors service reste indéfiniment en mode portail. Avec un timeout, il redémarre et retente le réseau enregistré.
Stockez les nombres sous forme de texte dans le portail, convertissez une seule fois. Les paramètres WiFiManager sont des champs texte. Conservez le port sous forme de chaîne dans le stockage et convertissez-le avec atoi() lors de la configuration du client.
Masquez le champ du mot de passe. L'argument supplémentaire type='password' masque le mot de passe MQTT dans le portail. La valeur transite néanmoins en HTTP en clair sur le point d'accès ouvert : ne faites donc le provisioning que dans un lieu de confiance.
Valider ce que saisissent les utilisateurs
Un portail captif accepte tout ce que saisit l'utilisateur : le firmware doit donc savoir gérer les erreurs. Quelques vérifications après l'enregistrement évitent qu'un appareil ne se rende inutilisable avec de mauvais paramètres :
- Port : n'acceptez que des valeurs entre 1 et 65535. Si
atoi()renvoie 0 parce que le champ contenait du texte, revenez à 1883. - Hôte : supprimez les espaces et rejetez une chaîne vide. Les utilisateurs collent souvent une URL comme
mqtt://broker.connectmq.com; retirez le schéma et tout ce qui suit le nom d'hôte. - Préfixe de topic : supprimez une barre oblique finale et rejetez les wildcards (
+et#), interdits dans les topics de publication. - Identifiants : si un nom d'utilisateur est défini mais que le mot de passe est vide, affichez un avertissement sur la console série, car le broker refusera la connexion.
Lorsque la connexion MQTT échoue à répétition avec un code mqtt.state() positif (identifiants incorrects ou non autorisé), il s'agit généralement d'un problème de paramètres plutôt que de réseau. Un bon schéma consiste à compter ces échecs et à rouvrir automatiquement le portail après plusieurs échecs consécutifs, pour que l'utilisateur puisse corriger les valeurs sans bouton ni câble série.
Ajouter le TLS pour les brokers de production
Le port 1883 est en clair et convient à la zone publique de test. Pour un broker privé, basculez sur WiFiClientSecure lorsque l'utilisateur choisit le port 8883 :
#include <WiFiClientSecure.h>
WiFiClientSecure secureNet;
void configureTransport() {
if (atoi(mqttPort) == 8883) {
secureNet.setCACert(ISRG_ROOT_X1); // racine Let's Encrypt, stockée sous forme de chaîne PEM
mqtt.setClient(secureNet);
} else {
mqtt.setClient(net);
}
mqtt.setServer(mqttHost, atoi(mqttPort));
}
Appelez configureTransport() à la place de la ligne setServer() simple dans setup(). Avec ConnectMQ, les utilisateurs saisissent alors dans le portail le client ID, le nom d'utilisateur et le mot de passe générés pour leur broker (YOUR_CLIENT_ID, YOUR_USERNAME, YOUR_PASSWORD). Le client ID est lié à ces identifiants : il doit donc être saisi exactement tel qu'il a été émis. Le guide ESP32 MQTT SSL/TLS explique comment intégrer le certificat racine.
Dépannage
| Problème | Cause probable | Solution |
|---|---|---|
| Le portail n'apparaît jamais | Le Wi-Fi enregistré se connecte toujours | Maintenez le bouton BOOT pendant le reset, ou appelez wm.resetSettings() une fois |
| Les paramètres ne sont pas enregistrés | Le callback d'enregistrement n'est pas déclenché | Utilisez setSaveParamsCallback() et appuyez sur Save dans la page du portail |
| Valeurs tronquées | Longueur du champ inférieure à la saisie | Augmentez la taille du tampon et la longueur du paramètre |
| Se connecte au Wi-Fi mais pas à MQTT | Mauvais port pour le transport, ou mauvais identifiants | Vérifiez mqtt.state() et la combinaison port/TLS |
| Le téléphone n'ouvre pas le portail | Détection du portail captif bloquée | Rendez-vous manuellement sur 192.168.4.1 |
Pour aller plus loin
Une fois les paramètres configurables, les étapes suivantes naturelles sont les mises à jour à distance et le suivi du statut : les mises à jour OTA ESP32 déclenchées via MQTT montrent comment livrer un nouveau firmware sans USB, et le tutoriel IoT ESP32 intègre le provisioning dans un projet d'appareil complet.
Provisionnez vos appareils pour ConnectMQ
Les paramètres par défaut pointent vers le serveur MQTT public gratuit, prévu pour les tests : un appareil neuf fonctionne donc dès qu'il rejoint le Wi-Fi. Pour les déploiements, créez un broker privé sur ConnectMQ et saisissez ses identifiants dans le portail. ConnectMQ est un broker MQTT managé : les personnes qui configurent les appareils n'ont jamais à faire tourner de serveur. Commencez avec l'offre gratuite Serverless sur app.connectmq.com, sans carte bancaire.
Questions fréquentes
Où WiFiManager stocke-t-il le mot de passe Wi-Fi ?
Le pilote Wi-Fi de l'ESP32 stocke les identifiants réseau dans la flash NVS. Dans ce sketch, les champs MQTT sont stockés séparément avec Preferences, sous l'espace de noms mqtt.
Comment rouvrir le portail WiFiManager plus tard ?
Dans ce sketch, maintenez le bouton BOOT pendant trois secondes au démarrage, ce qui appelle startConfigPortal(). Vous pourriez aussi le déclencher par une commande MQTT, tant que l'appareil reste joignable.
Peut-on utiliser WiFiManager avec AsyncMqttClient ?
Oui. WiFiManager ne gère que le provisioning. Une fois qu'il rend la main, utilisez les valeurs enregistrées pour configurer le client MQTT de votre choix.
Le portail captif est-il sécurisé ?
Le portail fonctionne comme un point d'accès ouvert en HTTP en clair : toute personne à proximité pourrait observer les valeurs pendant la configuration. Faites le provisioning dans un lieu de confiance, et envisagez de protéger le point d'accès de configuration par un mot de passe.



