draft: Utiliser le WiFi du NodeMCU

This commit is contained in:
Cédrix
2026-05-16 22:46:32 +02:00
parent 4669c999d9
commit 0c41545473
3 changed files with 605 additions and 0 deletions
+5
View File
@@ -546,3 +546,8 @@
{"ts":"2026-05-16 20:37:16","url":"/informatique/hack/la-quadrature-du-net","ref":"","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.4; rv:135.0) Gecko/20100101 Firefox/135.0"} {"ts":"2026-05-16 20:37:16","url":"/informatique/hack/la-quadrature-du-net","ref":"","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.4; rv:135.0) Gecko/20100101 Firefox/135.0"}
{"ts":"2026-05-16 20:41:31","url":"/informatique/linux/commandes/du","ref":"","ua":"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"} {"ts":"2026-05-16 20:41:31","url":"/informatique/linux/commandes/du","ref":"","ua":"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"}
{"ts":"2026-05-16 20:43:28","url":"/loisirs/lego/6665/start","ref":"","ua":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36"} {"ts":"2026-05-16 20:43:28","url":"/loisirs/lego/6665/start","ref":"","ua":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36"}
{"ts":"2026-05-16 20:44:21","url":"/informatique/linux/system/dossiers-remarquables/opt/index","ref":"","ua":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36"}
{"ts":"2026-05-16 20:44:34","url":"/journal_geek/2024/2024-11-05-audacity-3-7-0-est-sortie-mais-elle-crash","ref":"","ua":"Mozilla/5.0 (compatible; Qwantbot/1.0_2600171; +https://help.qwant.com/bot/)"}
{"ts":"2026-05-16 20:45:31","url":"/informatique/linux/cle-wifi-linux","ref":"","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0"}
{"ts":"2026-05-16 20:45:35","url":"/journal_geek/2023/20230109-obs-studio-29","ref":"","ua":"Mozilla/5.0 (compatible; Qwantbot/1.0_2600166; +https://help.qwant.com/bot/)"}
{"ts":"2026-05-16 20:46:27","url":"/electronique/arduino/100-presentation-et-principe-de-l-arduino","ref":"","ua":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36"}
@@ -0,0 +1,4 @@
{
"title": "Utiliser le WiFi du NodeMCU",
"_updated_at": "2026-05-16 20:46:32"
}
@@ -0,0 +1,596 @@
# 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()`
```cpp
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 mode `WIFI_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 mode `WIFI_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()`
```cpp
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 :
1. Se connecter à un point d'accès (avec son nom et son mot de passe),
2. Obtenir une **adresse IP** auprès du serveur DHCP de la box.
L'exemple `WiFiClient_simple.ino` illustre cette procédure.
### Établir la connexion
```cpp
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
```cpp
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
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
```
Décortiquons l'en-tête :
- **`HTTP/1.0 200 OK`** : version du protocole + code de statut. `200` signifie 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
```cpp
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 :
- `host` vaut `"api.tom.tools"`
- `httpPort` vaut `80`
`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
```cpp
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) :
1. **`GET /hits/ HTTP/1.1`** — la requête elle-même :
- `GET` est la méthode HTTP pour *récupérer* une ressource.
- `/hits/` est le chemin vers la ressource.
- `HTTP/1.1` est 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 en `HTTP/1.0`).
2. **`Host: api.tom.tools`** — indispensable pour la **virtualisation**. Sur une même machine physique, plusieurs sites peuvent partager une même adresse IP (par exemple `api.tom.tools` et `www.justinbieber-fan.fr`). Au niveau TCP/IP, impossible de deviner lequel des deux on souhaite contacter. C'est le champ `Host` qui lève l'ambiguïté.
3. **`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
```cpp
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
```cpp
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
```cpp
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 :
```cpp
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":`.
```cpp
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 :
```cpp
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 `-1` si 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, car `atof` attend 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 :
```json
{
"resolution_verticale": 1200,
"resolution_horizontale": 900,
"couleurs": true
}
```
#### Les tableaux
Entre **crochets droits**, une suite de valeurs séparées par des virgules :
```json
[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.
```json
{
"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
```cpp
#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.org`** est 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.
```cpp
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
```cpp
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
```cpp
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 `1883`** est le port MQTT standard (non chiffré). Pour MQTT sécurisé (MQTTS), c'est `8883`.
- **`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 de `atof()`).
### 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](https://mqtt-explorer.com/), 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 **`ArduinoJson`** plutôt qu'avec `indexOf()`.
- 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.
- Explorer **Home Assistant** ou **Node-RED** pour visualiser et orchestrer toute votre installation IoT.