Code minimal des fonctions réseau : Différence entre versions

Ligne 514 : Ligne 514 :
  
 
''Une fois le code téléversé sur votre carte, pour voir le résultat, connectez-vous (avec un ordinateur ou un mobile) sur le point d'accès "AP_PetitDeb" (mot de passe "PSWD1234"), puis lancez votre navigateur préféré et tapez 192.168.4.1.''
 
''Une fois le code téléversé sur votre carte, pour voir le résultat, connectez-vous (avec un ordinateur ou un mobile) sur le point d'accès "AP_PetitDeb" (mot de passe "PSWD1234"), puis lancez votre navigateur préféré et tapez 192.168.4.1.''
 +
<br />{{#annotatedImageLight:Fichier:Code minimal des fonctions reseau Ecran-Etape-4.png|0=785px|hash=|jsondata=|mediaClass=Image|type=frameless|alt=Ecran-Etape-4A|align=center|src=https://www.wikidebrouillard.org/images/9/98/Code_minimal_des_fonctions_reseau_Ecran-Etape-4.png|href=./Fichier:Code minimal des fonctions reseau Ecran-Etape-4.png|resource=./Fichier:Code minimal des fonctions reseau Ecran-Etape-4.png|caption=Ecran-Etape-4A|size=785px}}
 +
 +
 +
 
<br /><syntaxhighlight lang="arduino" line="1">
 
<br /><syntaxhighlight lang="arduino" line="1">
 
/* =========================================================================================================
 
/* =========================================================================================================
Ligne 1 110 : Ligne 1 114 :
  
 
<br />
 
<br />
}}
 
{{Tuto Step
 
|Step_Title=Interrogation de serveurs de données (mode JSON)
 
|Step_Content='''JSON, c'est quoi ?'''
 
 
 
Nous allons maintenant nous intéresser à la récupération de données sur Internet (informations sur la météo, sur la pollution, sur les derniers recensements, ...). De nombreux serveurs de données, et en particulier les serveurs "Open Data" (offrant des données libres de droit), sont accessibles en mode web. C'est-à-dire qu'une simple requête dans la barre d'adresse de votre navigateur, permet de récupérer les informations souhaitées.
 
 
 
 
Et, encore mieux, dans la plupart des cas, la réponse revient sous une forme standardisée de type '''JSON''' (JavaScript Objet Notation), que les navigateurs récents sont capables de décoder. A titre d'exemple, ouvrez un nouvel onglet dans votre navigateur, et recopiez dans la barre d'adresse ce qui suit ...
 
 
<br />
 
<nowiki>https://data.rennesmetropole.fr//api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=rocade</nowiki>
 
... et vous devriez avoir en retour un texte de ce type :   
 
 
 
{"nhits": 63, "parameters": {"dataset": "etat-du-trafic-en-temps-reel", "q": "rocade", "rows": 10, "start": 0, "format": "json", "timezone": "UTC"}, "records": [{"datasetid": "etat-du-trafic-en-temps-reel", "recordid": "c8cd4fc9d2a9f1840170322c834f827fc100cc75", "fields": {"traveltimereliability": 100, "traveltime": 55, "predefinedlocationreference": "30023", "averagevehiclespeed": 91, "datetime": "2022-11-29T15:11:00+01:00", "gml_id": "v_rva_troncon_fcd.fid-722fb9f8_184c264cda5_453f", "trafficstatus": "freeFlow", "func_class": 666, "geo_point_2d": [48.14130932076887, -1.6781068587055177], '''(...)'''
 
 
... mais que votre navigateur va quasi-immédiatement immédiatement reconnaître comme un format JSON, et afficher sous une forme plus structurée :
 
 
{{#annotatedImageLight:Fichier:Code minimal des fonctions reseau Code JSON.png|0=564px|hash=|jsondata=|mediaClass=Image|type=frameless|alt=Exemple de réponse JSON|align=center|src=https://www.wikidebrouillard.org/images/d/d7/Code_minimal_des_fonctions_reseau_Code_JSON.png|href=./Fichier:Code minimal des fonctions reseau Code JSON.png|resource=./Fichier:Code minimal des fonctions reseau Code JSON.png|caption=Exemple de réponse JSON|size=564px}}
 
 
Nous avons fait ici appel au serveur Open Data de la ville de Rennes, et avons fait une requête demandant l'état du trafic sur la rocade principale. Ce même serveur propose un tas d'autres données libres, et on peut trouver sur Internet une multitude d'autres serveurs "Open Data" en mode JSON.   
 
 
 
'''Oui, mais mon D1 mini n'a pas de navigateur ?'''
 
 
 
C'est là où deux autres bibliothèques vont nous être utiles : 
 
 
*la première pour permettre à notre carte se connecter au serveur de données en mode sécurisé (car la plupart des sites web ont une adresse ''''https'''://www...') : '''WiFiClientSecure'''. Celle-ci est intégrée de base dans l'environnement de développement Arduino.
 
*la seconde pour décoder le format  JSON et extraire facilement les éléments de réponse qui nous intéressent : '''ArduinoJson'''. Celle-ci doit être récupérée  dans le gestionnaire de bibliothèques :<br /> {{#annotatedImageLight:Fichier:Code minimal des fonctions reseau Biblio ArduinoJSON.png|0=752px|hash=|jsondata=|mediaClass=Image|type=frameless|alt=Bibliothèque ArduinoJSON|align=center|src=https://www.wikidebrouillard.org/images/9/92/Code_minimal_des_fonctions_reseau_Biblio_ArduinoJSON.png|href=./Fichier:Code minimal des fonctions reseau Biblio ArduinoJSON.png|resource=./Fichier:Code minimal des fonctions reseau Biblio ArduinoJSON.png|caption=Bibliothèque ArduinoJSON|size=752px}}
 
 
Les possibilités sont multiples, et l'exploitation des données JSON par les cartes D1 mini ou ESP32, peut prendre des formes très sympathiques : voir par exemple les réalisations "[https://www.wiki.lesfabriquesduponant.net/index.php?title=POCL_:_VOIR_DEMAIN Voir Demain]" et "[https://www.wiki.lesfabriquesduponant.net/index.php?title=POCL_:_Hawaiiiii Hawaiiiii]" issues d'un hackathon organisé en décembre 2021 par Les Petits Débrouillards Grand Ouest et L'Edulab de l'Université de Rennes 2.
 
 
TABLEAU
 
 
 
{| class="wikitable" cellspacing="0" border="0"
 
| height="17" bgcolor="#999999" align="left" |
 
| valign="middle" bgcolor="#999999" align="center" |
 
| bgcolor="#999999" align="center" | ---
 
|-
 
| rowspan="2" valign="middle" height="49" bgcolor="#999999" align="center" |Avant le Setup
 
| valign="middle" bgcolor="#999999" align="center" |Importation de la bibliothèque
 
| valign="middle" align="left" |#include <MQTT.h>
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Création de l’objet
 
| valign="middle" align="left" |MQTTClient myMQTTClient;
 
|-
 
| rowspan="5" valign="middle" height="17" bgcolor="#999999" align="center" |Dans le Setup (ou le loop)
 
| valign="middle" bgcolor="#999999" align="center" |Initialisation
 
| valign="middle" align="left" |myMQTTClient.begin(@IP Broker, Port Broker, Client Wifi) ;
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Se préparer à la réception de messages
 
| valign="middle" align="left" |myMQTTClient.onMessage(référence de la fonction à appeler sur réception d'un message) ;
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Connexion au broker MQTT
 
|myMQTTClient.connect(ID unique)
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Souscrire à un "topic" particulier
 
|myMQTTClient.subscribe(topic) ;
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Publier un message
 
|myMQTTClient.publish(topic, message) ;
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Dans le Loop
 
| valign="middle" bgcolor="#999999" align="center" |Activation régulière
 
|obligatoire : myMQTTClient.loop() ;
 
|}
 
 
 
FIN TABLEAU
 
 
Pour connaître toutes les autres possibilités de cette bibliothèque, voir sa référence,  [https://github.com/256dpi/arduino-mqtt ici].
 
 
 
{| class="wikitable" cellspacing="0" border="0"
 
| height="17" bgcolor="#999999" align="left" |
 
| valign="middle" bgcolor="#999999" align="center" |
 
| bgcolor="#999999" align="center" |Bibliothèque JSON
 
|-
 
| valign="middle" height="49" bgcolor="#999999" align="center" |Avant le Setup
 
| valign="middle" bgcolor="#999999" align="center" |Importation de la bibliothèque
 
| valign="middle" align="left" |#include <ArduinoJson.h>
 
|-
 
| rowspan="2" |Dans le setup ou le loop
 
| valign="middle" bgcolor="#999999" align="center" |Création de l'objet JSON (cas 1 ou 2 : voir code minimal)
 
| valign="middle" align="left" |DynamicJsonDocument myJSON(taille) ; // Cas 1, ou ...
 
StaticJSONDocument<taille> ; // Cas 2.
 
|-
 
| valign="middle" bgcolor="#999999" align="center" |Obtenir la structure JSON à partir du message texte
 
|DeserializationError error = deserializeJson(myJSON, JSON_text, JSON_Size) ;
 
|}<br />
 
'''Code minimal :'''
 
 
 
Bon, en fait, pas tout à fait "minimal" :
 
 
*pour des raisons de clarté, nous avons défini deux fonctions :  '''serverRequest''' pour générer la requête auprès du serveur et récupérer la réponse, et '''showJSONAnswer''' pour analyser la réponse (décodage des informations JSON).
 
*pour faciliter la réutilisation de ce code, plutôt que de tout traiter dans le setup(), nous activerons ces fonctions régulièrement, depuis la boucle loop(), ce qui est le mode de fonctionnement habituel.
 
 
<br /><syntaxhighlight lang="arduino" line="1">
 
/* =========================================================================================================
 
*
 
*                              CODE MINIMAL RESEAU - ETAPE 5 : Données JSON
 
*         
 
* ---------------------------------------------------------------------------------------------------------
 
* Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
 
* ========================================================================================================= */
 
 
// Bibliothèques requises
 
// ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
 
 
#include <WiFiManager.h>                          // Gestion de la connexion Wi-Fi (recherche de points d'accès) 
 
#include <WiFiClientSecure.h>                    // Gestion de la connexion (HTTP) à un serveur de données
 
#include <ArduinoJson.h>                          // Fonctions de décodage JSON des réponses du serveur.
 
 
 
 
// Variables globales
 
 
WiFiManager myWiFiManager;                        // Création de mon instance de WiFiManager.
 
WiFiClientSecure myWiFiClient;                    // Création de mon instance de client WiFi.
 
const char* mySSID  = "AP_PetitDeb" ;            // Nom de la carte en mode Point d'Accès.
 
const char* mySecKey = "PSWD1234" ;              // Mot de passe associé, 8 caractères au minimum.
 
 
char* Data_HOST = "data.rennesmetropole.fr";      // Serveur web hébergeant les données qui nous intéressent
 
int  Data_PORT = 443;                            // Port sur lequel envoyer la requête
 
char* Data_REQUEST =                              // Requête (sur cet exemple : demande de l'état du trafic au point
 
                                                  // 31553, correspondant à la porte de Saint-Malo de la rocade de Rennes
 
      "/api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=31553"; 
 
 
 
const int MAX_RESPONSE_SIZE = 6000 ;              // Taille max de la réponse attendue d'un serveur. A modifier en fonction du besoin.
 
char Data_Response[MAX_RESPONSE_SIZE] ;          // Buffer qui contiendra la réponse du serveur.
 
 
 
#define TEN_SECONDS 10000                        // On appelera le serveur de données toutes les 10000 ms = 10 secondes.
 
unsigned long myWakeUp ;                          // Timer mis en place pour limiter le nombre d'appels au serveur de données.
 
 
/* --------------------------------------------------------------------------------------------------------------
 
*  serverRequest() : Envoi requête HTTP au serveur et récupération de la réponse
 
*  paramètres :
 
*    - pHost    : nom du serveur ;
 
*    - pPort    : port sur lequel est appelé le serveur ;
 
*    - pRequest  : requête au serveur.
 
*    - pResponse : endroit où stocker la réponse
 
*    - pRespMax  : nombre max de caractères autorisés pour la réponse
 
*  valeur de retour :
 
*      -2 = réponse tronquée (trop de caractères) ;
 
*      -1 = pas de réponse ;
 
        0 = pas de connexion au serveur ;
 
*      1 = ok.
 
*  ------------------------------------------------------------------------------------------------------------- */
 
int serverRequest(char* pHost, int pPort, char* pRequest, char *pResponse, int pRespMax) {
 
   
 
    const int API_TIMEOUT = 15000;      // Pour être sûr de recevoir l'en-tête de la réponse client.
 
 
    // Comme la connexion est sécurisée (protocole HTTPS), il faudrait indiquer le certificat du site web.
 
    // Pour simplifier, on va utiliser l'option magique ".setInsecure()", ce qui n'est pas important dans
 
    // notre exemple, où les données échangées ne sont pas confidentielles.
 
 
    myWiFiClient.setInsecure();
 
    myWiFiClient.setTimeout(API_TIMEOUT);
 
 
    // Connexion au serveur (on essaie 5 fois, avec un intervalle d'une seconde)
 
 
    Serial.print("--- Connexion au serveur [" + String(pHost) + "] ");
 
    int nbTries = 1;
 
    while(!myWiFiClient.connect(pHost, pPort)) {
 
        Serial.print(".");
 
        if (++nbTries > 5) {
 
            Serial.println("--- Connexion impossible :-(");
 
            myWiFiClient.stop();
 
            return(0);
 
        }
 
        delay(1000);
 
    } 
 
 
    // Connecté à notre serveur ! --> Envoi de la requête URL. Il faut envoyer en fait une suite de lignes :
 
    //        "GET <notre requête> HTTP/1.1"
 
    //        "Host: <nom du serveur>"
 
    //        "Connection: close"
 
    //        <ligne vide>
 
    // Cet envoi se fait simplement grâce à la fonction println du client WiFi, similaire à celle que
 
    // l'on utilise pour envoyer des données au moniteur série pour nos traces.
 
 
    String myURL = String(pRequest);
 
    Serial.println() ;
 
    Serial.println("--- Connexion OK ! --> Envoi requête URL - " + myURL);
 
    myWiFiClient.println("GET " + myURL + " HTTP/1.1") ;
 
    myWiFiClient.println("Host: " + String(pHost)) ;
 
    myWiFiClient.println("Connection: close") ;
 
    myWiFiClient.println() ;
 
   
 
    // Attente de la réponse ....(on essaie 50 fois, avec un intervalle de 100ms, donc 5 secondes en tout)
 
   
 
    nbTries = 1;
 
    while(!myWiFiClient.available()){
 
        if (++nbTries > 50) {
 
            Serial.println("--- Pas de réponse :-(");
 
            myWiFiClient.stop();
 
            return(-1);
 
        }
 
        delay(100);
 
    }
 
 
    // Récupération de l'en-tête de la réponse (dont on ne fera rien)
 
    // Cette entête est une suite de caractères, composant un certain nombre de lignes (ie se terminant par '\n'),
 
    // la dernière ligne de l'entête n'est composée que du caractère "\r" (suivie du '\n') ;
 
   
 
    Serial.println("--- Réponse OK --> Récupération de l'en-tête ...");
 
    String myLine ;
 
    while (myWiFiClient.connected()) {
 
        myLine = myWiFiClient.readStringUntil('\n');
 
        if (myLine == "\r") {
 
            break;
 
        }
 
    }
 
 
    // Entête reçue ! On va alors recopier dans pResponse tous les caractères qui suivent
 
    // en faisant attention à ne pas dépasser la taille du buffer.
 
 
    Serial.println("--- Entête ok --> Récupération des données ...");
 
    int myIndex = 0 ;
 
    while(myWiFiClient.available() && myWiFiClient.connected() ){
 
 
        char myResp = myWiFiClient.read();
 
        pResponse[myIndex] = myResp;   
 
        if (myIndex++ >= pRespMax) {
 
            Serial.println("*** Réponse trop longue : " + String(pRespMax) + "caractères, et ne peut pas être traitée") ;
 
            myWiFiClient.stop();
 
            return(-2);
 
        }
 
        pResponse[myIndex] = '\0';            // Vu sur forums : conseillé d'ajouté 'fin de chaîne' systématiquement
 
        delay(1) ;                            // Et également d'ajouter ce tout petit délai pour éviter des plantages.
 
       
 
    }
 
 
    // Tout s'est bien passé ! On arrête notre client WiFi
 
 
    Serial.println("--- Récupération des données ok (" + String(myIndex) + " caractères).") ;
 
    myWiFiClient.stop();
 
    return(1) ;
 
 
}
 
 
/* --------------------------------------------------------------------------------------------------------
 
*  showJSONAnswer : Décodage de la structure de données JSON
 
*  Paramètres :
 
*    - pResponse : endroit se trouve la réponse (au format JSON) du serveur
 
*    - pRespMax  : nombre max de caractères autorisés pour la réponse
 
* -------------------------------------------------------------------------------------------------------- */
 
void showJSONAnswer(char *pResponse, int pRespMax) {
 
 
    // Création de notre structure JSON
 
    // Le besoin en mémoire (capacity) doit être vérifié sur l'assistant https://arduinojson.org/v6/assistant/
 
    // 1) dans la première page de l'assistant, sélectionnez le processeur (par exemple "ESP8266"), le mode
 
    //    "Deserialize", et le type d'entrée "char*", puis cliquez sur le bouton "Netx:JSON"
 
    // 2) Lancez votre requête depuis un navigateur. Dans notre exemple, tapez dans la barre d'adresse :
 
    //      "https://data.rennesmetropole.fr/api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=31553"
 
    // 3) Recopiez la réponse obtenue - sous sa forme "Données Brutes" du navigateur vers l'assistant
 
    // 4) L'assistant va alors préconiser le bon objet à créer (StaticJsonDocument ou DynamicJsonDocument),
 
    //    ainsi que la taille à réserver. L'assistant va même proposer un exemple de programme exploitant toutes
 
    //    les informations de la structure JSON.
 
    // Pour notre exemple, l'assistant a proposé la définition qui suit.
 
   
 
    StaticJsonDocument<768> doc;
 
 
    // Décodage de la réponse JSON.
 
    // La fonction deserializeJson va transformer la réponse "texte" du serveur, en une structure de données recopiée
 
    // dans la variable 'doc', où il sera ensuite facile d'aller chercher les informations souhaitées.
 
   
 
    DeserializationError error = deserializeJson(doc, pResponse, pRespMax);
 
    if (error) {
 
        Serial.println("--- Décodage réponse JSON KO, code " + String(error.f_str())) ;
 
        return;
 
    }
 
    Serial.println("--- Décodage réponse JSON OK !") ;
 
 
    // Nous pouvons maintenant extraire facilement les informations qui nous intéressent,
 
    // en n'oubliant pas le niveau de profondeur de la donnée au sein de la structure JSON.
 
    // Ce niveau de profondeur est incrémenté par le nombre de '{' ou '[' rencontrés, et
 
    // décrémenté lors de la rencontre des ']' et {}'. Sur notre exemple 'rocade de Rennes',
 
    // cela donne ceci :
 
    //      +-----------------------------------------------------------------+
 
    //      |  {                                                            | ... Entrée niveau 1
 
    //      |      "nhits": 1,                                                |
 
    //      |      "parameters": {                                            | ... Entrée niveau 2
 
    //      |          "dataset": "etat-du-trafic-en-temps-reel",            |
 
    //      |          (...)                                                  |
 
    //      |      },                                                        | ... Retour niveau 1
 
    //      |      "records": [                                              | ... Début d'un tableau : niveau 2
 
    //      |          {                                                      | ... Entrée niveau 3
 
    //      |              (...)                                              |                                                          |
 
    //      |              "fields": {                                        | ... Entrée niveau 4
 
    //      |                  (...)                                        |
 
    //      |                  "averagevehiclespeed": 88,                    |
 
    //      |                  (...)                                        |
 
    //      |                  "datetime": "2022-11-30T11:57:00+01:00",      |
 
    //      +-----------------------------------------------------------------+
 
    // ... et donc :
 
    //  - (1er niveau) --------- doc["nhits"] donnera la valeur 1,
 
    //  - (2ème niveau) -------- doc["parameters"]["dataset"] donnera la valeur "etat-du-trafic-en-temps-reel"
 
    //  - (4ème niveau) -------- doc["records"][0]["fields"]["averagevehiclespeed"] donnera la valeur 87
 
 
    // Extraction et affichage sur le port série de trois  valeurs
 
 
    String myLocRef = String(doc["records"][0]["fields"]["predefinedlocationreference"]) ;
 
    String myTime = String(doc["records"][0]["fields"]["datetime"]) ;
 
    int mySpeed    = doc["records"][0]["fields"]["averagevehiclespeed"] ;
 
   
 
    Serial.print("Vitesse au point " + myLocRef + " ") ;
 
    Serial.print("le " +  myTime.substring(8,10) + "/" + myTime.substring(5,7) + "/" + myTime.substring(0,4) + " ") ;
 
    Serial.print("à " +  myTime.substring(11,13) + "h" + myTime.substring(14,16) + " ") ;
 
    Serial.println(" : " + String(mySpeed) + " km/h.") ;
 
 
}
 
 
/* --------------------------------------------------------------------------------------------------------
 
*  SETUP : Initialisation
 
* -------------------------------------------------------------------------------------------------------- */
 
void setup() {
 
 
    // Initialisation de la liaison série, affichage 1er message
 
 
    Serial.begin(115200);
 
    delay(100) ;
 
    Serial.println();
 
    Serial.println("-----------------------") ;
 
    Serial.println("Exemple extraction JSON") ;
 
    Serial.println("-----------------------") ;
 
 
    // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi  se connecter au dernier Point d'Accès connu,
 
    // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
 
    // Point d'Accès choisir. Par défaut, on restera bloqué tant que l'utilisateur n'aura pas fait de choix.
 
   
 
    Serial.println("Connexion au Wi-Fi ...");
 
    if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
 
        Serial.println(); Serial.print("Connecté ! Adresse IP : ");
 
        Serial.println(WiFi.localIP());
 
    }
 
    else {
 
        Serial.println("Connexion Wi-Fi KO :-(");   
 
    }
 
 
    // Initialisation du timer qui sera testé dans loop() - pour faire appel au serveur seulement toutes les 10 secondes
 
    // millis() est une fonction système donnant le nombre de ms depuis le lancement ou la réinitialisation de la carte.
 
 
    unsigned long myWakeUp = millis() + TEN_SECONDS ;
 
 
}
 
 
/* --------------------------------------------------------------------------------------------------------------
 
*  LOOP : fonction appelée régulièrement par le système
 
*  ------------------------------------------------------------------------------------------------------------- */
 
void loop() {
 
 
    unsigned long myNow = millis() ;
 
    if (myNow >= myWakeUp) {
 
        Serial.println("Wake Up ! Nouvelle demande au serveur ...") ;
 
        if (serverRequest(Data_HOST, Data_PORT, Data_REQUEST, Data_Response, MAX_RESPONSE_SIZE) == 1) {
 
            Serial.println("Réponse reçue du serveur, lancement analyse JSON ...") ;
 
            showJSONAnswer(Data_Response, MAX_RESPONSE_SIZE) ;
 
        }
 
        myWakeUp = myNow + TEN_SECONDS ;       
 
    }
 
 
}
 
</syntaxhighlight>
 
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step

Version du 20 décembre 2022 à 10:49

Auteur avatarPhilippe Blusseau | Dernière modification 6/02/2023 par Philby

Code minimal des fonctions r seau WiKi Reseau.png
Nous allons résumer ici les différentes façons d'utiliser les fonction réseau des cartes compatibles Arduino possédant une puce Wi-Fi (Wemos D1 mini, ESP32, ...). Ces fonctions vont permettre à ces cartes de discuter entre elles, d'accéder à des données sur Internet, ou même de proposer une interface web.
Licence : Attribution (CC-BY)

Introduction

Cette expérience regroupe différentes manières de bénéficier des capacités de communication des cartes compatibles Arduino possédant une puce Wi-Fi. On suppose (Cf. "Expériences ré-requises" ci-après) que vous avez déjà manipulé une carte Arduino et son environnement de développement. Toutes les étapes décrites ici sont en fait indépendantes :


  • Les deux premières étapes permettent à notre carte de se connecter au Wi-Fi, de façon basique (étape 1) ou un peu plus évoluée, et souple (étape 2) ;
  • L'étape 3 permet à la carte d'être dans un mode particulier, dit "AP"(Access Point") et se comporter comme une borne Wi-Fi.
  • Les étapes suivantes correspondent à des besoins différents :
    • étape 4 : visualiser et/ou modifier des paramètres de notre carte via une interface web ;
    • étape 5 : permettre à notre carte d'accéder à des données d'un site externe, utilisant le format standardisé JSON (1)
    • étape 6 : permettre à notre carte d'envoyer et de recevoir des messages courts, via le protocole standardisé MQTT (1)

Dans la même philosophie que les expériences "Code minimal des capteurs pour Arduino" et "Code minimal des actionneurs pour Arduino", nous fournirons ici uniquement le code relatif à nos besoins de connexion, sans mettre au point une quelconque application. Donc, ici, pas besoin de connecter une led ou un capteur, donc pas de schéma de montage : vous branchez simplement votre carte en USB sur votre ordinateur, et les résultats seront visibles en mode texte dans le moniteur série de l'environnement de développement Arduino.


Il existe bien sûr déjà un tas de tutoriels avec des exemples similaires, sans compter bien sûr les exemples fournis avec les bibliothèques que l'on va utiliser (2). La modeste contribution de de cette page est de tenter de regrouper les cas de figures les plus importants. S'il en manque, n'hésitez pas à mettre un commentaire, voire à ajouter une étape !


(1) Pas d'inquiétude sur les "JSON "et autre "MQTT", on explique tout dans les étapes correspondantes

(2) On donnera d'ailleurs les liens vers les bibliothèques et leurs exemples à chaque étape.
  • Expériences pré-requises

Étape 1 - Connexion Wi-Fi en environnement stable

Cette première étape permet à un Wemos D1 mini (ou autre carte compatible Arduino avec puce Wi-Fi) de se connecter au Wi-Fi dans un environnement connu, et qui ne change pas ; C'est-à-dire que l'on à accès à une borne Wi-Fi, on connait son mot de passe - aka "clé de sécurité réseau", et a priori la carte va rester dans cet environnement.


Ces bibliothèques sont pré-chargées dans l'environnement Arduino, il n'est pas nécessaire d'aller les chercher dans le gestionnaire de bibliothèques.

Connexion Wi-Fi de base
Avant le Setup Importation de la bibliothèque #include <ESP8266WiFi.h> // Pour le Wemos D1 Mini, ou ...

#include <WiFi.h> // ... Pour l'ESP32

Création de l’objet
Dans le Setup Démarrage de la connexion WiFi.begin(SSID, SecKey) ; // Où SSID est le nom du point d'accès Wi-Fi, et SecKey son mot de passe
Test de la connexion if (WiFi.Status() == WL_CONNECTED) { (...) }
Récupération de l'adresse IP WiFi.localIP() ;
Dans le Loop Utilisation Le test de la connexion, et la récupération de l'adresse IP peuvent aussi être utilisés dans le loop().


Pour connaître toutes les autres possibilités de ces bibliothèques, voir leurs références, respectivement ici (D1 Mini) et ici (ESP32).


 1 /* =========================================================================================================
 2  * 
 3  *                         CODE MINIMAL RESEAU - ETAPE 1 : connexion basique au Wi-Fi
 4  * 
 5  * ---------------------------------------------------------------------------------------------------------
 6  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
 7  * ========================================================================================================= */
 8 
 9 // Bibliothèques WiFi : UNE SEULE EST NECESSAIRE, choisir celle correspondant à votre matériel. 
10 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
11 
12 #include <ESP8266WiFi.h>          // A utiliser pour le D1 Mini 
13 //#include <WiFi.h>               // A utiliser pour l'ESP32
14 
15 // Définition du point d'accès Wi-Fi et de son mot de passe ("clé de sécurité")
16 // A REMPLACER PAR LES VERITABLES VALEURS CORRESPONDANT A VOTRE EMPLACEMENT
17         
18 const char* mySSID   = "MA_BOX_INTERNET";
19 const char* mySecKey = "MA_CLE_DE_SECURITE";
20 
21 /* --------------------------------------------------------------------------------------------------------
22  *  SETUP : Initialisation
23  *  -------------------------------------------------------------------------------------------------------- */
24 void setup() {
25 
26     // Initialisation de la liaison série, affichage 1er message
27 
28     Serial.begin(115200);
29     delay(100) ;
30     Serial.println(); 
31     Serial.println("----------------------------------") ;
32     Serial.println("Exemple de connexion Wi-Fi basique") ;
33     Serial.println("----------------------------------") ;
34 
35     // Démarrage de la tentative de connexion, avec le nom du point d'accès Wi-Fi et son mot de passe
36 
37     WiFi.begin(mySSID, mySecKey) ;
38 
39     // Attente de la connexion pendant 10 secondes (20 x 500 ms)
40 
41     Serial.print("Connexion à "); Serial.print(mySSID) ; Serial.print(" ") ;
42     int tryNumber = 1 ;
43     while (WiFi.status() != WL_CONNECTED)
44     {
45         delay(500);
46         Serial.print(".");
47         if (++tryNumber > 20) {
48             Serial.println() ; Serial.println("Pas de connexion, abandon") ;
49             return ;
50         }
51     } 
52 
53     // La connexion a réussi ! On affiche l'adresse IP obtenue.
54     
55     Serial.println(); Serial.print("Connecté ! Adresse IP : ");
56     Serial.println(WiFi.localIP());
57     
58 }
59 
60 /* --------------------------------------------------------------------------------------------------------------
61  *  LOOP : fonction appelée régulièrement par le système
62  *  ------------------------------------------------------------------------------------------------------------- */
63 void loop() { 
64   /* On ne fait rien de particulier sur cet exemple */
65 }

Étape 2 - Oui, mais si je dois changer de borne Wi-Fi ?

Dans l'étape précédente on supposait que le Wi-Fi était permanent (cas où les cartes ne quittent pas notre labo, par exemple). Mais si on souhaite faire voyager nos cartes (démos dans des écoles, etc ...), on a trois possibilités :

  • On vient avec son environnement de développement Arduino, on met à jour le code Arduino avec les nouvelles informations Wi-Fi, et on téléverse le code sur toutes nos cartes ... un peu laborieux, n'est-il pas ?
  • ou bien, on dispose en permanence d'un téléphone mobile avec forfait données (4G ou +), et on utilise systématiquement celui-ci en mode "Point d'accès mobile". C'est donc ce téléphone qui sera vu en permanence comme le point d'accès Wi-Fi préféré de votre carte (D1 mini ou ESP32), quelque soit l'endroit où vous vous trouvez. Pas toujours possible ...
  • et sinon, on utilise la bibliothèque "WiFiManager", qui nous simplifie grandement la tâche !

En effet, cette bibliothèque permet d'enregistrer de façon pérenne (même si on débranche la carte) le dernier Point d'Accès Wi-Fi sur lequel la carte a réussi à se connecter. La bibliothèque va d'abord chercher à se connecter sur ce Point d'Accès "connu". Et si ça ne fonctionne pas (on a changé de lieu, par exemple), alors elle va se positionner elle-même en mode "Point d'Accès", et va proposer sur l'adresse 192.168.4.1 une interface web permettant d'afficher les Points d'Accès Wi-Fi environnants, et d'en sélectionner un. Le nouveau Point d'Accès choisi sera sauvegardé pour les fois suivantes.


Interface web du WiFi Manager :

  1. Avec un téléphone ou ordinateur, connectez-vous au point d'accès "AP_PetitDeb" et tapez le mot de passe associé. Il y aura probablement un message indiquant qu'Internet n'est pas disponible, c'est normal, ne pas en tenir compte ;
  2. Sur votre navigateur préféré, tapez "192.168.4.1", ce qui fera apparaître l'interface web générée par la bibliothèque WiFiManager. Cliquez sur "Configure WiFi", vous arrivez sur le choix du point d'accès.
  3. Choisissez alors votre point d'accès Wi-Fi préféré, son nom sera reporté dans la case "SSID", complétez avec le mot de passe dans la case "Password", et cliquez sur Save

Image permettant de comprendre l'utilisation de la bibliothèque WiFiManager


Bibliothèque


La bibliothèque doit être rajoutée à l'environnement Arduino dans le gestionnaire de Bibliothèques (voir ici pour le mode d'emploi) :


Image de la Bibliothèque WiFiManager dans le Gestionnaire de Bibliothèques Arduino


Bibliothèque Wifi Manager
Avant le Setup Importation de la bibliothèque #include <WiFiManager.h>
Création de l’objet WiFiManager myWiFiManager;
Dans le Setup Déclaration du mode bloquant myWiFiManager.setConfigPortalBlocking(true); // ... ou rien (mode par défaut)
Déclaration du mode non bloquant myWiFiManager.setConfigPortalBlocking(false);
Tentative de connexion à un PA Wi-Fi if (myWiFiManager.autoConnect(Nom_AP, MotDePasse_AP)) { (... connexion OK !) }
Dans le Loop Activation régulière pour le mode non bloquant myWiFiManager.process() ; // Obligatoire en mode non bloquant

Pour connaître toutes les autres possibilités de cette bibliothèque, voir sa référence, ici.


Code minimal  : mode bloquant (par défaut)


 1 /* =========================================================================================================
 2  * 
 3  *          CODE MINIMAL RESEAU - ETAPE 2 : Connexion à un point d'accès choisi par l'utilisateur
 4  *          
 5  *          CAS A : MODE BLOQUANT - On attend tant que l'utilisateur n'a pas choisi son Point d'Accès Wi-Fi
 6  * 
 7  * ---------------------------------------------------------------------------------------------------------
 8  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
 9  * ========================================================================================================= */
10 
11 // Bibliothèque WiFiManager. Un seule bibibliothèque suffit ici, quelque soit la carte (ESP32 ou Wemos D1 Mini)
12 
13 #include <WiFiManager.h>          // Gestion de la connexion Wi-Fi (recherche de points d'accès)  
14 WiFiManager myWiFiManager;        // Création de mon instance de WiFiManager.
15 
16 // Définition de la carte lorsqu'elle se positionne en mode "Point d'Accès".
17         
18 const char* mySSID   = "AP_PetitDeb" ;      // Nom du point d'accès
19 const char* mySecKey = "PSWD1234" ;         // Mot de passe, 8 caractères au minimum
20 
21 /* --------------------------------------------------------------------------------------------------------
22  *  SETUP : Initialisation
23  *  -------------------------------------------------------------------------------------------------------- */
24 void setup() {
25 
26     // Initialisation de la liaison série, affichage 1er message
27 
28     Serial.begin(115200);
29     delay(100) ;
30     Serial.println(); 
31     Serial.println("----------------------------------") ;
32     Serial.println("Exemple de connexion Wi-Fi évoluée") ;
33     Serial.println("----------------------------------") ;
34 
35     // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi  se connecter au dernier Point d'Accès connu,
36     // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
37     // Point d'Accès choisir. Par défaut, on restera bloqué tant que l'utilisateur n'aura pas fait de choix.
38     
39     Serial.println("Connexion au Wi-Fi ...");
40     if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
41       
42         // Wi-Fi en mode standard ok --> On affiche l'adresse IP obtenue.
43 
44         Serial.println(); Serial.print("Connecté ! Adresse IP : ");
45         Serial.println(WiFi.localIP());
46         
47     }
48     else {
49         Serial.println("Connexion Wi-Fi KO :-(");     
50     }
51    
52 }
53 
54 /* --------------------------------------------------------------------------------------------------------------
55  *  LOOP : fonction appelée régulièrement par le système
56  *  ------------------------------------------------------------------------------------------------------------- */
57 void loop() { 
58   /* On ne fait rien de particulier sur cet exemple */
59 }


Code minimal  : mode non bloquant
 1 /* =========================================================================================================
 2  * 
 3  *          CODE MINIMAL RESEAU - ETAPE 2 : Connexion à un point d'accès choisi par l'utilisateur
 4  *          
 5  *          CAS B : MODE NON BLOQUANT - On peut faire autre chose en attendant que l'utilisateur ait choisi
 6  *                  son Point d'Accès Wi-Fi
 7  * 
 8  * ---------------------------------------------------------------------------------------------------------
 9  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
10  * ========================================================================================================= */
11 
12 // Bibliothèque WiFiManager. Un seule bibibliothèque suffit ici, quelque soit la carte (ESP32 ou Wemos D1 Mini)
13 
14 #include <WiFiManager.h>          // Gestion de la connexion Wi-Fi (recherche de points d'accès)  
15 WiFiManager myWiFiManager;        // Création de mon instance de WiFiManager.
16 
17 // Définition de la carte lorsqu'elle se positionne en mode "Point d'Accès".
18         
19 const char* mySSID   = "AP_PetitDeb" ;      // Nom du point d'accès
20 const char* mySecKey = "PSWD1234" ;         // Mot de passe, 8 caractères au minimum
21 
22 // Pour les besoins de l'exemple (traces)
23 
24 bool IAmNotConnected = true ;
25 
26 /* --------------------------------------------------------------------------------------------------------
27  *  SETUP : Initialisation
28  *  -------------------------------------------------------------------------------------------------------- */
29 void setup() {
30 
31     // Initialisation de la liaison série, affichage 1er message
32 
33     Serial.begin(115200);
34     delay(100) ;
35     Serial.println(); 
36     Serial.println("----------------------------------") ;
37     Serial.println("Exemple de connexion Wi-Fi évoluée") ;
38     Serial.println("----------------------------------") ;
39 
40     // Déclaration du mode "non bloquant".
41     // Bonus : suppression des traces fournies par le WiFiManager (il est très bavard)
42     
43      myWiFiManager.setConfigPortalBlocking(false); 
44      myWiFiManager.setDebugOutput(false);     
45 
46     // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi  se connecter au dernier Point d'Accès connu,
47     // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
48     // Point d'Accès choisir. On ne reste pas bloqué, la suite du setup() va se dérouler, et le WiFiManager
49     // traitera les demandes ultérieurement, dans la fonction loop().
50      
51     Serial.println("Connexion au Wi-Fi ...");
52     if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
53       
54         // Wi-Fi en mode standard ok --> On affiche l'adresse IP obtenue.
55 
56         Serial.println(); Serial.print("Connecté ! Adresse IP : ");
57         Serial.println(WiFi.localIP());
58         
59     }
60     else {
61 
62         // Wi-Fi standard KO, on est passé en mode AP, qui sera traité dans le loop().
63         
64         Serial.println("Pas de point Wi-Fi connu, passage en mode AP (identifiant \"" + String(mySSID) + "\")");     
65     }
66    
67 }
68 
69 /* --------------------------------------------------------------------------------------------------------------
70  *  LOOP : fonction appelée régulièrement par le système
71  *  ------------------------------------------------------------------------------------------------------------- */
72 void loop() { 
73   
74   // Obligatoire en mode non bloquant, pour que le WiFiManager continue sa tâche.
75 
76   myWiFiManager.process() ;
77 
78   // Test pour savoir si on est enfin connecté - on ne l'affiche qu'une fois pour limiter les traces
79 
80   if (IAmNotConnected) {
81       if (WiFi.status() == WL_CONNECTED) {
82           Serial.print("Connecté au point d'accès " + String(WiFi.SSID()) + ", Adresse IP : ") ;
83           Serial.println(WiFi.localIP());
84           IAmNotConnected = false ;
85       }
86   }
87   
88 }

Étape 3 - Et si je veux rester en mode AP (Access Point) Wi-Fi ?

Il existe des cas particuliers où une application n'a en fait pas besoin du Wi-Fi pour aller envoyer ou recevoir des données d'Internet, mais souhaite être juste être considérée comme un Point d'Accès Wi-Fi. Ce mode est suffisant si l'on souhaite commander notre carte depuis un système proche. Par exemple pour commander un système domotique en mode web depuis notre mobile ... petit "spoiler" de l'étape 4 :-)


Pour utiliser le mode "Point d'Accès", on garde les librairies de base, vues à l'étape 1, en utilisant d'autres fonctions.

Mode Point d'Accès
Avant le Setup Importation de la bibliothèque #include <ESP8266WiFi.h> // Pour le Wemos D1 Mini, ou ...

#include <WiFi.h> // ... Pour l'ESP32

Création de l’objet
Dans le Setup Lancement du mode AP

avec mot de passe

if (WiFi.softAP(SSID, SecKey)) { (... succès ... } ; // Où SSID est le nom du point d'accès Wi-Fi, et SecKey son mot de passe
Lancement du mode AP

sans mot de passe

if (WiFi.softAP(SSID)) { (... succès ... } ; // Où SSID est le nom du point d'accès Wi-Fi
Récupération de l'adresse IP de base WiFi.softAPIP() ;
Dans le Loop Utilisation La récupération de l'adresse IP peut aussi être utilisée dans le loop().


Pour connaître toutes les autres possibilités de ces bibliothèques, voir leurs références, respectivement ici (D1 Mini) et ici (ESP32).


 1 /* =========================================================================================================
 2  * 
 3  *          CODE MINIMAL RESEAU - ETAPE 3 : Configuration en mode Point d'Accès
 4  *          
 5  * ---------------------------------------------------------------------------------------------------------
 6  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
 7  * ========================================================================================================= */
 8 
 9 // Bibliothèques WiFi : UNE SEULE EST NECESSAIRE, choisir celle correspondant à votre matériel. 
10 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
11 
12 #include <ESP8266WiFi.h>          // A utiliser pour le D1 Mini 
13 //#include <WiFi.h>               // A utiliser pour l'ESP32
14 
15 const char* mySSID = "AP_PetitDeb" ;
16 const char* mySecKey = "PSWD1234" ;
17 
18 /* --------------------------------------------------------------------------------------------------------
19  *  SETUP : Initialisation
20  *  -------------------------------------------------------------------------------------------------------- */
21 void setup() {
22 
23     // Initialisation de la liaison série, affichage 1er message
24 
25     Serial.begin(115200);
26     delay(100) ;
27     Serial.println(); 
28     Serial.println("-----------------------------") ;
29     Serial.println("Exemple en mode Point d'Accès") ;
30     Serial.println("-----------------------------") ;
31 
32     // Déclaration du mode "Point d'Accès"
33 
34     Serial.println("Déclaration Mode AP, SSID \"" + String(mySSID) + "\"") ;
35 
36     if (WiFi.softAP(mySSID,mySecKey)) {
37 
38         // Voilà, nous somme en mode "Point d'Accès", notre carte sera visible des systèmes Wi-Fi environnants,
39         // au même titre que les autres box Wi-Fi du voisinage. Par contre, ça s'arrête là, et si une fois
40         // connecté sur ce Point d'Accès "AP_PetitDeb" on cherche à joindre notre carte sur l'adresse IP obtenue
41         // ci-dessous par WiFi.softAPIP(), on aura droit à un beau "ERR_CONNECTION_REFUSED". Normal, on n'a pas
42         // précisé dans l'application ce qu'il faut faire : voir exemple suivant Code_Minimal_Etape4.
43         
44         Serial.print("Mode AP OK, IP Address : ") ;
45         Serial.println(WiFi.softAPIP()) ;
46     }
47     else {
48         Serial.println("Mode AP KO ... :-(") ;
49     }
50     
51 }
52 
53 /* --------------------------------------------------------------------------------------------------------------
54  *  LOOP : fonction appelée régulièrement par le système
55  *  ------------------------------------------------------------------------------------------------------------- */
56 void loop() { 
57   
58     // Rien de spécial dans cet exemple
59       
60 }

Étape 4 - Mise en place d'un serveur Web

Une première possibilité d'utilisation du Wi-Fi sur nos cartes, est d'y définir un mini-serveur web , qui nous permettra d'afficher voire de modifier, depuis n'importe quel navigateur, des données gérées par la carte - et par conséquent de récupérer la valeur de capteurs (température, ...) ou gérer des actionneurs (moteurs, leds, ...).


On peut trouver quelques exemples d'applications de ce type, réalisée par des Petits Débrouillards, en particulier le Petit Bot, petit robot commandable, ou encore Commander un D1 mini avec une interface web, permettant d'animer des leds, de gérer un moteur, et de récupérer des données du D1 mini sur une interface web.


Site web
Avant le Setup Importation de la bibliothèque #include <ESP8266WebServer.h> // Pour le D1 Mini, ou ...

#include <WebServer.h> // ... pour l'ESP32

Création du serveur web (sur le port 80) ESP8266WebServer myWeb(80); // Pour le D1 Mini , ou ...

// WebServer myWeb(80) ; // ... pour l'ESP32

Dans le Setup Déclaration de la fonction qui s'occupera

de la génération de la page web

myWeb.on ( "/", runPage01 );
Démarrage du serveur web myWeb.begin();
Dans le Loop Traitement des requêtes web myWeb.handleClient();

Code Minimal 4a : affichage d'un paramètre de la carte.


La page web hébergée sur notre carte doit être codée en HTML. La page peut être assez évoluée, intégrer du code javascript, être formatée en mode CSS, etc ... Dans l'exemple "minimal" ci-dessous, on se contente d'une page HTML basique.

Une fois le code téléversé sur votre carte, pour voir le résultat, connectez-vous (avec un ordinateur ou un mobile) sur le point d'accès "AP_PetitDeb" (mot de passe "PSWD1234"), puis lancez votre navigateur préféré et tapez 192.168.4.1.


Ecran-Etape-4A



  1 /* =========================================================================================================
  2  * 
  3  *                              CODE MINIMAL RESEAU - ETAPE 4 : site WEB
  4  *          
  5  *              CAS A : Page HTML Basique, consultation d'une variable de la carte
  6  *          
  7  * ---------------------------------------------------------------------------------------------------------
  8  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
  9  * ========================================================================================================= */
 10 
 11 // Bibliothèques WiFi et WebServer: ATTENTION, choisir celles correspondant à votre matériel. 
 12 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
 13 
 14 #include <ESP8266WiFi.h>                // A utiliser pour le D1 Mini 
 15 #include <ESP8266WebServer.h>           // A utiliser pour le D1 Mini
 16 //#include <WiFi.h>                     // A utiliser pour l'ESP32
 17 //#include <WebServer.h>                // A utiliser pour l'ESP32
 18 
 19 const char* mySSID = "AP_PetitDeb" ;    // On va utiliser le mode "Access Point" pour cet exemple
 20 const char* mySecKey = "PSWD1234" ;
 21 
 22 // Déclaration de notre serveur web interne. 
 23 
 24 ESP8266WebServer myWeb(80);           // A utiliser pour le D1 Mini 
 25 // WebServer myWeb(80) ;              // A utiliser pour l'ESP32
 26 
 27 /* --------------------------------------------------------------------------------------------------------
 28  *  webPage01 : formattage HTML de la page web. 
 29  *  - En fait cette fonction doit rendre une chaîne (String) contenant l'intégralité du code HTML qui sera
 30  *    envoyé au navigateur de l'utilisateur.
 31  *  - On peut y insérer des informations de la carte, comme ici par exemple, le nom du Point d'accès (mySSID). 
 32  *    Dans une véritable application, ça pourrait être la valeur d'un capteur de température.
 33  * - Pour pouvoir débugger facilement le code HTML/Javascript sur un browser (par exemple Firefox / Outils 
 34  *   supplémentaires / Outils de développement Web), il est préférable d'indenter le code à l'intérieur de la chaîne
 35  *   de caractère, et de mettre des sauts de ligne ("\n") à la fin de chaque ligne de code HTML. 
 36  *  -------------------------------------------------------------------------------------------------------- */
 37 String webPage01() {
 38   
 39     String p;
 40     p =  "<html lang=fr-FR><head><title>ETAPE 4 (Mini-Web)</title></head>\n" ;
 41     p += "<body>\n" ;
 42     p += "    <br><br><br><center><font size=\"12\">\n" ;
 43     p += "    Bonjour, je suis " + String(mySSID) + "...\n" ;          // C'est ici qu'on place l'information SSID.
 44     p += "    <br>... tr&egrave;s heureux de te rencontrer !\n" ;
 45     p += "    </center>\n" ;
 46     p += "</body></html>\n" ;
 47     return p;
 48   
 49 }
 50 
 51 /* --------------------------------------------------------------------------------------------------------
 52  *  runPage01 : gestion de la page web
 53  *  -------------------------------------------------------------------------------------------------------- */
 54 void runPage01() {
 55 
 56     // Affichage de la page Web.
 57     myWeb.send ( 200, "text/html", webPage01() ); 
 58 }
 59 
 60 /* --------------------------------------------------------------------------------------------------------
 61  *  SETUP : Initialisation
 62  *  -------------------------------------------------------------------------------------------------------- */
 63 void setup() {
 64 
 65     // Initialisation de la liaison série, affichage 1er message
 66 
 67     Serial.begin(115200);
 68     delay(100) ;
 69     Serial.println(); 
 70     Serial.println("----------------------") ;
 71     Serial.println("Exemple de serveur WEB") ;
 72     Serial.println("----------------------") ;
 73 
 74     // Déclaration du mode "Point d'Accès". On s'arrête là si échec.
 75 
 76     Serial.println("Déclaration Mode AP, SSID \"" + String(mySSID) + "\"") ;
 77     if (!WiFi.softAP(mySSID,mySecKey)) {
 78         Serial.println("Mode AP KO ... :-(") ;
 79         return ;
 80     }
 81 
 82     // Affichage de l'adresse IP principale du Point d'Accès.
 83 
 84     Serial.print("Mode AP OK, IP Address : ") ;
 85     Serial.println(WiFi.softAPIP()) ;
 86 
 87     // Définition des points d'entrée du serveur Web (un seul ici), et démarrage du serveur.
 88   
 89     myWeb.on ( "/", runPage01 );
 90     myWeb.begin();
 91     
 92 }
 93 
 94 /* --------------------------------------------------------------------------------------------------------------
 95  *  LOOP : fonction appelée régulièrement par le système
 96  *  ------------------------------------------------------------------------------------------------------------- */
 97 void loop() { 
 98   
 99     // Traitement des requêtes web.
100     myWeb.handleClient(); 
101       
102 }

Code Minimal 4b : actionner la carte via l'interface web


Dans l'exemple précédent, on se contente de récupérer un paramètre de la carte. Mais il est également possible de modifier un paramètre (par exemple l'état d'une des sorties de la carte, et donc d'agir sur un de ses périphériques : led, moteur, ...).


Le code ci-dessous présente donc un code "moins minimal", permettant d'afficher et de modifier une variable du programme Arduino. Dans une application réelle, il suffira alors d'utiliser cette variable pour afficher et agir sur sur l'état d'une des entrées/sorties de la carte.

La partie HTML est un peu plus complexe, car on va y définir une fonction javascript, qui permettra de faire passer des informations du navigateur au serveur web hébergé. Voir les explications complémentaires dans le code lui-même.

  1 /* =========================================================================================================
  2  * 
  3  *                              CODE MINIMAL RESEAU - ETAPE 4 : site WEB
  4  *          
  5  *              CAS B : Page HTML plus évoluéee, et modification d'une variable de la carte
  6  *          
  7  * ---------------------------------------------------------------------------------------------------------
  8  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
  9  * ========================================================================================================= */
 10 
 11 // Bibliothèques WiFi et WebServer: ATTENTION, choisir celles correspondant à votre matériel. 
 12 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
 13 
 14 #include <ESP8266WiFi.h>                // A utiliser pour le D1 Mini 
 15 #include <ESP8266WebServer.h>           // A utiliser pour le D1 Mini
 16 //#include <WiFi.h>                     // A utiliser pour l'ESP32
 17 //#include <WebServer.h>                // A utiliser pour l'ESP32
 18 
 19 const char* mySSID = "AP_PetitDeb" ;    // On va utiliser le mode "Access Point" pour cet exemple
 20 const char* mySecKey = "PSWD1234" ;
 21 
 22 // Déclaration de notre serveur web interne, qui écoutera sur le port 80.
 23 
 24 ESP8266WebServer myWeb(80);           // A utiliser pour le D1 Mini 
 25 // WebServer myWeb(80) ;              // A utiliser pour l'ESP32
 26 
 27 // Variable qui sera affichée et modifiée depuis notre interface web.
 28 
 29 int myValue = 0 ;
 30 
 31 /* --------------------------------------------------------------------------------------------------------
 32  *  webPage01 : formattage HTML de la page web. 
 33  *  - En fait cette fonction doit rendre une chaîne (String) contenant l'intégralité du code HTML qui sera
 34  *    envoyé au navigateur de l'utilisateur.
 35  *  - Comme dans l'exemple précédent (Exemple_4A), on insère dans cette chaîne une information de la carte,
 36  *    ici la valeur de notre variable 'my value'. Mais on va aussi ajouter des boutons permettant de modifier
 37  *    cette valeur sur la carte. 
 38  * - Idem Exemple_4A : pour pouvoir débugger facilement le code HTML/Javascript sur un browser (par exemple 
 39  *   Firefox / Outils supplémentaires / Outils de développement Web), il est préférable d'indenter le code à
 40  *   l'intérieur de la chaîne de caractère, et de mettre des sauts de ligne ("\n") à la fin de chaque ligne 
 41  *   de code HTML. 
 42  *  -------------------------------------------------------------------------------------------------------- */
 43 String webPage01() {
 44 
 45     String p;  
 46 
 47     // Début de construction de la page web (entête, titre, paramètres)
 48     
 49     p =  "<html lang=fr-FR><head>\n" ; 
 50     p += "<title>ETAPE 4B</title>\n" ;              // Titre de la page
 51     p += "    <meta charset='UTF-8'>\n" ;           // Codage des caractères, UTF-8 est fortement recommandé
 52     p += "</head>\n" ;
 53 
 54     // Définitions CSS (), qui permettent de décrire le format des objets sur la page web. 
 55     // Si vous voulez tout savoir sur CSS, on peut trouver une bonne introduction ici : https://developer.mozilla.org/fr/docs/Learn/CSS
 56     // et une référence complète ici : https://developer.mozilla.org/fr/docs/Web/CSS/Reference
 57     
 58     p  += "<style>\n" ;
 59     p += "    body { background-color: #000088; color: white; font-size: 25px; }\n";           // couleur fond écran (bleu foncé) et textes (blanc).
 60     p += "    input { width:25%; margin:10px; font-size:20px; border-radius: 5px; }\n";        // format des boutons (taille, coins arrondis, ...).
 61     p += "</style>\n" ;
 62 
 63     // Début du code javascript. Javascript est le langage utilisé au niveau des navigateurs web (Firefox, Microsoft Edge, Google Chrome, ...)
 64     // pour introduire un peu de dynamisme et d'intelligence dans les pages web. Cela peut permettre, par exemple, de réaliser une action 
 65     // locale et immediate, telle que l'agrandissement d'une image, le changement d'un texte, etc ... sans avoir à réinterroger le serveur web.
 66     // 
 67     // Dans notre cas, la fonction 'addition(val)' ci-dessous va ajouter le paramètres 'val' à l'adresse du serveur web, et va ensuite appeler
 68     // la page web de notre carte, avec ce paramètre. Par exemple, si l'adresse du site web de notre carte est 192.168.4.1, l'appel à la fonction
 69     // addition(-1) va demander la page '192.168.4.1?add=-1'. Le paramètre 'add' de valeur '-1' sera alors exploité par la carte dans la 
 70     // fonction runPage01() définie plus bas. 
 71     //
 72     // Dans un exemple réel on pourrait bien sûr définir plusieurs paramètres, du style '192.168.4.1?voyant=vert&servo1=90&servo2=0'
 73 
 74     p += "<script>\n" ;
 75     p +=  "function addition(val) {\n";                                             
 76     p +=  "    window.location = window.location.pathname + '?add=' + val ;\n";    
 77     p +=  "}\n";
 78     p += "</script>\n" ;
 79 
 80     // Corps de la page web : affichage de la valeur récupérée sur la carte, et de deux boutons 'ajouter 1' et 'enlever 1'.
 81     // La fonction addition() définie dans le code javascript ci-dessus, sera appelée lorsqu'on appuie sur ces boutons.
 82     
 83     p += "<body><center>\n" ;
 84     p += "    </br></br>Valeur actuelle : " + String(myValue) + "</br></br>\n";                 
 85     p +=  "   <form>\n";  
 86     p +=  "         <input type='submit' value='ajouter 1' formaction='javascript:addition(1);' formmethod=post>\n" ;
 87     p +=  "         <input type='submit' value='enlever 1' formaction='javascript:addition(-1);' formmethod=post>\n" ;
 88     p +=  "   </form>\n";
 89     p += "</center></body></html>" ;
 90 
 91     // ça y est, la page web est complètement constituée !
 92 
 93     return p;
 94   
 95 }
 96 
 97 /* --------------------------------------------------------------------------------------------------------
 98  *  runPage01 : gestion de la page web
 99  *  -------------------------------------------------------------------------------------------------------- */
100 void runPage01() {
101 
102     // Si la page a un paramètre 'add', alors on récupère sa valeur, et on l'ajoute à notre variable 'myValue'.
103   
104     if ( myWeb.hasArg("add") ) { 
105         Serial.println("Traitement pge web, arg = '" + String(myWeb.arg("add")) + "'") ;
106         int myArg = myWeb.arg("add").toInt() ; 
107         myValue = myValue + myArg ;
108         Serial.println("Traitement page web, arg = '" + String(myWeb.arg("add")) + "' --> Nouvelle valeur : " + String(myValue)) ;
109     }
110 
111     // On renvoie la page Web.
112     myWeb.send ( 200, "text/html", webPage01() ); 
113 }
114 
115 /* --------------------------------------------------------------------------------------------------------
116  *  SETUP : Initialisation
117  *  -------------------------------------------------------------------------------------------------------- */
118 void setup() {
119 
120     // Initialisation de la liaison série, affichage 1er message
121 
122     Serial.begin(115200);
123     delay(100) ;
124     Serial.println(); 
125     Serial.println("----------------------") ;
126     Serial.println("Exemple de serveur WEB") ;
127     Serial.println("----------------------") ;
128 
129     // Déclaration du mode "Point d'Accès". On s'arrête là si échec.
130 
131     Serial.println("Déclaration Mode AP, SSID \"" + String(mySSID) + "\"") ;
132     if (!WiFi.softAP(mySSID,mySecKey)) {
133         Serial.println("Mode AP KO ... :-(") ;
134         return ;
135     }
136 
137     // Affichage de l'adresse IP principale du Point d'Accès.
138 
139     Serial.print("Mode AP OK, IP Address : ") ;
140     Serial.println(WiFi.softAPIP()) ;
141 
142     // Définition des points d'entrée du serveur Web (un seul ici), 
143     // et démarrage du serveur.
144   
145     myWeb.on ( "/", runPage01 );
146     myWeb.begin();
147     
148 }
149 
150 /* --------------------------------------------------------------------------------------------------------------
151  *  LOOP : fonction appelée régulièrement par le système
152  *  ------------------------------------------------------------------------------------------------------------- */
153 void loop() { 
154   
155     // Traitement des requêtes web.
156     myWeb.handleClient(); 
157       
158 }

Étape 5 - Interrogation de serveurs de données (mode JSON)

JSON, c'est quoi ?


Nous allons maintenant nous intéresser à la récupération de données sur Internet (informations sur la météo, sur la pollution, sur les derniers recensements, ...). De nombreux serveurs de données, et en particulier les serveurs "Open Data" (offrant des données libres de droit), sont accessibles en mode web. C'est-à-dire qu'une simple requête dans la barre d'adresse de votre navigateur, permet de récupérer les informations souhaitées.


Et, encore mieux, dans la plupart des cas, la réponse revient sous une forme standardisée de type JSON (JavaScript Objet Notation), que les navigateurs récents sont capables de décoder. A titre d'exemple, ouvrez un nouvel onglet dans votre navigateur, et recopiez dans la barre d'adresse ce qui suit ...


https://data.rennesmetropole.fr//api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=rocade

... et vous devriez avoir en retour un texte de ce type :

{"nhits": 63, "parameters": {"dataset": "etat-du-trafic-en-temps-reel", "q": "rocade", "rows": 10, "start": 0, "format": "json", "timezone": "UTC"}, "records": [{"datasetid": "etat-du-trafic-en-temps-reel", "recordid": "c8cd4fc9d2a9f1840170322c834f827fc100cc75", "fields": {"traveltimereliability": 100, "traveltime": 55, "predefinedlocationreference": "30023", "averagevehiclespeed": 91, "datetime": "2022-11-29T15:11:00+01:00", "gml_id": "v_rva_troncon_fcd.fid-722fb9f8_184c264cda5_453f", "trafficstatus": "freeFlow", "func_class": 666, "geo_point_2d": [48.14130932076887, -1.6781068587055177], (...)

... mais que votre navigateur va quasi-immédiatement immédiatement reconnaître comme un format JSON, et afficher sous une forme plus structurée :

Exemple de réponse JSON

Nous avons fait ici appel au serveur Open Data de la ville de Rennes, et avons fait une requête demandant l'état du trafic sur la rocade principale. Ce même serveur propose un tas d'autres données libres, et on peut trouver sur Internet une multitude d'autres serveurs "Open Data" en mode JSON.


Oui, mais mon D1 mini n'a pas de navigateur ?


C'est là où deux autres bibliothèques vont nous être utiles :

  • la première pour permettre à notre carte se connecter au serveur de données en mode sécurisé (car la plupart des sites web ont une adresse 'https://www...') : WiFiClientSecure. Celle-ci est intégrée de base dans l'environnement de développement Arduino.
  • la seconde pour décoder le format JSON et extraire facilement les éléments de réponse qui nous intéressent : ArduinoJson. Celle-ci doit être récupérée dans le gestionnaire de bibliothèques :
    Bibliothèque ArduinoJSON

Les possibilités sont multiples, et l'exploitation des données JSON par les cartes D1 mini ou ESP32, peut prendre des formes très sympathiques : voir par exemple les réalisations "Voir Demain" et "Hawaiiiii" issues d'un hackathon organisé en décembre 2021 par Les Petits Débrouillards Grand Ouest et L'Edulab de l'Université de Rennes 2.


Fonctions JSON


Pour connaître toutes les autres possibilités de cette bibliothèque, voir sa référence, ici.


Code minimal :


Bon, en fait, pas tout à fait "minimal" :

  • pour des raisons de clarté, nous avons défini deux fonctions : serverRequest pour générer la requête auprès du serveur et récupérer la réponse, et showJSONAnswer pour analyser la réponse (décodage des informations JSON).
  • pour faciliter la réutilisation de ce code, plutôt que de tout traiter dans le setup(), nous activerons ces fonctions régulièrement, depuis la boucle loop(), ce qui est le mode de fonctionnement habituel.

  1 /* =========================================================================================================
  2  * 
  3  *                              CODE MINIMAL RESEAU - ETAPE 5 : Données JSON
  4  *          
  5  * ---------------------------------------------------------------------------------------------------------
  6  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
  7  * ========================================================================================================= */
  8 
  9 // Bibliothèques requises
 10 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
 11 
 12 #include <WiFiManager.h>                          // Gestion de la connexion Wi-Fi (recherche de points d'accès)  
 13 #include <WiFiClientSecure.h>                     // Gestion de la connexion (HTTP) à un serveur de données
 14 #include <ArduinoJson.h>                          // Fonctions de décodage JSON des réponses du serveur. 
 15 
 16 
 17 
 18 // Variables globales
 19 
 20 WiFiManager myWiFiManager;                        // Création de mon instance de WiFiManager.
 21 WiFiClientSecure myWiFiClient;                    // Création de mon instance de client WiFi.
 22 const char* mySSID   = "AP_PetitDeb" ;            // Nom de la carte en mode Point d'Accès.
 23 const char* mySecKey = "PSWD1234" ;               // Mot de passe associé, 8 caractères au minimum.
 24 
 25 char* Data_HOST = "data.rennesmetropole.fr";      // Serveur web hébergeant les données qui nous intéressent
 26 int   Data_PORT = 443;                            // Port sur lequel envoyer la requête
 27 char* Data_REQUEST =                              // Requête (sur cet exemple : demande de l'état du trafic au point
 28                                                   // 31553, correspondant à la porte de Saint-Malo de la rocade de Rennes 
 29       "/api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=31553";  
 30 
 31 
 32 const int MAX_RESPONSE_SIZE = 6000 ;              // Taille max de la réponse attendue d'un serveur. A modifier en fonction du besoin.
 33 char Data_Response[MAX_RESPONSE_SIZE] ;           // Buffer qui contiendra la réponse du serveur.
 34   
 35 #define TEN_SECONDS 10000                         // On appelera le serveur de données toutes les 10000 ms = 10 secondes.
 36 unsigned long myWakeUp ;                          // Timer mis en place pour limiter le nombre d'appels au serveur de données.
 37 
 38 /* --------------------------------------------------------------------------------------------------------------
 39  *  serverRequest() : Envoi requête HTTP au serveur et récupération de la réponse
 40  *  paramètres : 
 41  *    - pHost     : nom du serveur ; 
 42  *    - pPort     : port sur lequel est appelé le serveur ; 
 43  *    - pRequest  : requête au serveur.
 44  *    - pResponse : endroit où stocker la réponse
 45  *    - pRespMax  : nombre max de caractères autorisés pour la réponse
 46  *  valeur de retour : 
 47  *      -2 = réponse tronquée (trop de caractères) ;
 48 *       -1 = pas de réponse ;
 49          0 = pas de connexion au serveur ;
 50  *       1 = ok.
 51  *  ------------------------------------------------------------------------------------------------------------- */
 52 int serverRequest(char* pHost, int pPort, char* pRequest, char *pResponse, int pRespMax) {
 53     
 54     const int API_TIMEOUT = 15000;      // Pour être sûr de recevoir l'en-tête de la réponse client.
 55 
 56     // Comme la connexion est sécurisée (protocole HTTPS), il faudrait indiquer le certificat du site web.
 57     // Pour simplifier, on va utiliser l'option magique ".setInsecure()", ce qui n'est pas important dans 
 58     // notre exemple, où les données échangées ne sont pas confidentielles.
 59 
 60     myWiFiClient.setInsecure();
 61     myWiFiClient.setTimeout(API_TIMEOUT);
 62 
 63     // Connexion au serveur (on essaie 5 fois, avec un intervalle d'une seconde)
 64 
 65     Serial.print("--- Connexion au serveur [" + String(pHost) + "] "); 
 66     int nbTries = 1;
 67     while(!myWiFiClient.connect(pHost, pPort)) {
 68         Serial.print(".");
 69         if (++nbTries > 5) {
 70             Serial.println("--- Connexion impossible :-(");
 71             myWiFiClient.stop();
 72             return(0);
 73         }
 74         delay(1000);
 75     }   
 76 
 77     // Connecté à notre serveur ! --> Envoi de la requête URL. Il faut envoyer en fait une suite de lignes : 
 78     //        "GET <notre requête> HTTP/1.1"
 79     //        "Host: <nom du serveur>"
 80     //        "Connection: close"
 81     //        <ligne vide>
 82     // Cet envoi se fait simplement grâce à la fonction println du client WiFi, similaire à celle que 
 83     // l'on utilise pour envoyer des données au moniteur série pour nos traces.
 84 
 85     String myURL = String(pRequest);
 86     Serial.println() ; 
 87     Serial.println("--- Connexion OK ! --> Envoi requête URL - " + myURL);
 88     myWiFiClient.println("GET " + myURL + " HTTP/1.1") ;
 89     myWiFiClient.println("Host: " + String(pHost)) ;
 90     myWiFiClient.println("Connection: close") ;
 91     myWiFiClient.println() ;
 92     
 93     // Attente de la réponse ....(on essaie 50 fois, avec un intervalle de 100ms, donc 5 secondes en tout)
 94      
 95     nbTries = 1;
 96     while(!myWiFiClient.available()){
 97         if (++nbTries > 50) {
 98             Serial.println("--- Pas de réponse :-(");
 99             myWiFiClient.stop();
100             return(-1);
101         }
102         delay(100);
103     }
104 
105     // Récupération de l'en-tête de la réponse (dont on ne fera rien)
106     // Cette entête est une suite de caractères, composant un certain nombre de lignes (ie se terminant par '\n'), 
107     // la dernière ligne de l'entête n'est composée que du caractère "\r" (suivie du '\n') ;
108      
109     Serial.println("--- Réponse OK --> Récupération de l'en-tête ...");
110     String myLine ;
111     while (myWiFiClient.connected()) {
112         myLine = myWiFiClient.readStringUntil('\n');
113         if (myLine == "\r") {
114             break;
115         }
116     }
117 
118     // Entête reçue ! On va alors recopier dans pResponse tous les caractères qui suivent 
119     // en faisant attention à ne pas dépasser la taille du buffer.
120 
121     Serial.println("--- Entête ok --> Récupération des données ...");
122     int myIndex = 0 ;
123     while(myWiFiClient.available() && myWiFiClient.connected() ){
124 
125         char myResp = myWiFiClient.read();
126         pResponse[myIndex] = myResp;     
127         if (myIndex++ >= pRespMax) {
128              Serial.println("*** Réponse trop longue : " + String(pRespMax) + "caractères, et ne peut pas être traitée") ;
129              myWiFiClient.stop();
130              return(-2);
131         }
132         pResponse[myIndex] = '\0';            // Vu sur forums : conseillé d'ajouté 'fin de chaîne' systématiquement
133         delay(1) ;                            // Et également d'ajouter ce tout petit délai pour éviter des plantages.
134         
135     }
136 
137     // Tout s'est bien passé ! On arrête notre client WiFi
138 
139     Serial.println("--- Récupération des données ok (" + String(myIndex) + " caractères).") ;
140     myWiFiClient.stop();
141     return(1) ;
142 
143 }
144 
145 /* --------------------------------------------------------------------------------------------------------
146  *  showJSONAnswer : Décodage de la structure de données JSON
147  *  Paramètres :
148  *    - pResponse : endroit se trouve la réponse (au format JSON) du serveur
149  *    - pRespMax  : nombre max de caractères autorisés pour la réponse
150  * -------------------------------------------------------------------------------------------------------- */
151 void showJSONAnswer(char *pResponse, int pRespMax) {
152 
153     // Création de notre structure JSON
154     // Le besoin en mémoire (capacity) doit être vérifié sur l'assistant https://arduinojson.org/v6/assistant/
155     // 1) dans la première page de l'assistant, sélectionnez le processeur (par exemple "ESP8266"), le mode
156     //    "Deserialize", et le type d'entrée "char*", puis cliquez sur le bouton "Netx:JSON"
157     // 2) Lancez votre requête depuis un navigateur. Dans notre exemple, tapez dans la barre d'adresse :
158     //      "https://data.rennesmetropole.fr/api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=31553"
159     // 3) Recopiez la réponse obtenue - sous sa forme "Données Brutes" du navigateur vers l'assistant
160     // 4) L'assistant va alors préconiser le bon objet à créer (StaticJsonDocument ou DynamicJsonDocument),
161     //    ainsi que la taille à réserver. L'assistant va même proposer un exemple de programme exploitant toutes 
162     //    les informations de la structure JSON. 
163     // Pour notre exemple, l'assistant a proposé la définition qui suit.
164     
165     StaticJsonDocument<768> doc;
166 
167     // Décodage de la réponse JSON.
168     // La fonction deserializeJson va transformer la réponse "texte" du serveur, en une structure de données recopiée
169     // dans la variable 'doc', où il sera ensuite facile d'aller chercher les informations souhaitées.
170      
171     DeserializationError error = deserializeJson(doc, pResponse, pRespMax);
172     if (error) {
173         Serial.println("--- Décodage réponse JSON KO, code " + String(error.f_str())) ;
174         return;
175     }
176     Serial.println("--- Décodage réponse JSON OK !") ;
177 
178     // Nous pouvons maintenant extraire facilement les informations qui nous intéressent,
179     // en n'oubliant pas le niveau de profondeur de la donnée au sein de la structure JSON. 
180     // Ce niveau de profondeur est incrémenté par le nombre de '{' ou '[' rencontrés, et 
181     // décrémenté lors de la rencontre des ']' et {}'. Sur notre exemple 'rocade de Rennes',
182     // cela donne ceci :
183     //       +-----------------------------------------------------------------+
184     //       |   {                                                             | ... Entrée niveau 1
185     //       |      "nhits": 1,                                                |
186     //       |      "parameters": {                                            | ... Entrée niveau 2
187     //       |          "dataset": "etat-du-trafic-en-temps-reel",             |
188     //       |          (...)                                                  |
189     //       |      },                                                         | ... Retour niveau 1
190     //       |      "records": [                                               | ... Début d'un tableau : niveau 2
191     //       |          {                                                      | ... Entrée niveau 3
192     //       |              (...)                                              |                                                           |
193     //       |              "fields": {                                        | ... Entrée niveau 4
194     //       |                   (...)                                         |
195     //       |                   "averagevehiclespeed": 88,                    |
196     //       |                   (...)                                         |
197     //       |                   "datetime": "2022-11-30T11:57:00+01:00",      |
198     //       +-----------------------------------------------------------------+
199     // ... et donc :
200     //  - (1er niveau) --------- doc["nhits"] donnera la valeur 1,
201     //  - (2ème niveau) -------- doc["parameters"]["dataset"] donnera la valeur "etat-du-trafic-en-temps-reel"
202     //  - (4ème niveau) -------- doc["records"][0]["fields"]["averagevehiclespeed"] donnera la valeur 87
203 
204     // Extraction et affichage sur le port série de trois  valeurs
205 
206     String myLocRef = String(doc["records"][0]["fields"]["predefinedlocationreference"]) ;
207     String myTime = String(doc["records"][0]["fields"]["datetime"]) ;
208     int mySpeed    = doc["records"][0]["fields"]["averagevehiclespeed"] ;
209     
210     Serial.print("Vitesse au point " + myLocRef + " ") ;
211     Serial.print("le " +  myTime.substring(8,10) + "/" + myTime.substring(5,7) + "/" + myTime.substring(0,4) + " ") ;
212     Serial.print("à " +  myTime.substring(11,13) + "h" + myTime.substring(14,16) + " ") ;
213     Serial.println(" : " + String(mySpeed) + " km/h.") ; 
214 
215 }
216 
217 /* --------------------------------------------------------------------------------------------------------
218  *  SETUP : Initialisation
219  * -------------------------------------------------------------------------------------------------------- */
220 void setup() {
221 
222     // Initialisation de la liaison série, affichage 1er message
223 
224     Serial.begin(115200);
225     delay(100) ;
226     Serial.println(); 
227     Serial.println("-----------------------") ;
228     Serial.println("Exemple extraction JSON") ;
229     Serial.println("-----------------------") ;
230 
231     // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi  se connecter au dernier Point d'Accès connu,
232     // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
233     // Point d'Accès choisir. Par défaut, on restera bloqué tant que l'utilisateur n'aura pas fait de choix.
234     
235     Serial.println("Connexion au Wi-Fi ...");
236     if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
237         Serial.println(); Serial.print("Connecté ! Adresse IP : ");
238         Serial.println(WiFi.localIP());
239     }
240     else {
241         Serial.println("Connexion Wi-Fi KO :-(");     
242     }
243 
244     // Initialisation du timer qui sera testé dans loop() - pour faire appel au serveur seulement toutes les 10 secondes 
245     // millis() est une fonction système donnant le nombre de ms depuis le lancement ou la réinitialisation de la carte.
246 
247     unsigned long myWakeUp = millis() + TEN_SECONDS ;
248 
249 }
250 
251 /* --------------------------------------------------------------------------------------------------------------
252  *  LOOP : fonction appelée régulièrement par le système
253  *  ------------------------------------------------------------------------------------------------------------- */
254 void loop() { 
255 
256     unsigned long myNow = millis() ;
257     if (myNow >= myWakeUp) {
258         Serial.println("Wake Up ! Nouvelle demande au serveur ...") ;
259         if (serverRequest(Data_HOST, Data_PORT, Data_REQUEST, Data_Response, MAX_RESPONSE_SIZE) == 1) {
260             Serial.println("Réponse reçue du serveur, lancement analyse JSON ...") ;
261             showJSONAnswer(Data_Response, MAX_RESPONSE_SIZE) ;
262         }
263         myWakeUp = myNow + TEN_SECONDS ;         
264     }
265 
266 }




Étape 6 - Diffusion d'information (mode MQTT)

MQTT c'est quoi ?


MQTT (Message Queuing Telemetry Transport) permet l'envoi et la réception de messages de petite taille. MQTT s'appuie sur un "broker MQTT", serveur externe, qui va recevoir les données d'un système, et les redistribuer à d'autres systèmes.


MQTT est souvent utilisé pour collecter des données en provenance de petits capteurs (par exemple, capteurs de température dans un système domotique, capteurs de pollution au niveau d'une région voire d'un pays), car il a aussi comme avantage d'être peu consommateur de ressources.


MQTT est basé sur un principe d'abonnement : le système émetteur doit préciser à quel sujet ("topic") se rattache son message, et tous les systèmes qui s'étaient préalablement abonnés à ce "topic" recevront alors le message. Principe proche de Twitter ou Instagram et leurs "hashtags", donc.


On peut implémenter son propre broker MQTT (le code est libre d'usage), ou s'appuyer sur des brokers gérés par des associations ou des entreprises. Dans l'exemple ci-après, on utilise le broker des Petits Débrouillards, à utiliser avec modération.

Mais ce n'est pas l'objet du tutoriel, nous nous intéressons ici uniquement à la partie "client", c'est à dire ce qu'il faut mettre en œuvre sur nos cartes D1 mini ou ESP.


Mise en œuvre sur nos petites cartes :


Il existe plusieurs bibliothèques Arduino permettent de gérer des messages MQTT. Pour notre part, on utilise celle-ci (à aller chercher dans le gestionnaire de bibliothèque) :


Bibliothèque MQTT



Gestion du MQTT
Avant le Setup Importation de la bibliothèque #include <MQTT.h>
Création de l’objet MQTTClient myMQTTClient;
Dans le Setup (ou le loop) Initialisation myMQTTClient.begin(@IP Broker, Port Broker, Client Wifi) ;
Se préparer à la réception de messages myMQTTClient.onMessage(référence de la fonction à appeler sur réception d'un message) ;
Connexion au broker MQTT myMQTTClient.connect(ID unique)
Souscrire à un "topic" particulier myMQTTClient.subscribe(topic) ;
Publier un message myMQTTClient.publish(topic, message) ;
Dans le Loop Activation régulière obligatoire : myMQTTClient.loop() ;

Pour connaître toutes les autres possibilités de cette bibliothèque, voir sa référence, ici.

Code minimal :


Dans cet exemple, notre carte est à la fois émettrice (elle va envoyer des "Pings") et réceptrice sur le topic "PING_PONG". Elle va aussi répondre "Pong" sur réception d'un "Ping". En règle générale, dans les vraies applications, une carte est souvent émettrice (envoi de données d'un capteur de pollution, par exemple), et il peut y avoir une seule autre carte chargée d'exploiter les données remontées par plusieurs capteurs.
  1 /* =========================================================================================================
  2  * 
  3  *                              CODE MINIMAL RESEAU - ETAPE 6 : Messages MQTT
  4  *          
  5  * ---------------------------------------------------------------------------------------------------------
  6  * Les petits Débrouillards - décembre 2022 - CC-By-Sa http://creativecommons.org/licenses/by-nc-sa/3.0/
  7  * ========================================================================================================= */
  8 
  9 // Bibliothèques requises
 10 // ATTENTION AUX MAJUSCULES & MINUSCULES ! Sinon d'autres bibliothèques, plus ou moins valides, seraient utilisées.
 11 
 12 #include <WiFiManager.h>                          // Gestion de la connexion Wi-Fi (recherche de points d'accès)  
 13 #include <WiFiClientSecure.h>                     // Gestion de la connexion à un serveur de données
 14 #include <MQTT.h>                                 // Gestion des requêtes MQTT
 15 
 16 
 17 // Variables globales
 18 
 19 WiFiManager myWiFiManager;                        // Manager Wi-Fi
 20 WiFiClient myWiFiClient ;                         // Client WiFi
 21 const char* mySSID   = "AP_PetitDeb" ;            // Nom de la carte en mode Point d'Accès.
 22 const char* mySecKey = "PSWD1234" ;               // Mot de passe associé, 8 caractères au minimum.
 23 
 24 
 25 #define MQTT_BROKER_IP "debrouillards.ddns.net"   // Serveur sur lequel est installé le Broker MQTT.
 26 #define MQTT_BROKER_PORT 1883                     // Port sur lequel écoute le broker MQTT
 27 
 28 char MY_MQTT_ID[20] ;                             // Id unique de notre objet, basé sur ESP.getChipId()
 29 const char MY_MQTT_TOPIC[] = "PING_PONG" ;        // Nom de notre topic (sujet) MQTT
 30 
 31 MQTTClient myMQTTClient;                          // Client MQTT
 32 
 33 bool IHaveToAnswer = false ;                      // Passe à 'true' si on doit répondre.
 34 
 35 #define TEN_SECONDS 10000                         // On enverra un message au broker toutes les 10000 ms = 10 secondes.
 36 unsigned long myWakeUp ;                          // Timer mis en place pour limiter le nombre d'envois au broker.
 37 
 38 /* --------------------------------------------------------------------------------------------------------------
 39  * MQTT_Received : réception d'un message MQTT
 40  * ------------------------------------------------------------------------------------------------------------- */
 41 void MQTT_Received(String &topic, String &payload) {
 42 
 43     char myTrace[80] ;
 44 
 45     sprintf(myTrace, "Réception sur le topic \"%s\" du message MQTT \"%s\".", topic, payload) ;
 46     Serial.println(myTrace) ;
 47 
 48     // Comme indiqué dans la documentation, il ne faut pas renvoyer de messages dans cette fonction,
 49     // ça risque de mal se passer (blocages, ...). Si on souhaite répondre au message reçu, il vaut
 50     // mieux mettre à jour une variable globale, qui sera vérifiée dans la partie loop().
 51 
 52     if (payload == String("Ping")) {
 53         IHaveToAnswer = true ;
 54     }
 55   
 56 }
 57 
 58 /* --------------------------------------------------------------------------------------------------------------
 59  *  MQTT_Connect : Connexion - ou reconnexion - au broker MQTT.
 60  * ------------------------------------------------------------------------------------------------------------- */
 61 void MQTT_Connect() {
 62 
 63     // Vérification WiFi OK.
 64     
 65     int nbTries = 0 ;
 66     while (WiFi.status() != WL_CONNECTED) {
 67         if (nbTries++ > 10) {
 68             Serial.println("Connexion WiFi KO :-(") ;
 69             return ;
 70         }
 71         delay(500);
 72     }
 73 
 74     // Connexion au broker.
 75     
 76     Serial.println("--- Connexion MQTT, Id unique \"" + String(MY_MQTT_ID) + "\" ") ;
 77     nbTries = 0 ;
 78     while (!myMQTTClient.connect(MY_MQTT_ID)) {                                       
 79         Serial.print(".") ; 
 80         if (nbTries++ > 10) {
 81             Serial.println(" KO :-(") ;
 82             return ;
 83         }
 84         delay(500);
 85     }
 86 
 87     // Abonnement au topic.
 88     
 89     Serial.println("--- Abonnement au sujet \"" + String(MY_MQTT_TOPIC) + "\"") ;   
 90     myMQTTClient.subscribe(MY_MQTT_TOPIC);  
 91     
 92 }
 93 /* --------------------------------------------------------------------------------------------------------
 94  *  SETUP : Initialisation
 95  * -------------------------------------------------------------------------------------------------------- */
 96 void setup() {
 97 
 98     // Initialisation de la liaison série, affichage 1er message
 99 
100     Serial.begin(115200);
101     delay(100) ;
102     Serial.println(); 
103     Serial.println("---------------------") ;
104     Serial.println("Exemple messages MQTT") ;
105     Serial.println("---------------------") ;
106 
107     // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi  se connecter au dernier Point d'Accès connu,
108     // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
109     // Point d'Accès choisir. Par défaut, on restera bloqué tant que l'utilisateur n'aura pas fait de choix.
110     
111     Serial.println("Connexion au Wi-Fi ...");
112     if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
113         Serial.println(); Serial.print("Connecté ! Adresse IP : ");
114         Serial.println(WiFi.localIP());
115     }
116     else {
117         Serial.println("Connexion Wi-Fi KO :-(");     
118     }
119 
120     // Initialisation du MQTT
121 
122     Serial.println("Initialisation MQTT ...");
123     myMQTTClient.begin(MQTT_BROKER_IP, MQTT_BROKER_PORT, myWiFiClient);               // lancement du client MQTT ...
124     myMQTTClient.onMessage(MQTT_Received);                                            // ... qui appelera la fonction MQTT_Received si un message est reçu.
125 
126     strncpy(MY_MQTT_ID, String(ESP.getChipId()).c_str(),sizeof(MY_MQTT_ID)) ;         // Fabrication de mon ID unique, sur la base du n° de puce
127     MY_MQTT_ID[sizeof(MY_MQTT_ID)-1] = '\0' ;
128 
129     // Connexion au broker 
130 
131     MQTT_Connect() ;
132     
133     // Initialisation du timer qui sera testé dans loop() - pour faire appel au serveur seulement toutes les 10 secondes 
134     // millis() est une fonction système donnant le nombre de ms depuis le lancement ou la réinitialisation de la carte.
135 
136     unsigned long myWakeUp = millis() + TEN_SECONDS ;
137 
138 }
139 
140 /* --------------------------------------------------------------------------------------------------------------
141  *  LOOP : fonction appelée régulièrement par le système
142  *  ------------------------------------------------------------------------------------------------------------- */
143 void loop() { 
144 
145     // Réactivation du client MQTT
146 
147     myMQTTClient.loop() ;
148     delay(10) ;             // Problèmes de stabilité Wi-Fi ? (Cf. doc MQTT)
149 
150     // Reconnexion au broker MQTT si nécessaire ...
151 
152     if (!myMQTTClient.connected()) {
153         MQTT_Connect();
154     }
155 
156     // Si on a précédemment reçu un 'Ping', on va répondre 'Pong'
157 
158     if (IHaveToAnswer) {
159         IHaveToAnswer = false ;
160         Serial.println("Envoi de la réponse \"Pong\" sur le topic \"" + String(MY_MQTT_TOPIC) + "\".") ;
161         myMQTTClient.publish(MY_MQTT_TOPIC, "Pong") ;
162     }
163 
164     // Envoi d'un message toutes les 10 secondew.
165    
166     unsigned long myNow = millis() ;
167     if (myNow >= myWakeUp) {
168         Serial.println("Wake Up ! envoi du message \"Ping\" sur le topic \"" + String(MY_MQTT_TOPIC) + "\".") ;
169         myMQTTClient.publish(MY_MQTT_TOPIC, "Ping") ;
170         myWakeUp = myNow + TEN_SECONDS ;         
171     }
172 
173 
174 }


Dernière modification 6/02/2023 par user:Philby.

Commentaires

Draft