From 4669c999d9531af65a44cb59d070cfb7b14669f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drix?= Date: Sat, 16 May 2026 22:43:40 +0200 Subject: [PATCH] publish: Utiliser le WiFi du NodeMCU --- .../draft_overlay.json | 15 - .../draft_overlay.md | 419 --------------- ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/index.md | 501 ++++++++++++------ .../meta.json | 23 +- .../revisions/0001.md | 240 +++++++++ 5 files changed, 598 insertions(+), 600 deletions(-) delete mode 100644 ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.json delete mode 100644 ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.md create mode 100644 ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/revisions/0001.md diff --git a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.json b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.json deleted file mode 100644 index b26ca3b..0000000 --- a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "title": "Utiliser le WiFi du NodeMCU", - "_updated_at": "2026-05-16 20:43:38", - "slug": "utiliser-le-wifi-du-nodemcu", - "published": true, - "published_at": "2020-04-17 18:22", - "category": "Électronique", - "tags": { - "tags": [ - "NodeMCU" - ] - }, - "seo_title": "", - "seo_description": "" -} diff --git a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.md b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.md deleted file mode 100644 index e2439cc..0000000 --- a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/draft_overlay.md +++ /dev/null @@ -1,419 +0,0 @@ -# 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 - -147 -``` - -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 ! - ---- - -## 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 - -**Idées de projets** pour mettre en pratique : - -- Un thermomètre connecté qui affiche la météo extérieure sur un servo-moteur. -- Une station de qualité d'air qui publie ses mesures sur un serveur distant. -- Un bouton physique qui déclenche une action sur un service web (lumière, notification…). - -Pour aller plus loin, vous pouvez explorer les **méthodes HTTP** autres que `GET` (`POST`, `PUT`, `DELETE`), apprendre à parser le JSON proprement avec une bibliothèque comme `ArduinoJson`, ou découvrir des protocoles dédiés à l'IoT comme **MQTT**. \ No newline at end of file diff --git a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/index.md b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/index.md index bbe9a46..e2439cc 100644 --- a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/index.md +++ b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/index.md @@ -1,67 +1,122 @@ -# Utiliser le Wifi du NodeMCU +# Utiliser le WiFi du NodeMCU -On va commencer par le début. Récupérer le programme exemple en allant *Fichier*=>*Exemples*=>*ESP8266WiFi*=>*WiFiScan* +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. -``` -Dans la fonction de configuration setup, on met le module WiFi en mode Station (on aurait pu le mettre aussi en point d'accès). Ne cherchez pas la méthode mode dans la doc Arduino, il s'agit d'une commande spécifique au module ESP (voir ici). La méthode disconnect permet ensuite de déconnecter le module ESP d'un point d'accès, au cas où ! +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. -Dans la fonction boucle (loop) on retrouve les instructions qui permettent d'afficher périodiquement les WiFi captés par le module ESP. La méthode scanNetwork permet , comme son nom l'indique, de scanner les différents canaux (fréquences) dédiés au WiFi et retourner le nombre de réseaux trouvés : +--- - int n = WiFi.scanNetworks(); +## 1. Scanner les réseaux WiFi disponibles -Ainsi, si ce nombre n'est pas nul, ils vont être affichés un par un grâce à une boucle for. +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. - for (int i = 0; i < n; ++i) { - 1. Print SSID and RSSI for each network found - Serial.print(i + 1); - Serial.print(": "); - Serial.print(WiFi.SSID(i)); - Serial.print(" ("); - Serial.print(WiFi.RSSI(i)); - Serial.print(")"); - Serial.println[^note: WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*"); - delay(10); - } +**Pour le charger** : *Fichier* → *Exemples* → *ESP8266WiFi* → *WiFiScan* -La méthode SSID permet d'afficher le nom du réseau WiFi. Dans le vocabulaire Wi-Fi, SSID veut dire Service Set IDentifier, mais bon c'est pas plus limpide ! La méthode RSSI (Received Signal Strength Indication) affiche la puissance du signal reçu. Les box et plus généralement les points d'accès WiFi émettent 10 fois par seconde un message donnant le nom du réseaux Wi-Fi. La méthode scanNetwork écoute toutes les fréquences et récupère tous ces noms. +### 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 ``` -Une fois connecté au réseau WiFi, nous ne pouvons pas encore communiquer avec d'autres équipements car nous n'avons pas d'adresse IP. +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); +} ``` -Pour obtenir une adresse, nous nous appuyons sur le programme WiFiClient_simple.ino comme son nom l'indique est simple et utilise le WiFi. On retrouve la connexion au réseau WiFi avec la méthode begin : - WiFi.begin(ssid, password); * On se connecte - - while (WiFi.status() != WL_CONNECTED) { * On attend +### 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 permet d'attendre que la connexion soit effective et que le serveur DHCP de la box ait fourni les paramètres nécessaires pour se connecter au réseau Internet, qui sont affichés avec le code suivant : - - Serial.println(""); * on affiche les paramètres - Serial.println("WiFi connecté"); - Serial.print("Adresse IP du module EPC: "); - Serial.println(WiFi.localIP(]; - Serial.print("Adresse IP de la box : "); - Serial.println(WiFi.gatewayIP()); - -La gateway correspond à machine vers laquelle notre module ESP va envoyer les informations, c'est-à-dire à l'adresse IP de la box. +} ``` +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()); ``` -Le modèle client/serveur -Le web est la huitième merveille du monde, au point qu'on le confond souvent avec l'Internet. Il ne s'agit pourtant que d'un des services possible sur le réseau. Ce qui est remarquable avec le web c'est sa scalabilité. C'est à dire la possibilité de répondre à un nombre très important de requêtes sans perdre en performances. Cela est dû à un ensemble de règles pour la conception des services. +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. -Le Web fonctionne sur le mode client/serveur. Le client veut une information et le serveur obéit en la retournant. Si le serveur devait se rappeler de tout ce qu'il a fait, il serait vite débordé. Alors la première règle fondamentale est que seul le client va garder une mémoire (un état) de ce qui se passe. Le serveur va envoyer ce qu'on lui a demandé et va passer à la demande suivante et ainsi de suite. Comme il y a beaucoup plus de clients que de serveurs, c'est plus facile à gérer. Par exemple, quand une page web est demandée à un serveur, elle va contenir d'autres références. Le client reçoit cette information, va la traiter, l'afficher et demander aux serveurs les éléments qui lui manquent. Ces informations peuvent être sur le même serveur ou sur un autre, ce n'est pas important car les serveurs ne se rappellent de rien après avoir traité la requête. On peut ainsi concevoir un service qui repose sur plusieurs serveurs et ainsi répartir le trafic et le traitement. -Les méta-données +--- -Les éléments traités par un serveur sont appelés ressources. La définition de ressource est très vague. Il s'agit d'une information binaire de taille finie. Cela peut correspondre à du texte, à une image, une vidéo, des données,... En plus des données, on peut mettre des méta-données. C'est à dire des choses qui sont au-dessus des données, comme la méta-physique est au-dessus de la physique ou le métabolisme est au-dessus du bolisme ;). +## 3. Comprendre le Web avant de l'utiliser -Les méta-données vont permettre de mieux comprendre les données. Cela peut correspondre à leur date de production, à leur taille ou leur type. Cette dernière information est importante car elle permettra au client de bien les traiter. Dans la réponse d'un serveur, les méta-données (ou l'en-tête) sont séparées des données par une ligne vide, comme l'a montré la réponse qui a été affichée par l'Arduino pour le compteur. +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 @@ -69,172 +124,296 @@ X-Frame-Options: SAMEORIGIN Content-Type: text/plain 147 - -Dans cet exemple, la première ligne donne le statut de la requête, HTTP/1.0 donne la version du protocole. Le second champ, ici 200 va donner le code indiquant le succès ou l'échec du traitement de la requête par le serveur. On a de la chance, 200 signifie que c'est bon. Le premier chiffre donne la nature de la notification : - - 1XX : indication d'un traitement en cours - 2XX : succès - 3XX : redirection, il faut interroger un autre serveur pour avoir la réponse - 4XX : erreur du coté du client - 5XX : erreur du coté du serveur - -Ainsi la célèbre erreur 404 indiquant que la page n'est pas trouvée est bien une erreur du client qui pose des questions stupides au serveur. La ligne suivante donne la date, à laquelle la page a été produite par le serveur. La ligne suivante, un peu plus énigmatique, elle indique que notre super page web ne peut pas être incluse dans la page web d'un autre serveur. - -La dernière ligne de l'en-tête donne la nature de l'information, ici c'est du texte non formaté. - -Ensuite on a une ligne vide suivie de ce qui sera affiché, à savoir la valeur du compteur. -Les URI - -Un autre facteur clé pour le succès du Web, en plus de l'utilisation de serveurs simples, est l'utilisation d'une désignation uniforme des ressources. Initialement appelées URL (Uniform Resource Locator), on emploie maintenant plus le terme URI (Uniform Resource Identifier) plus générique et qui peut couvrir d'autres technologies (comme la téléphonie sur Internet). Mais de manière simple, une URI se structure de la manière suivante quand on l'utilise pour le Web ou l'Internet des objets : - -schéma:*serveur:port/chemin/jusqu/a/la/ressource - -Contrairement à l'idée admise, la première information, schéma, ne correspond pas au protocole employé sur le réseau, mais indique comment le reste de l'URI va être structuré. Pour le Web, on retrouve principalement les schémas pour http (ou https pour sa version sécurisée). Il est suivi par le nom d'un serveur (ou son adresse IP), un numéro de port peut être indiqué. Si ce n'est pas le cas, on utilisera le port 80 qui désigne le protocole HTTP. Ensuite on retrouve un chemin interne au serveur qui permet de localiser la ressource. - -Ainsi cet URI (qui pointe vers un excellent MOOC) : - -https:*www.fun-mooc.fr/courses/MinesTelecom/04011S02/session02/about - -utilise le schéma de représentation https, il y a donc ensuite un serveur www.fun-mooc.fr et le chemin est courses/MinesTelecom/04011S02/session02/about ``` +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 ``` -Le programme -Le programme Arduino que l'on a fait permet d'ouvrir la connexion vers le serveur api.tom.tools sur le port 80. +| 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 | -Si on le regarde avec plus de détail le programme WiFiClient-compteur.ino, dans la boucle (loop) on retrouve cette instruction : +> ⚠️ 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**. -- le serveur Web attend traditionnellement sur le port 80 - const int httpPort = 80; +Exemple : -- Si la connexion a échouée, ça sera pour la prochaine fois - if (!client.connect(host, httpPort)) { +``` +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; - } +} +``` -host et httpPort sont deux variables déclarées en début de programme qui valent respectivement api.tom.tools, soit le nom du serveur et connect permet d'ouvrir la connexion TCP avec le serveur. Si elle échoue, elle retourne false qui fait afficher un message d'erreur. Si par contre la connexion réussit, on continue à dérouler le programme : +Les variables `host` et `httpPort` sont déclarées en début de programme : - String url = String("/hits/"); +- `host` vaut `"api.tom.tools"` +- `httpPort` vaut `80` -On place dans la variable url, le chemin pour accéder à la ressource sur le serveur. On vient de construire l'URI suivante http:*api.tom.tools/hits. +`client.connect()` établit la connexion TCP. Si elle échoue, on affiche un message et on quitte la boucle. - client.print(String("GET ") + url + " HTTP/1.1\r\n" + - "Host: " + host + "\r\n" + - "Connection: close\r\n\r\n"); +### 4.2. Construire et envoyer la requête HTTP -On envoie ensuite sur la connexion TCP une chaîne de caractères comportant 3 lignes (\r\n indique qu'il s'agit d'un changement de ligne). La première ligne contient la requête. Elle commence par l'instruction : +```cpp +String url = String("/hits/"); - GET permet de récupérer la valeur de la ressource. - url contient le chemin vers cette ressource. - HTTP/1.1 désigne la version du protocole HTTP que l'on souhaite utiliser avec le serveur. Bien entendu, si le serveur ne connaît pas cette version, il répondra avec une autre version. On peut le voir dans la réponse examinée précédemment que le serveur avait répondu avec la version HTTP/1.0. +client.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Connection: close\r\n\r\n"); +``` -La deuxième ligne est plus complexe à comprendre. Elle permet de faire de la virtualisation, c'est-à-dire faire tourner plusieurs serveur Web indépendants sur la même machine. Supposons que nous voulions mettre deux serveurs api.tom.tools et www.justinbeiber-fan.fr sur la même machine. Ces deux serveurs vont avoir la même adresse IP. Donc, quand on ouvre une connexion, au niveau IP et TCP, il est impossible de savoir si l'on veut avoir des informations sur le Nelson ou connaître les frasques de notre chanteur favori. En rappelant le nom du serveur après la commande Hosts, le serveur saura quoi répondre. +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) : -La dernière ligne indique que l'on peut fermer la connexion quand tout est fini. +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`). -Une fois cette requête lancée, on attend une seconde pour avoir la réponse du serveur, puis : +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é. - delay(1000); +3. **`Connection: close`** — on signale au serveur qu'il peut fermer la connexion après avoir répondu. -Tant qu'il y a des données reçues, on les affiche ligne par ligne : +La ligne vide finale (`\r\n\r\n`) marque la fin de l'en-tête de la requête. - while(client.available()){ - String line = client.readStringUntil('\r'); * découpe ligne par ligne - Serial.print(line); - } +### 4.3. Recevoir et afficher la réponse -et on ferme la connexion TCP. On attend 30 secondes et on recommence en ouvrant une connexion... +```cpp +delay(1000); // on laisse au serveur le temps de répondre - Serial.println(); - Serial.println("connexion fermée"); - -Intéressons nous à la température - -En reprenant la structure du programme d'interrogation du compteur, il est facile de la transformer pour interroger un autre serveur. Intéressons-nous maintenant au programme WiFiTemperature.ino qui va interroger le serveur openweathermap.org : - -* valeurs pour le serveur Web -const char* host = "api.openweathermap.org"; -const char* apikey = "votre clé API (apikey)"; * il est possible d'utiliser la clé d'API suivante : 1a702a15a2f46e405e61804cf67c0d30 -const char* town = "Rennes,fr"; - -On ajoute aussi deux variables apikey et town qui serviront à construire l'URL. - - String url = String("/data/2.5/weather?q=") + town + "&appid=" + apikey; - -Le chemin est un peu plus complexe car il contient une partie fixe et à la fin il y a un point d'interrogation. Formellement, il s'agit de paramètres que l'on fournit à la ressource /data/2.5/weather pour qu'elle puisse fournir un résultat. Ici, on indique la ville et l'API key qui sert à identifier l'utilisateur qui fait la requête. - -On attend la réponse: - -- On attend 10 millisecondes - delay(10); - -puis on ignore toutes les méta-données de l'en-tête en attendant une ligne vide : - - inBody = false; * on est dans l'en-tête - -- On lit les données reçues, s'il y en a - while(client.available()){ +while (client.available()) { String line = client.readStringUntil('\r'); - - if (line.length() == 1) inBody = true; /* passer l'en-tête jusqu'à une ligne vide */ - if (inBody) { * ligne du corps du message, on cherche le mot clé + Serial.print(line); +} -La variable booléenne inBody reste à faux tant que l'on ne reçoit pas une ligne vide, c'est-à-dire d'une longueur de 1 (en prenant en compte le retour à la ligne). Si on est dans le corps (c'est-à-dire les données) on peut rechercher le mot clé qui nous intéresse dans la structure. Il est indiqué au début du programme : +Serial.println(); +Serial.println("connexion fermée"); +``` - String keyword = String("\"temp\":"); *chaîne que l'on recherche dans le JSON +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. -Notez les \ qui permettent de mettre les " dans la chaîne de caractères, donc en fait on recherche "temp" : +--- - if (inBody) { * ligne du corps du message, on cherche le mot clé - int pos = line.indexOf(keyword); +## 5. Cas pratique : récupérer la température extérieure - if (pos > 0) { /* mot clé trouvé */ - 1. indexOf donne la position du début du mot clé, en ajoutant sa longueur - 1. on se place à la fin. - pos += keyword.length(); +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. -Si pos est positif, c'est que le mot clé a été trouvé dans la ligne. Comme pos donne l'endroit où cette chaîne de caractères commence dans la ligne, en additionnant sa taille, on obtient la fin de la chaîne de caractères, c'est-à-dire le début du nombre recherché. Comme c'est également une chaîne de caractères, pour le transformer en nombre flottant (c'est-à-dire à virgule) on utilise la commande atof.> +### 5.1. Configurer la requête -Cette commande prend l'adresse en mémoire du début de la chaîne de caractères, c'est pour cela que l'opérateur & est utilisé : +```cpp +const char* host = "api.openweathermap.org"; +const char* apikey = "1a702a15a2f46e405e61804cf67c0d30"; // clé d'API +const char* town = "Rennes,fr"; +``` - temperature = atof(&line[pos]); +Trois variables sont nécessaires : -Voilà on a la valeur, il ne vous reste plus qu'à la combiner avec la commande vers le servo de votre Nelson pour le transformer en thermomètre ! -LE JSON +- **`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. -JSON - prononcé jisone en français ou jay-zon avec l'accent anglais - n'est pas un personnage de films d'horreur, mais l'acronyme de Java Script Object Notation. C'est un format qui a été à l'origine conçu pour transporter des données pour les pages Web. Mais, à l'inverse du tueur en série, JSON étant très tolérant, il a été très vite utilisé pour structurer d'autres informations et est très populaire pour l'internet des objets, car, en plus, il est compact et simple à utiliser. +### 5.2. Construire l'URL avec des paramètres -Structurer des données est quelque chose de très simple : sionnestructurepasunephraseenmettantdesespacesilesttrèsdurdelalireetdyretrouverdesmots. -Pour les données c'est la même chose. Si on veut uniquement avoir une température on peut très bien retourner juste 25, mais si dans la réponse on veut mettre d'autres informations, il faut définir une structure. Cela pourrait être par exemple des espaces séparant les valeurs, mais très vite il est difficile de se rappeler à quoi correspond telle colonne. +```cpp +String url = String("/data/2.5/weather?q=") + town + "&appid=" + apikey; +``` -JSON définit deux types de données de base, les chaînes de caractères qui sont entre " et les nombres qui sont non pas entre " et qui contiennent principalement des chiffres. Par exemple "123" est une chaîne de caractères et -.1e+3 est un nombre (c'est -100). Pour ce dernier, on a un peu exagéré en utilisant la notation eX qui correspond à 10 puissance X (comme sur les calculatrices). +Ce chemin est plus complexe que `/hits/`. Après le `?`, on trouve des **paramètres** au format `clé=valeur`, séparés par des `&` : -Ensuite on va avoir deux types structures, les listes de paires qui se composent de deux champs, le mot clé qui est un chaîne de caractères, suivi de : et d'une valeur qui peut être n'importe quoi. Les listes sont délimitées par des accolades. Par exemple, nous pourrions donner donne les propriétés d'un écran avec le json suivant : +- `q=Rennes,fr` : la ville recherchée. +- `appid=...` : la clé d'API. -{ "resolution_verticale": 1200, "resolution_horizontale": 900, "couleurs": true} +Ces paramètres permettent à la ressource `/data/2.5/weather` d'adapter sa réponse à notre demande. -Json permet également de faire des tableaux : +### 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] +``` -On peut noter que c'est beaucoup plus compact, mais dans ce cas la position est importante, il faut savoir à quoi correspond le premier élément, le second... Un tableau est délimité par des crochets droits et les éléments sont séparés par des virgules. La beauté de JSON est que l'on peut faire des liste de tableaux, des tableaux de listes, des tableaux de tableaux, des listes de listes, de tableaux de listes de tableaux de listes... Par exemple : +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, + "Hauteur": 800, "Largeur": 600, - "Titre": "Vue du 5ieme étage", + "Titre": "Vue du 5ème étage", "Vignette": { - "Url": "http://www.example.com/image/481989943", + "Url": "http://www.example.com/image/481989943", "Hauteur": 125, - "Largeur": 100 + "Largeur": 100 }, - "Animee" : false, + "Animee": false, "IDs": [116, 943, 234, 38793] - } - } + } +} +``` -Ce JSON décrit l'image par une liste de paires, donnant la hauteur, la largeur, un titre, une sous-liste qui décrivent une vignette,... Si vous lisez l'anglais, vous pouvez regarder la description officielle de JSON dans un standard. -``` \ No newline at end of file +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 ! + +--- + +## 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 + +**Idées de projets** pour mettre en pratique : + +- Un thermomètre connecté qui affiche la météo extérieure sur un servo-moteur. +- Une station de qualité d'air qui publie ses mesures sur un serveur distant. +- Un bouton physique qui déclenche une action sur un service web (lumière, notification…). + +Pour aller plus loin, vous pouvez explorer les **méthodes HTTP** autres que `GET` (`POST`, `PUT`, `DELETE`), apprendre à parser le JSON proprement avec une bibliothèque comme `ArduinoJson`, ou découvrir des protocoles dédiés à l'IoT comme **MQTT**. \ No newline at end of file diff --git a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/meta.json b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/meta.json index 9742b6a..fc813e8 100644 --- a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/meta.json +++ b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/meta.json @@ -1,18 +1,31 @@ { "uuid": "ae28cd3b-052c-43c0-bafd-69fd2fd96dd7", "slug": "utiliser-le-wifi-du-nodemcu", - "title": "Utiliser le Wifi du NodeMCU", + "title": "Utiliser le WiFi du NodeMCU", "author": "cedric@abonnel.fr", "published": true, - "published_at": "2020-04-17 18:22:49", + "featured": false, + "published_at": "2020-04-17 18:22", "created_at": "2020-04-17 18:22:49", - "updated_at": "2020-04-17 18:22:49", - "revisions": [], + "updated_at": "2026-05-16 20:43:40", + "revisions": [ + { + "n": 1, + "date": "2026-05-16 20:43:40", + "comment": "Titre modifié, tags modifiés, contenu modifié", + "title": "Utiliser le Wifi du NodeMCU" + } + ], "cover": "", "files_meta": [], "external_links": [], "seo_title": "", "seo_description": "", "og_image": "", - "category": "Électronique" + "category": "Électronique", + "tags": { + "tags": [ + "NodeMCU" + ] + } } diff --git a/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/revisions/0001.md b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/revisions/0001.md new file mode 100644 index 0000000..bbe9a46 --- /dev/null +++ b/ae28cd3b-052c-43c0-bafd-69fd2fd96dd7/revisions/0001.md @@ -0,0 +1,240 @@ +# Utiliser le Wifi du NodeMCU + +On va commencer par le début. Récupérer le programme exemple en allant *Fichier*=>*Exemples*=>*ESP8266WiFi*=>*WiFiScan* + +``` +Dans la fonction de configuration setup, on met le module WiFi en mode Station (on aurait pu le mettre aussi en point d'accès). Ne cherchez pas la méthode mode dans la doc Arduino, il s'agit d'une commande spécifique au module ESP (voir ici). La méthode disconnect permet ensuite de déconnecter le module ESP d'un point d'accès, au cas où ! + +Dans la fonction boucle (loop) on retrouve les instructions qui permettent d'afficher périodiquement les WiFi captés par le module ESP. La méthode scanNetwork permet , comme son nom l'indique, de scanner les différents canaux (fréquences) dédiés au WiFi et retourner le nombre de réseaux trouvés : + + int n = WiFi.scanNetworks(); + +Ainsi, si ce nombre n'est pas nul, ils vont être affichés un par un grâce à une boucle for. + + for (int i = 0; i < n; ++i) { + 1. Print SSID and RSSI for each network found + Serial.print(i + 1); + Serial.print(": "); + Serial.print(WiFi.SSID(i)); + Serial.print(" ("); + Serial.print(WiFi.RSSI(i)); + Serial.print(")"); + Serial.println[^note: WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*"); + delay(10); + } + +La méthode SSID permet d'afficher le nom du réseau WiFi. Dans le vocabulaire Wi-Fi, SSID veut dire Service Set IDentifier, mais bon c'est pas plus limpide ! La méthode RSSI (Received Signal Strength Indication) affiche la puissance du signal reçu. Les box et plus généralement les points d'accès WiFi émettent 10 fois par seconde un message donnant le nom du réseaux Wi-Fi. La méthode scanNetwork écoute toutes les fréquences et récupère tous ces noms. +``` + +Une fois connecté au réseau WiFi, nous ne pouvons pas encore communiquer avec d'autres équipements car nous n'avons pas d'adresse IP. + +``` +Pour obtenir une adresse, nous nous appuyons sur le programme WiFiClient_simple.ino comme son nom l'indique est simple et utilise le WiFi. On retrouve la connexion au réseau WiFi avec la méthode begin : + + WiFi.begin(ssid, password); * On se connecte + + while (WiFi.status() != WL_CONNECTED) { * On attend + delay(500); + Serial.print("."); + } + +La boucle while permet d'attendre que la connexion soit effective et que le serveur DHCP de la box ait fourni les paramètres nécessaires pour se connecter au réseau Internet, qui sont affichés avec le code suivant : + + Serial.println(""); * on affiche les paramètres + Serial.println("WiFi connecté"); + Serial.print("Adresse IP du module EPC: "); + Serial.println(WiFi.localIP(]; + Serial.print("Adresse IP de la box : "); + Serial.println(WiFi.gatewayIP()); + +La gateway correspond à machine vers laquelle notre module ESP va envoyer les informations, c'est-à-dire à l'adresse IP de la box. +``` + +``` +Le modèle client/serveur + +Le web est la huitième merveille du monde, au point qu'on le confond souvent avec l'Internet. Il ne s'agit pourtant que d'un des services possible sur le réseau. Ce qui est remarquable avec le web c'est sa scalabilité. C'est à dire la possibilité de répondre à un nombre très important de requêtes sans perdre en performances. Cela est dû à un ensemble de règles pour la conception des services. + +Le Web fonctionne sur le mode client/serveur. Le client veut une information et le serveur obéit en la retournant. Si le serveur devait se rappeler de tout ce qu'il a fait, il serait vite débordé. Alors la première règle fondamentale est que seul le client va garder une mémoire (un état) de ce qui se passe. Le serveur va envoyer ce qu'on lui a demandé et va passer à la demande suivante et ainsi de suite. Comme il y a beaucoup plus de clients que de serveurs, c'est plus facile à gérer. Par exemple, quand une page web est demandée à un serveur, elle va contenir d'autres références. Le client reçoit cette information, va la traiter, l'afficher et demander aux serveurs les éléments qui lui manquent. Ces informations peuvent être sur le même serveur ou sur un autre, ce n'est pas important car les serveurs ne se rappellent de rien après avoir traité la requête. On peut ainsi concevoir un service qui repose sur plusieurs serveurs et ainsi répartir le trafic et le traitement. +Les méta-données + +Les éléments traités par un serveur sont appelés ressources. La définition de ressource est très vague. Il s'agit d'une information binaire de taille finie. Cela peut correspondre à du texte, à une image, une vidéo, des données,... En plus des données, on peut mettre des méta-données. C'est à dire des choses qui sont au-dessus des données, comme la méta-physique est au-dessus de la physique ou le métabolisme est au-dessus du bolisme ;). + +Les méta-données vont permettre de mieux comprendre les données. Cela peut correspondre à leur date de production, à leur taille ou leur type. Cette dernière information est importante car elle permettra au client de bien les traiter. Dans la réponse d'un serveur, les méta-données (ou l'en-tête) sont séparées des données par une ligne vide, comme l'a montré la réponse qui a été affichée par l'Arduino pour le compteur. + +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 + +Dans cet exemple, la première ligne donne le statut de la requête, HTTP/1.0 donne la version du protocole. Le second champ, ici 200 va donner le code indiquant le succès ou l'échec du traitement de la requête par le serveur. On a de la chance, 200 signifie que c'est bon. Le premier chiffre donne la nature de la notification : + + 1XX : indication d'un traitement en cours + 2XX : succès + 3XX : redirection, il faut interroger un autre serveur pour avoir la réponse + 4XX : erreur du coté du client + 5XX : erreur du coté du serveur + +Ainsi la célèbre erreur 404 indiquant que la page n'est pas trouvée est bien une erreur du client qui pose des questions stupides au serveur. La ligne suivante donne la date, à laquelle la page a été produite par le serveur. La ligne suivante, un peu plus énigmatique, elle indique que notre super page web ne peut pas être incluse dans la page web d'un autre serveur. + +La dernière ligne de l'en-tête donne la nature de l'information, ici c'est du texte non formaté. + +Ensuite on a une ligne vide suivie de ce qui sera affiché, à savoir la valeur du compteur. +Les URI + +Un autre facteur clé pour le succès du Web, en plus de l'utilisation de serveurs simples, est l'utilisation d'une désignation uniforme des ressources. Initialement appelées URL (Uniform Resource Locator), on emploie maintenant plus le terme URI (Uniform Resource Identifier) plus générique et qui peut couvrir d'autres technologies (comme la téléphonie sur Internet). Mais de manière simple, une URI se structure de la manière suivante quand on l'utilise pour le Web ou l'Internet des objets : + +schéma:*serveur:port/chemin/jusqu/a/la/ressource + +Contrairement à l'idée admise, la première information, schéma, ne correspond pas au protocole employé sur le réseau, mais indique comment le reste de l'URI va être structuré. Pour le Web, on retrouve principalement les schémas pour http (ou https pour sa version sécurisée). Il est suivi par le nom d'un serveur (ou son adresse IP), un numéro de port peut être indiqué. Si ce n'est pas le cas, on utilisera le port 80 qui désigne le protocole HTTP. Ensuite on retrouve un chemin interne au serveur qui permet de localiser la ressource. + +Ainsi cet URI (qui pointe vers un excellent MOOC) : + +https:*www.fun-mooc.fr/courses/MinesTelecom/04011S02/session02/about + +utilise le schéma de représentation https, il y a donc ensuite un serveur www.fun-mooc.fr et le chemin est courses/MinesTelecom/04011S02/session02/about +``` + +``` +Le programme + +Le programme Arduino que l'on a fait permet d'ouvrir la connexion vers le serveur api.tom.tools sur le port 80. + +Si on le regarde avec plus de détail le programme WiFiClient-compteur.ino, dans la boucle (loop) on retrouve cette instruction : + +- le serveur Web attend traditionnellement sur le port 80 + const int httpPort = 80; + +- Si la connexion a échouée, ça sera pour la prochaine fois + if (!client.connect(host, httpPort)) { + Serial.println("connection failed"); + return; + } + +host et httpPort sont deux variables déclarées en début de programme qui valent respectivement api.tom.tools, soit le nom du serveur et connect permet d'ouvrir la connexion TCP avec le serveur. Si elle échoue, elle retourne false qui fait afficher un message d'erreur. Si par contre la connexion réussit, on continue à dérouler le programme : + + String url = String("/hits/"); + +On place dans la variable url, le chemin pour accéder à la ressource sur le serveur. On vient de construire l'URI suivante http:*api.tom.tools/hits. + + client.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Connection: close\r\n\r\n"); + +On envoie ensuite sur la connexion TCP une chaîne de caractères comportant 3 lignes (\r\n indique qu'il s'agit d'un changement de ligne). La première ligne contient la requête. Elle commence par l'instruction : + + GET permet de récupérer la valeur de la ressource. + url contient le chemin vers cette ressource. + HTTP/1.1 désigne la version du protocole HTTP que l'on souhaite utiliser avec le serveur. Bien entendu, si le serveur ne connaît pas cette version, il répondra avec une autre version. On peut le voir dans la réponse examinée précédemment que le serveur avait répondu avec la version HTTP/1.0. + +La deuxième ligne est plus complexe à comprendre. Elle permet de faire de la virtualisation, c'est-à-dire faire tourner plusieurs serveur Web indépendants sur la même machine. Supposons que nous voulions mettre deux serveurs api.tom.tools et www.justinbeiber-fan.fr sur la même machine. Ces deux serveurs vont avoir la même adresse IP. Donc, quand on ouvre une connexion, au niveau IP et TCP, il est impossible de savoir si l'on veut avoir des informations sur le Nelson ou connaître les frasques de notre chanteur favori. En rappelant le nom du serveur après la commande Hosts, le serveur saura quoi répondre. + +La dernière ligne indique que l'on peut fermer la connexion quand tout est fini. + +Une fois cette requête lancée, on attend une seconde pour avoir la réponse du serveur, puis : + + delay(1000); + +Tant qu'il y a des données reçues, on les affiche ligne par ligne : + + while(client.available()){ + String line = client.readStringUntil('\r'); * découpe ligne par ligne + Serial.print(line); + } + +et on ferme la connexion TCP. On attend 30 secondes et on recommence en ouvrant une connexion... + + Serial.println(); + Serial.println("connexion fermée"); + +Intéressons nous à la température + +En reprenant la structure du programme d'interrogation du compteur, il est facile de la transformer pour interroger un autre serveur. Intéressons-nous maintenant au programme WiFiTemperature.ino qui va interroger le serveur openweathermap.org : + +* valeurs pour le serveur Web +const char* host = "api.openweathermap.org"; +const char* apikey = "votre clé API (apikey)"; * il est possible d'utiliser la clé d'API suivante : 1a702a15a2f46e405e61804cf67c0d30 +const char* town = "Rennes,fr"; + +On ajoute aussi deux variables apikey et town qui serviront à construire l'URL. + + String url = String("/data/2.5/weather?q=") + town + "&appid=" + apikey; + +Le chemin est un peu plus complexe car il contient une partie fixe et à la fin il y a un point d'interrogation. Formellement, il s'agit de paramètres que l'on fournit à la ressource /data/2.5/weather pour qu'elle puisse fournir un résultat. Ici, on indique la ville et l'API key qui sert à identifier l'utilisateur qui fait la requête. + +On attend la réponse: + +- On attend 10 millisecondes + delay(10); + +puis on ignore toutes les méta-données de l'en-tête en attendant une ligne vide : + + inBody = false; * on est dans l'en-tête + +- On lit les données reçues, s'il y en a + while(client.available()){ + String line = client.readStringUntil('\r'); + + if (line.length() == 1) inBody = true; /* passer l'en-tête jusqu'à une ligne vide */ + if (inBody) { * ligne du corps du message, on cherche le mot clé + +La variable booléenne inBody reste à faux tant que l'on ne reçoit pas une ligne vide, c'est-à-dire d'une longueur de 1 (en prenant en compte le retour à la ligne). Si on est dans le corps (c'est-à-dire les données) on peut rechercher le mot clé qui nous intéresse dans la structure. Il est indiqué au début du programme : + + String keyword = String("\"temp\":"); *chaîne que l'on recherche dans le JSON + +Notez les \ qui permettent de mettre les " dans la chaîne de caractères, donc en fait on recherche "temp" : + + if (inBody) { * ligne du corps du message, on cherche le mot clé + int pos = line.indexOf(keyword); + + if (pos > 0) { /* mot clé trouvé */ + 1. indexOf donne la position du début du mot clé, en ajoutant sa longueur + 1. on se place à la fin. + pos += keyword.length(); + +Si pos est positif, c'est que le mot clé a été trouvé dans la ligne. Comme pos donne l'endroit où cette chaîne de caractères commence dans la ligne, en additionnant sa taille, on obtient la fin de la chaîne de caractères, c'est-à-dire le début du nombre recherché. Comme c'est également une chaîne de caractères, pour le transformer en nombre flottant (c'est-à-dire à virgule) on utilise la commande atof.> + +Cette commande prend l'adresse en mémoire du début de la chaîne de caractères, c'est pour cela que l'opérateur & est utilisé : + + temperature = atof(&line[pos]); + +Voilà on a la valeur, il ne vous reste plus qu'à la combiner avec la commande vers le servo de votre Nelson pour le transformer en thermomètre ! +LE JSON + +JSON - prononcé jisone en français ou jay-zon avec l'accent anglais - n'est pas un personnage de films d'horreur, mais l'acronyme de Java Script Object Notation. C'est un format qui a été à l'origine conçu pour transporter des données pour les pages Web. Mais, à l'inverse du tueur en série, JSON étant très tolérant, il a été très vite utilisé pour structurer d'autres informations et est très populaire pour l'internet des objets, car, en plus, il est compact et simple à utiliser. + +Structurer des données est quelque chose de très simple : sionnestructurepasunephraseenmettantdesespacesilesttrèsdurdelalireetdyretrouverdesmots. +Pour les données c'est la même chose. Si on veut uniquement avoir une température on peut très bien retourner juste 25, mais si dans la réponse on veut mettre d'autres informations, il faut définir une structure. Cela pourrait être par exemple des espaces séparant les valeurs, mais très vite il est difficile de se rappeler à quoi correspond telle colonne. + +JSON définit deux types de données de base, les chaînes de caractères qui sont entre " et les nombres qui sont non pas entre " et qui contiennent principalement des chiffres. Par exemple "123" est une chaîne de caractères et -.1e+3 est un nombre (c'est -100). Pour ce dernier, on a un peu exagéré en utilisant la notation eX qui correspond à 10 puissance X (comme sur les calculatrices). + +Ensuite on va avoir deux types structures, les listes de paires qui se composent de deux champs, le mot clé qui est un chaîne de caractères, suivi de : et d'une valeur qui peut être n'importe quoi. Les listes sont délimitées par des accolades. Par exemple, nous pourrions donner donne les propriétés d'un écran avec le json suivant : + +{ "resolution_verticale": 1200, "resolution_horizontale": 900, "couleurs": true} + +Json permet également de faire des tableaux : + +[1200, 900, true] + +On peut noter que c'est beaucoup plus compact, mais dans ce cas la position est importante, il faut savoir à quoi correspond le premier élément, le second... Un tableau est délimité par des crochets droits et les éléments sont séparés par des virgules. La beauté de JSON est que l'on peut faire des liste de tableaux, des tableaux de listes, des tableaux de tableaux, des listes de listes, de tableaux de listes de tableaux de listes... Par exemple : + + { + "Image": { + "Hauteur": 800, + "Largeur": 600, + "Titre": "Vue du 5ieme étage", + "Vignette": { + "Url": "http://www.example.com/image/481989943", + "Hauteur": 125, + "Largeur": 100 + }, + "Animee" : false, + "IDs": [116, 943, 234, 38793] + } + } + +Ce JSON décrit l'image par une liste de paires, donnant la hauteur, la largeur, un titre, une sous-liste qui décrivent une vignette,... Si vous lisez l'anglais, vous pouvez regarder la description officielle de JSON dans un standard. +``` \ No newline at end of file