24 KiB
Utiliser le WiFi du NodeMCU
Le module ESP8266 (embarqué sur les cartes NodeMCU) intègre nativement le WiFi. Dans ce tutoriel, nous allons apprendre pas à pas à exploiter cette capacité : scanner les réseaux disponibles, se connecter à un point d'accès, dialoguer avec un serveur web, puis interpréter les données reçues au format JSON.
L'objectif final ? Transformer votre NodeMCU en objet connecté capable, par exemple, d'afficher la température extérieure récupérée en temps réel sur Internet.
1. Scanner les réseaux WiFi disponibles
Avant de se connecter à un réseau, encore faut-il savoir quels réseaux sont à portée. C'est exactement ce que fait l'exemple WiFiScan, fourni avec la bibliothèque ESP8266.
Pour le charger : Fichier → Exemples → ESP8266WiFi → WiFiScan
Préparer le module dans setup()
WiFi.mode(WIFI_STA); // mode "Station" (client d'une box)
WiFi.disconnect(); // au cas où une connexion serait déjà active
Quelques précisions importantes :
WiFi.mode()est une méthode spécifique au module ESP, vous ne la trouverez pas dans la documentation officielle Arduino. Le modeWIFI_STA(Station) signifie que le module se comportera comme un client classique, à l'image d'un smartphone qui se connecte à une box. Il existe aussi un modeWIFI_AP(Access Point) où le module devient lui-même un point d'accès.WiFi.disconnect()garantit qu'aucune connexion antérieure ne va perturber notre scan.
Lister les réseaux dans loop()
int n = WiFi.scanNetworks();
for (int i = 0; i < n; ++i) {
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i)); // nom du réseau
Serial.print(" (");
Serial.print(WiFi.RSSI(i)); // puissance du signal
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
delay(10);
}
Comprendre ce qui se passe
| Méthode | Rôle |
|---|---|
WiFi.scanNetworks() |
Balaie toutes les fréquences WiFi et retourne le nombre de réseaux détectés |
WiFi.SSID(i) |
Renvoie le nom du i-ème réseau (SSID = Service Set IDentifier) |
WiFi.RSSI(i) |
Renvoie la puissance du signal reçu (RSSI = Received Signal Strength Indication), exprimée en dBm — plus la valeur est proche de 0, meilleur est le signal |
WiFi.encryptionType(i) |
Indique si le réseau est protégé par un mot de passe |
Comment ça marche en coulisses ? Les box (et plus généralement tous les points d'accès WiFi) émettent environ 10 fois par seconde un petit message appelé beacon qui annonce le nom du réseau. La méthode
scanNetworks()ne fait qu'écouter passivement toutes les fréquences pour capter ces annonces.
2. Se connecter à un réseau et obtenir une adresse IP
Détecter un réseau, c'est bien. Pouvoir l'utiliser, c'est mieux. Pour communiquer avec d'autres équipements, notre module doit :
- Se connecter à un point d'accès (avec son nom et son mot de passe),
- Obtenir une adresse IP auprès du serveur DHCP de la box.
L'exemple WiFiClient_simple.ino illustre cette procédure.
Établir la connexion
WiFi.begin(ssid, password); // on lance la connexion
while (WiFi.status() != WL_CONNECTED) { // on attend qu'elle aboutisse
delay(500);
Serial.print(".");
}
La boucle while est essentielle : la connexion n'est pas instantanée. Le module doit s'authentifier auprès de la box, puis attendre que le serveur DHCP lui attribue une adresse IP ainsi que les paramètres réseau (masque, passerelle, DNS).
Afficher les paramètres obtenus
Serial.println("");
Serial.println("WiFi connecté");
Serial.print("Adresse IP du module ESP : ");
Serial.println(WiFi.localIP());
Serial.print("Adresse IP de la box : ");
Serial.println(WiFi.gatewayIP());
La gateway (passerelle) correspond à la machine vers laquelle le module va envoyer ses données pour qu'elles sortent vers Internet — autrement dit, l'adresse IP de votre box.
3. Comprendre le Web avant de l'utiliser
Avant d'écrire du code qui interroge un serveur, il faut comprendre comment fonctionne le Web. Trois concepts sont indispensables : le modèle client/serveur, les méta-données, et les URI.
3.1. Le modèle client/serveur
Le Web est souvent confondu avec Internet, mais ce n'est qu'un service parmi d'autres qui circulent sur le réseau. Sa force ? Sa scalabilité : la capacité à répondre à un nombre énorme de requêtes sans s'effondrer.
Le principe est simple :
- Le client demande une information.
- Le serveur la lui retourne, puis oublie tout.
C'est cette amnésie volontaire du serveur qui permet la montée en charge. S'il devait se souvenir de chaque client et de chaque requête, il serait rapidement débordé. À la place, c'est le client qui conserve l'état de la session.
Conséquence pratique : une requête peut être traitée par n'importe quel serveur d'une ferme de machines, ce qui permet de répartir la charge. Quand une page web est demandée, elle contient souvent des références (images, scripts, styles) que le client ira chercher ensuite — éventuellement sur d'autres serveurs.
3.2. Les ressources et les méta-données
Le serveur manipule des ressources : une information binaire de taille finie. Cela peut être du texte, une image, une vidéo, des données structurées… La définition est volontairement très large.
Aux données brutes, on ajoute des méta-données : ce sont des informations sur les données (leur date, leur taille, leur type…). Elles permettent au client de savoir comment traiter ce qu'il reçoit.
Voici un exemple de réponse complète d'un serveur :
HTTP/1.0 200 OK
Date: Thu, 28 Apr 2016 17:51:07 GMT
Server: WSGIServer/0.1 Python/2.7.9
X-Frame-Options: SAMEORIGIN
Content-Type: text/plain
147
Décortiquons l'en-tête :
HTTP/1.0 200 OK: version du protocole + code de statut.200signifie succès.Date:: moment où la réponse a été produite.Content-Type: text/plain: la donnée renvoyée est du texte non formaté.
Puis une ligne vide sépare l'en-tête du corps de la réponse, qui contient ici la valeur 147.
Les codes de statut HTTP
Le premier chiffre du code indique la nature de la réponse :
| Code | Signification |
|---|---|
| 1xx | Traitement en cours |
| 2xx | Succès |
| 3xx | Redirection (il faut interroger un autre serveur) |
| 4xx | Erreur côté client (mauvaise requête) |
| 5xx | Erreur côté serveur |
La fameuse erreur 404 ("page non trouvée") est donc bien une erreur du client, qui a demandé une ressource qui n'existe pas.
3.3. Les URI : une adresse universelle pour les ressources
Pour identifier les ressources, le Web utilise les URI (Uniform Resource Identifier). On parlait historiquement d'URL, mais URI est devenu le terme générique.
La structure d'une URI typique :
schéma://serveur:port/chemin/vers/la/ressource
| Partie | Rôle |
|---|---|
| schéma | Indique comment lire le reste de l'URI (http, https, ftp…) |
| serveur | Nom de domaine ou adresse IP |
| port | Optionnel — par défaut 80 pour HTTP, 443 pour HTTPS |
| chemin | Localisation interne au serveur |
⚠️ Contrairement à une idée reçue, le schéma ne désigne pas directement un protocole réseau, mais la manière dont l'URI est structurée.
Exemple :
https://www.fun-mooc.fr/courses/MinesTelecom/04011S02/session02/about
- Schéma :
https - Serveur :
www.fun-mooc.fr - Chemin :
/courses/MinesTelecom/04011S02/session02/about
4. Interroger un serveur web depuis le NodeMCU
Maintenant que la théorie est posée, passons à la pratique avec le programme WiFiClient-compteur.ino. Il ouvre une connexion vers le serveur api.tom.tools sur le port 80 et récupère la valeur d'un compteur.
4.1. Ouvrir une connexion TCP
const int httpPort = 80; // port standard du Web
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
return;
}
Les variables host et httpPort sont déclarées en début de programme :
hostvaut"api.tom.tools"httpPortvaut80
client.connect() établit la connexion TCP. Si elle échoue, on affiche un message et on quitte la boucle.
4.2. Construire et envoyer la requête HTTP
String url = String("/hits/");
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
On vient de construire l'URI http://api.tom.tools/hits/ et d'envoyer une requête HTTP composée de 3 lignes (\r\n signale une fin de ligne) :
-
GET /hits/ HTTP/1.1— la requête elle-même :GETest la méthode HTTP pour récupérer une ressource./hits/est le chemin vers la ressource.HTTP/1.1est la version du protocole. Le serveur peut répondre dans une autre version si la nôtre ne lui convient pas (on a vu plus haut une réponse enHTTP/1.0).
-
Host: api.tom.tools— indispensable pour la virtualisation. Sur une même machine physique, plusieurs sites peuvent partager une même adresse IP (par exempleapi.tom.toolsetwww.justinbieber-fan.fr). Au niveau TCP/IP, impossible de deviner lequel des deux on souhaite contacter. C'est le champHostqui lève l'ambiguïté. -
Connection: close— on signale au serveur qu'il peut fermer la connexion après avoir répondu.
La ligne vide finale (\r\n\r\n) marque la fin de l'en-tête de la requête.
4.3. Recevoir et afficher la réponse
delay(1000); // on laisse au serveur le temps de répondre
while (client.available()) {
String line = client.readStringUntil('\r');
Serial.print(line);
}
Serial.println();
Serial.println("connexion fermée");
Tant que des données arrivent, on les lit ligne par ligne et on les affiche. Puis on attend (typiquement 30 secondes) avant de recommencer.
5. Cas pratique : récupérer la température extérieure
Le programme WiFiTemperature.ino reprend la structure précédente, mais interroge cette fois l'API publique OpenWeatherMap pour obtenir la météo d'une ville.
5.1. Configurer la requête
const char* host = "api.openweathermap.org";
const char* apikey = "1a702a15a2f46e405e61804cf67c0d30"; // clé d'API
const char* town = "Rennes,fr";
Trois variables sont nécessaires :
host: le serveur à contacter.apikey: une clé d'identification fournie par OpenWeatherMap pour identifier l'utilisateur qui fait la requête.town: la ville dont on veut la météo.
5.2. Construire l'URL avec des paramètres
String url = String("/data/2.5/weather?q=") + town + "&appid=" + apikey;
Ce chemin est plus complexe que /hits/. Après le ?, on trouve des paramètres au format clé=valeur, séparés par des & :
q=Rennes,fr: la ville recherchée.appid=...: la clé d'API.
Ces paramètres permettent à la ressource /data/2.5/weather d'adapter sa réponse à notre demande.
5.3. Sauter l'en-tête pour atteindre le corps
La réponse du serveur contient un en-tête (les méta-données) suivi d'une ligne vide, puis des données utiles. Pour atteindre ces données, on utilise un drapeau booléen :
bool inBody = false;
while (client.available()) {
String line = client.readStringUntil('\r');
if (line.length() == 1) inBody = true; // ligne vide → début du corps
if (inBody) {
// ici on traite les données utiles
}
}
Tant que inBody reste false, on est dans l'en-tête. Dès qu'on rencontre une ligne vide (de longueur 1 si on compte le saut de ligne), on bascule dans le corps.
5.4. Extraire la température du JSON
OpenWeatherMap renvoie ses données au format JSON (voir section suivante). La température se trouve après le mot-clé "temp":.
String keyword = String("\"temp\":"); // les \ permettent d'inclure " dans la chaîne
💡 Les antislashs servent à échapper les guillemets, sinon le compilateur penserait que la chaîne se termine prématurément.
Puis on cherche cette chaîne dans chaque ligne :
if (inBody) {
int pos = line.indexOf(keyword);
if (pos > 0) { // mot-clé trouvé
pos += keyword.length(); // on se place juste après "temp":
temperature = atof(&line[pos]); // conversion en nombre flottant
}
}
Petites explications :
indexOf()renvoie la position de la chaîne recherchée, ou-1si elle n'est pas trouvée.- En ajoutant la longueur du mot-clé, on se positionne sur le premier caractère du nombre.
atof()(ASCII to float) convertit une chaîne de caractères en nombre à virgule.- L'opérateur
&donne l'adresse mémoire du caractère, caratofattend un pointeur.
Et voilà ! La variable temperature contient maintenant la température en kelvins (OpenWeatherMap renvoie ses températures en kelvins par défaut — il suffira de soustraire 273,15 pour les convertir en degrés Celsius).
6. Le format JSON
Le programme précédent recherche une valeur dans un texte structuré au format JSON (JavaScript Object Notation). Comprendre ce format est indispensable pour l'Internet des objets.
6.1. Pourquoi structurer les données ?
Imaginez une phrase sans espaces :
sionnestructurepasunephraseavecdesespacesilesttrèsdurdelalireetdyretrouverdesmots
Pour les données, c'est pareil. Si l'on transmet juste 25, ça peut être une température, un âge, un pourcentage… On a besoin d'une structure qui dise à quoi correspond chaque valeur.
JSON a été conçu pour le Web, mais sa simplicité et sa compacité l'ont rendu très populaire pour les objets connectés.
6.2. Les types de données JSON
JSON définit deux types simples :
| Type | Forme | Exemple |
|---|---|---|
| Chaîne de caractères | entre guillemets " |
"Bonjour", "123" |
| Nombre | sans guillemets | 42, -3.14, -.1e+3 (= -100) |
Et trois types composés :
Les objets (listes de paires clé/valeur)
Entre accolades, une suite de paires "clé": valeur séparées par des virgules :
{
"resolution_verticale": 1200,
"resolution_horizontale": 900,
"couleurs": true
}
Les tableaux
Entre crochets droits, une suite de valeurs séparées par des virgules :
[1200, 900, true]
C'est plus compact, mais la position devient déterminante : il faut savoir que le premier nombre est la résolution verticale, le second l'horizontale, etc.
L'imbrication
La force de JSON est qu'on peut combiner ces structures à l'infini : objets dans des tableaux, tableaux dans des objets, etc.
{
"Image": {
"Hauteur": 800,
"Largeur": 600,
"Titre": "Vue du 5ème étage",
"Vignette": {
"Url": "http://www.example.com/image/481989943",
"Hauteur": 125,
"Largeur": 100
},
"Animee": false,
"IDs": [116, 943, 234, 38793]
}
}
Ici, l'objet Image contient un sous-objet Vignette et un tableau IDs. Cette flexibilité explique pourquoi JSON s'est imposé comme standard d'échange de données.
💡 Anecdote : JSON se prononce jisone en français ou jay-zon à l'anglaise. Rien à voir avec le tueur des films d'horreur — celui-là s'écrit Jason !
7. Dialoguer en MQTT plutôt qu'en HTTP
HTTP fonctionne très bien pour interroger ponctuellement un serveur, mais il devient vite inadapté dès qu'on veut faire de la vraie communication d'objets connectés : capteurs qui publient leurs mesures en continu, actionneurs qui doivent réagir en temps réel, plusieurs objets qui dialoguent entre eux…
C'est là qu'intervient MQTT (Message Queuing Telemetry Transport), un protocole conçu spécifiquement pour l'IoT.
7.1. HTTP vs MQTT : pourquoi changer ?
| Critère | HTTP | MQTT |
|---|---|---|
| Modèle | Client/serveur (requête/réponse) | Publish/subscribe (publication/abonnement) |
| Initiative | Le client demande, le serveur répond | Tout le monde peut publier et écouter |
| Connexion | Ouverte puis fermée à chaque requête | Permanente |
| Taille des messages | En-têtes verbeux (souvent plusieurs centaines d'octets) | Très léger (2 octets d'en-tête minimum) |
| Temps réel | Difficile : il faut interroger en boucle (polling) | Natif : les messages arrivent dès qu'ils sont publiés |
| Consommation | Élevée (CPU, énergie, bande passante) | Faible — idéal pour les objets sur batterie |
En résumé : si votre NodeMCU doit envoyer une température toutes les 10 secondes pendant des mois sur une batterie, HTTP va vider la pile en quelques jours, là où MQTT tiendra des mois.
7.2. Le modèle publish/subscribe
Oubliez le client/serveur. En MQTT, on parle de :
- Broker : un serveur central qui reçoit tous les messages et les redistribue. C'est le seul élément "lourd" du système.
- Publishers : les objets (ou applications) qui envoient des messages.
- Subscribers : les objets (ou applications) qui reçoivent des messages.
Un même objet peut être à la fois publisher et subscriber.
Les messages ne sont pas envoyés directement d'un objet à un autre : ils sont publiés sur des topics (sujets), organisés comme une arborescence :
maison/salon/temperature
maison/salon/humidite
maison/cuisine/temperature
maison/jardin/luminosite
Le séparateur est le /. Un capteur publie sur un topic, et tous les objets abonnés à ce topic reçoivent automatiquement le message.
💡 Avantage clé : le publisher ne sait pas qui va lire son message, et le subscriber ne sait pas qui l'a envoyé. Cette découplage rend le système très souple — on peut ajouter ou retirer des objets sans rien reconfigurer.
7.3. Les jokers (wildcards)
Pour s'abonner à plusieurs topics à la fois, MQTT propose deux caractères spéciaux :
| Symbole | Rôle | Exemple |
|---|---|---|
+ |
Joker pour un seul niveau | maison/+/temperature capte le salon, la cuisine, la chambre… mais pas maison/exterieur/jardin/temperature |
# |
Joker pour tous les niveaux suivants | maison/# capte absolument tout ce qui commence par maison/ |
7.4. La qualité de service (QoS)
MQTT propose trois niveaux de garantie de livraison :
- QoS 0 — Au plus une fois : le message part, et tant pis s'il se perd. Le plus rapide, mais sans garantie.
- QoS 1 — Au moins une fois : le message arrivera, mais peut être livré plusieurs fois en cas de problème réseau.
- QoS 2 — Exactement une fois : garantie totale, mais beaucoup plus coûteux en échanges réseau.
Pour de la télémétrie (une température parmi des centaines), QoS 0 suffit largement. Pour un ordre critique (déverrouiller une porte), on passera en QoS 1 ou 2.
7.5. Mise en pratique sur le NodeMCU
La bibliothèque la plus utilisée s'appelle PubSubClient. On l'installe via le gestionnaire de bibliothèques d'Arduino IDE (Croquis → Inclure une bibliothèque → Gérer les bibliothèques → rechercher PubSubClient).
Étape 1 — Inclusions et configuration
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* ssid = "votre_wifi";
const char* password = "votre_mot_de_passe";
const char* mqtt_server = "test.mosquitto.org"; // broker public de test
WiFiClient espClient;
PubSubClient client(espClient);
💡
test.mosquitto.orgest un broker public gratuit parfait pour apprendre. Pour de vrais projets, vous installerez votre propre broker (par exemple Mosquitto sur un Raspberry Pi) ou utiliserez un service en ligne (HiveMQ, AWS IoT Core, Adafruit IO…).
Étape 2 — Définir la fonction de réception
C'est ici que toute la magie opère : dès qu'un message arrive sur un topic auquel on est abonné, cette fonction est appelée automatiquement.
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message reçu sur [");
Serial.print(topic);
Serial.print("] : ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
payload est un tableau d'octets (pas une chaîne C terminée par \0), d'où la nécessité du paramètre length pour savoir où s'arrêter.
Étape 3 — Se connecter au broker
void reconnect() {
while (!client.connected()) {
Serial.print("Connexion au broker MQTT... ");
// Chaque client doit avoir un identifiant unique
String clientId = "NodeMCU-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("connecté !");
client.subscribe("maison/salon/commande"); // on s'abonne
} else {
Serial.print("échec, code=");
Serial.print(client.state());
Serial.println(" — nouvelle tentative dans 5s");
delay(5000);
}
}
}
Étape 4 — La boucle principale
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
client.setServer(mqtt_server, 1883); // port MQTT standard
client.setCallback(callback);
}
void loop() {
if (!client.connected()) reconnect();
client.loop(); // INDISPENSABLE : traite les messages entrants
// Toutes les 10 secondes, on publie une température fictive
static unsigned long lastMsg = 0;
if (millis() - lastMsg > 10000) {
lastMsg = millis();
float temperature = 20.0 + random(0, 100) / 10.0;
char payload[10];
dtostrf(temperature, 4, 1, payload); // float → chaîne
client.publish("maison/salon/temperature", payload);
Serial.print("Publié : ");
Serial.println(payload);
}
}
Quelques points clés :
- Le port
1883est le port MQTT standard (non chiffré). Pour MQTT sécurisé (MQTTS), c'est8883. client.loop()doit être appelé en permanence : sans lui, aucun message entrant ne sera traité.dtostrf()convertit un float en chaîne de caractères (équivalent inverse deatof()).
7.6. Tester sans matériel supplémentaire
Pour vérifier que votre NodeMCU publie bien, vous pouvez utiliser un client MQTT de bureau comme MQTT Explorer, gratuit et multiplateforme. Connectez-le au même broker (test.mosquitto.org), abonnez-vous à maison/# et vous verrez vos messages apparaître en direct.
Vous pouvez aussi publier depuis MQTT Explorer un message sur maison/salon/commande pour voir votre NodeMCU le recevoir dans le moniteur série.
⚠️ Attention sur les brokers publics : tous les messages sont visibles par tout le monde. Ne publiez rien de sensible, et préférez un préfixe original (
projet-de-pierre-42/...) pour ne pas polluer les topics communs.
Pour aller plus loin
Vous disposez maintenant de toutes les briques pour construire un objet connecté :
- ✅ Scanner les réseaux WiFi disponibles
- ✅ Se connecter à un point d'accès
- ✅ Émettre une requête HTTP vers un serveur
- ✅ Lire et interpréter une réponse JSON
- ✅ Publier et recevoir des messages via MQTT
Idées de projets pour mettre en pratique :
- Un thermomètre connecté qui publie sa mesure en MQTT, affichée sur un dashboard Node-RED ou Home Assistant.
- Une station de qualité d'air avec plusieurs NodeMCU qui publient leurs mesures sur un broker commun.
- Une télécommande sans fil : un bouton sur un NodeMCU publie une commande que d'autres NodeMCU exécutent (allumer une LED, faire bouger un servo…).
Pistes d'approfondissement :
- Parser proprement le JSON reçu avec la bibliothèque
ArduinoJsonplutôt qu'avecindexOf(). - Installer son propre broker Mosquitto sur un Raspberry Pi pour ne dépendre d'aucun service en ligne.
- Mettre en place MQTT sécurisé (MQTTS) avec certificats TLS pour les déploiements en production.
- Explorer Home Assistant ou Node-RED pour visualiser et orchestrer toute votre installation IoT.