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 // Gestion de la connexion Wi-Fi (recherche de points d'accès)
13 #include // Gestion de la connexion (HTTP) à un serveur de données
14 #include // 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 HTTP/1.1"
79 // "Host: "
80 // "Connection: close"
81 //
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 }
Vous avez entré un nom de page invalide, avec un ou plusieurs caractères suivants :
< > @ ~ : * € £ ` + = / \ | [ ] { } ; ? #