(Une révision intermédiaire par le même utilisateur non affichée) | |||
Ligne 137 : | Ligne 137 : | ||
* valeur de retour : | * valeur de retour : | ||
* -2 = réponse tronquée (trop de caractères) ; | * -2 = réponse tronquée (trop de caractères) ; | ||
− | * | + | * -1 = pas de réponse ; |
− | + | * 0 = pas de connexion au serveur ; | |
* 1 = ok. | * 1 = ok. | ||
* ------------------------------------------------------------------------------------------------------------- */ | * ------------------------------------------------------------------------------------------------------------- */ | ||
Ligne 200 : | Ligne 200 : | ||
Serial.println("--- Réponse OK --> Récupération de l'en-tête ..."); | Serial.println("--- Réponse OK --> Récupération de l'en-tête ..."); | ||
String myLine ; | String myLine ; | ||
− | while (myWiFiClient. | + | while (myWiFiClient.available()) { |
myLine = myWiFiClient.readStringUntil('\n'); | myLine = myWiFiClient.readStringUntil('\n'); | ||
if (myLine == "\r") { | if (myLine == "\r") { | ||
Ligne 212 : | Ligne 212 : | ||
Serial.println("--- Entête ok --> Récupération des données ..."); | Serial.println("--- Entête ok --> Récupération des données ..."); | ||
int myIndex = 0 ; | int myIndex = 0 ; | ||
− | while(myWiFiClient.available | + | while (myWiFiClient.available()) { |
char myResp = myWiFiClient.read(); | char myResp = myWiFiClient.read(); | ||
+ | /* Debug supprimé ... Serial.println(myResp) ; */ | ||
pResponse[myIndex] = myResp; | pResponse[myIndex] = myResp; | ||
if (myIndex++ >= pRespMax) { | if (myIndex++ >= pRespMax) { | ||
Ligne 221 : | Ligne 222 : | ||
return(-2); | return(-2); | ||
} | } | ||
− | pResponse[myIndex] = '\0'; | + | pResponse[myIndex] = '\0'; // Vu sur forums : conseillé d'ajouté 'fin de chaîne' systématiquement |
− | delay(1) ; | + | delay(1) ; // Et également d'ajouter ce tout petit délai pour éviter des plantages. |
} | } | ||
Ligne 254 : | Ligne 255 : | ||
// Pour notre exemple, l'assistant a proposé la définition qui suit. | // Pour notre exemple, l'assistant a proposé la définition qui suit. | ||
− | StaticJsonDocument< | + | StaticJsonDocument<1024> doc; |
// Décodage de la réponse JSON. | // Décodage de la réponse JSON. | ||
Ligne 270 : | Ligne 271 : | ||
// en n'oubliant pas le niveau de profondeur de la donnée au sein de la structure JSON. | // 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 | // Ce niveau de profondeur est incrémenté par le nombre de '{' ou '[' rencontrés, et | ||
− | // décrémenté lors de la rencontre des ']' et | + | // décrémenté lors de la rencontre des ']' et {}'. Sur notre exemple 'rocade de Rennes', |
// cela donne ceci : | // cela donne ceci : | ||
// +-----------------------------------------------------------------+ | // +-----------------------------------------------------------------+ | ||
Ligne 291 : | Ligne 292 : | ||
// - (1er niveau) --------- doc["nhits"] donnera la valeur 1, | // - (1er niveau) --------- doc["nhits"] donnera la valeur 1, | ||
// - (2ème niveau) -------- doc["parameters"]["dataset"] donnera la valeur "etat-du-trafic-en-temps-reel" | // - (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 | + | // - (4ème niveau) -------- doc["records"][0]["fields"]["averagevehiclespeed"] donnera la valeur 87 |
// Extraction et affichage sur le port série de trois valeurs | // Extraction et affichage sur le port série de trois valeurs | ||
Ligne 356 : | Ligne 357 : | ||
} | } | ||
+ | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Auteur Philippe Blusseau | Dernière modification 7/09/2023 par Philby
Code_Minimal_R_seau_-__3__Mon_D1_Mini_r_cup_re_des_donn_es_sur_Internet__Json__Le_D1_Mini_R_cup_re_des_Donn_es.png
Cette expérience fait partie d'une série de 4 épisodes, présentant différentes façons de bénéficier des capacités de communication des cartes compatibles Arduino possédant une puce Wi-Fi (Wemos D1 mini, ESP32, ...). 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. Ces 4 épisodes sont les suivants :
Il est nécessaire de commencer par l'épisode 1, par contre les épisodes suivants peuvent être consultés dans n'importe quel ordre.
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 :
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.
... bon, ok, mais mon D1 mini n'a pas de navigateur ?
C'est là où deux bibliothèques vont nous être utiles :
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.
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" :
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.available()) {
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()) {
124
125 char myResp = myWiFiClient.read();
126 /* Debug supprimé ... Serial.println(myResp) ; */
127 pResponse[myIndex] = myResp;
128 if (myIndex++ >= pRespMax) {
129 Serial.println("*** Réponse trop longue : " + String(pRespMax) + "caractères, et ne peut pas être traitée") ;
130 myWiFiClient.stop();
131 return(-2);
132 }
133 pResponse[myIndex] = '\0'; // Vu sur forums : conseillé d'ajouté 'fin de chaîne' systématiquement
134 delay(1) ; // Et également d'ajouter ce tout petit délai pour éviter des plantages.
135
136 }
137
138 // Tout s'est bien passé ! On arrête notre client WiFi
139
140 Serial.println("--- Récupération des données ok (" + String(myIndex) + " caractères).") ;
141 myWiFiClient.stop();
142 return(1) ;
143
144 }
145
146 /* --------------------------------------------------------------------------------------------------------
147 * showJSONAnswer : Décodage de la structure de données JSON
148 * Paramètres :
149 * - pResponse : endroit se trouve la réponse (au format JSON) du serveur
150 * - pRespMax : nombre max de caractères autorisés pour la réponse
151 * -------------------------------------------------------------------------------------------------------- */
152 void showJSONAnswer(char *pResponse, int pRespMax) {
153
154 // Création de notre structure JSON
155 // Le besoin en mémoire (capacity) doit être vérifié sur l'assistant https://arduinojson.org/v6/assistant/
156 // 1) dans la première page de l'assistant, sélectionnez le processeur (par exemple "ESP8266"), le mode
157 // "Deserialize", et le type d'entrée "char*", puis cliquez sur le bouton "Netx:JSON"
158 // 2) Lancez votre requête depuis un navigateur. Dans notre exemple, tapez dans la barre d'adresse :
159 // "https://data.rennesmetropole.fr/api/records/1.0/search/?dataset=etat-du-trafic-en-temps-reel&q=31553"
160 // 3) Recopiez la réponse obtenue - sous sa forme "Données Brutes" du navigateur vers l'assistant
161 // 4) L'assistant va alors préconiser le bon objet à créer (StaticJsonDocument ou DynamicJsonDocument),
162 // ainsi que la taille à réserver. L'assistant va même proposer un exemple de programme exploitant toutes
163 // les informations de la structure JSON.
164 // Pour notre exemple, l'assistant a proposé la définition qui suit.
165
166 StaticJsonDocument<1024> doc;
167
168 // Décodage de la réponse JSON.
169 // La fonction deserializeJson va transformer la réponse "texte" du serveur, en une structure de données recopiée
170 // dans la variable 'doc', où il sera ensuite facile d'aller chercher les informations souhaitées.
171
172 DeserializationError error = deserializeJson(doc, pResponse, pRespMax);
173 if (error) {
174 Serial.println("--- Décodage réponse JSON KO, code " + String(error.f_str())) ;
175 return;
176 }
177 Serial.println("--- Décodage réponse JSON OK !") ;
178
179 // Nous pouvons maintenant extraire facilement les informations qui nous intéressent,
180 // en n'oubliant pas le niveau de profondeur de la donnée au sein de la structure JSON.
181 // Ce niveau de profondeur est incrémenté par le nombre de '{' ou '[' rencontrés, et
182 // décrémenté lors de la rencontre des ']' et {}'. Sur notre exemple 'rocade de Rennes',
183 // cela donne ceci :
184 // +-----------------------------------------------------------------+
185 // | { | ... Entrée niveau 1
186 // | "nhits": 1, |
187 // | "parameters": { | ... Entrée niveau 2
188 // | "dataset": "etat-du-trafic-en-temps-reel", |
189 // | (...) |
190 // | }, | ... Retour niveau 1
191 // | "records": [ | ... Début d'un tableau : niveau 2
192 // | { | ... Entrée niveau 3
193 // | (...) | |
194 // | "fields": { | ... Entrée niveau 4
195 // | (...) |
196 // | "averagevehiclespeed": 88, |
197 // | (...) |
198 // | "datetime": "2022-11-30T11:57:00+01:00", |
199 // +-----------------------------------------------------------------+
200 // ... et donc :
201 // - (1er niveau) --------- doc["nhits"] donnera la valeur 1,
202 // - (2ème niveau) -------- doc["parameters"]["dataset"] donnera la valeur "etat-du-trafic-en-temps-reel"
203 // - (4ème niveau) -------- doc["records"][0]["fields"]["averagevehiclespeed"] donnera la valeur 87
204
205 // Extraction et affichage sur le port série de trois valeurs
206
207 String myLocRef = String(doc["records"][0]["fields"]["predefinedlocationreference"]) ;
208 String myTime = String(doc["records"][0]["fields"]["datetime"]) ;
209 int mySpeed = doc["records"][0]["fields"]["averagevehiclespeed"] ;
210
211 Serial.print("Vitesse au point " + myLocRef + " ") ;
212 Serial.print("le " + myTime.substring(8,10) + "/" + myTime.substring(5,7) + "/" + myTime.substring(0,4) + " ") ;
213 Serial.print("à " + myTime.substring(11,13) + "h" + myTime.substring(14,16) + " ") ;
214 Serial.println(" : " + String(mySpeed) + " km/h.") ;
215
216 }
217
218 /* --------------------------------------------------------------------------------------------------------
219 * SETUP : Initialisation
220 * -------------------------------------------------------------------------------------------------------- */
221 void setup() {
222
223 // Initialisation de la liaison série, affichage 1er message
224
225 Serial.begin(115200);
226 delay(100) ;
227 Serial.println();
228 Serial.println("-----------------------") ;
229 Serial.println("Exemple extraction JSON") ;
230 Serial.println("-----------------------") ;
231
232 // Tentative de connexion au Wi-Fi. Si la carte n'a pas réussi se connecter au dernier Point d'Accès connu,
233 // alors elle va se positionner en mode Point d'Accès, demandera sur l'adresse 192.168.4.1 quel nouveau
234 // Point d'Accès choisir. Par défaut, on restera bloqué tant que l'utilisateur n'aura pas fait de choix.
235
236 Serial.println("Connexion au Wi-Fi ...");
237 if (myWiFiManager.autoConnect(mySSID, mySecKey)) {
238 Serial.println(); Serial.print("Connecté ! Adresse IP : ");
239 Serial.println(WiFi.localIP());
240 }
241 else {
242 Serial.println("Connexion Wi-Fi KO :-(");
243 }
244
245 // Initialisation du timer qui sera testé dans loop() - pour faire appel au serveur seulement toutes les 10 secondes
246 // millis() est une fonction système donnant le nombre de ms depuis le lancement ou la réinitialisation de la carte.
247
248 unsigned long myWakeUp = millis() + TEN_SECONDS ;
249
250 }
251
252 /* --------------------------------------------------------------------------------------------------------------
253 * LOOP : fonction appelée régulièrement par le système
254 * ------------------------------------------------------------------------------------------------------------- */
255 void loop() {
256
257 unsigned long myNow = millis() ;
258 if (myNow >= myWakeUp) {
259 Serial.println("Wake Up ! Nouvelle demande au serveur ...") ;
260 if (serverRequest(Data_HOST, Data_PORT, Data_REQUEST, Data_Response, MAX_RESPONSE_SIZE) == 1) {
261 Serial.println("Réponse reçue du serveur, lancement analyse JSON ...") ;
262 showJSONAnswer(Data_Response, MAX_RESPONSE_SIZE) ;
263 }
264 myWakeUp = myNow + TEN_SECONDS ;
265 }
266
267 }
Dernière modification 7/09/2023 par user:Philby.
Published
Vous avez entré un nom de page invalide, avec un ou plusieurs caractères suivants :
< > @ ~ : * € £ ` + = / \ | [ ] { } ; ? #