POCL Parapluie connecté
(Redirigé depuis Parapluie connecté)
inspiré du travail de julien Levesque et d'Aurélien Fache voici un petit objet rigolo.
Connecté à la météo d'une ville choisie sur le site openweathermap, le parapluie et ouvert s'il pleut et il est fermé s'il ne pleut pas.
Comme la météo ne change pas souvent, il possède aussi une interface web permettant de l'activer à volonté !
Photographies du prototype n°1 :
Sommaire
Matériel
- Un D1 mini
- Un servomoteur
- Un parapluie Cocktail
pour bricoler :
- du fil de fer,
- un bouchon de liège,
- un pistolet à colle,
- du carton
A améliorer
- Modifier le code pour :
- choisir son réseau wifi
- choisir la ville
- meilleur design web
- créer une boitier à imprimer en 3D (à la place du carton)
code
1 ///////////////
2 // PARAPLUIE //
3 ///////////////
4 /*
5 * A FAIRE :
6 * installer wifiManager pour simplifier la connexion au wifi.
7 * inclure dans une page web un formulaire pour saisir le code la ville - https://randomnerdtutorials.com/esp32-esp8266-input-data-html-form/ et
8 * https://circuits4you.com/2019/03/20/esp8266-receive-post-get-request-data-from-website/
9 * enregistrer le code de la ville dans l'EEPROM - https://circuits4you.com/2016/12/16/esp8266-internal-eeprom-arduino/
10 */
11 /* Un programme pour récupérer les données météo du site http://openweathermap.org/
12 * puis les afficher sur le moniteur série (partie de programme inspiré de http://educ8s.tv/esp8266-weather-display/)
13 * et ouvre ou ferme un parapluie chinois suivant les précipitations d'un lieu choisis (inspiré de http://julienlevesque.net/little-umbrella/ )
14 * Cette version intègre une page web de test du parapluie en hackant des bout de codes trouvés ici : https://www.ulasdikme.com/projects/esp8266/esp8266_ajax_websocket.php
15 * Antony Le Goïc-Auffret
16 * sous licence CC-By-SA - dimanche 1er août 2020 - Brest- Bretagne - France - Europe - Terre - Système solaire - Voie Lactée.
17
18 _
19 _| |_
20 _/ / \ \_
21 _/ / \ \_
22 _/ / \ \_
23 _/ _._ / _._ \ _._ \_
24 / \_/ \_/ |_|\_/ \_/ \
25 /|#|
26 | | |
27 | | |
28 | | |
29 | | |
30 | | |
31 | | |
32 ____|_\_/_
33 | | |
34 BROCHAGE | | |
35 _________________ ______|____|_____|__________
36 / D1 mini \ | | |
37 Non Attribué - |[ ]RST TX[ ]| - Non Attribué | ___ | |
38 Non Attribué - |[ ]A0 -GPIO RX[ ]| - Non Attribué | |_°_| | |
39 Non Attribué - |[ ]D0-16 5-D1[ ]| - Non Attribué | | | | |
40 Non Attribué - |[ ]D5-14 4-D2[ ]| - Non Attribué | | | | |
41 Non Attribué - |[ ]D6-12 0-D3[X]| - gestion servo < _ | | __|___| |
42 Non Attribué - |[ ]D7-13 2-D4[X]| - LED_BUILTIN \ | | (o)_____/ |
43 Non Attribué - |[ ]D8-15 GND[X]| - alim servo <- || |Servo| |
44 Non Attribué - |[ ]3V3 . 5V[X]| - alim servo <_|_ || |_____| |
45 | +---+ | | \ \ |_°_| |
46 |_______|USB|_______| \ \ |\__/|| |
47 Le D1 mini est connecté en USB à votre ordinateur, \ \|___/ | |
48 le moniteur série ouvert \_|____/ |
49 |___________________________|
50
51 */
52 #include <ESP8266WiFi.h>
53 #include <ArduinoJson.h>
54 #include <Servo.h>
55 #include <ESP8266WebServer.h>
56
57 ESP8266WebServer server(80); //declaration du serveur web sur le port 80
58
59 const char* nomDuReseau = "xxxxxxxxx"; // Nom du réseau wifi local (Ce qu'on appelle le SSID).
60 const char* motDePasse = "yyyyyyyyy"; // mot de passe du réseau wifi local
61 String cledAPI = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; // clé de l'API du site http://openweathermap.org/
62 // il faudra vous créer un compte et récupérer votre clé d'API, une formalité !
63 // Sur le site, vous trouvez les identifiants de toutes les villes du monde.
64 String identifiantVille = "6427109"; //indiquez l'identifiant d'une ville (CityID), ici Brest en France
65 /*Quelques identifiants d'autres villes françaises :
66 * 3030300, Brest
67 * 6431033, Quimper
68 * 6430976, Morlaix
69 * 6453798, Lannion
70 * 6453805, Saint-Brieuc
71 * 6432801, Rennes
72 * 6437298, Lorient
73 * 2970777, Vannes
74 * 6434483, Nantes
75 * 6456407, Le Mans
76 * 6427109, Caen
77 * 6452361, Angers
78 * 6456577, La Roche sur Yon
79 * 3021411, Dieppe
80 */
81
82 WiFiClient client;
83 char nomDuServeur[] = "api.openweathermap.org"; // Serveur distant auquel on va se connecter
84 String resultat;
85
86 int webClic = 0;
87 unsigned long dateDernierChangement = 0;
88 unsigned long dateCourante;
89 unsigned long intervalle;
90
91 Servo monservo; // créer un objet "monservo" pour le contrôler
92
93 String descriptionMeteo = "";
94 String lieuMeteo = "";
95 String Pays;
96 float Temperature;
97 float Humidite;
98 float Pression;
99 int para = 100;
100 int parastock;
101 int ferme = 90; // angle pour fermer le parapluie
102 int ouvre = 170; // angle pour ouvrir le parapluie
103 bool pluie = 1; // enregistre si il pleut ou pas.
104
105 String webSite,javaScript,XML; //déclaration de variables
106 int start=0; // variable start
107
108 void buildWebsite(){ // Fonction qui écrit le code html du site web
109 buildJavascript(); // appel de la fonction qui contruit le code javascript
110 webSite="<!DOCTYPE HTML>\n";
111 webSite+=javaScript; // insertion du javascript dans la page
112 webSite+="<HTML>\n";
113 webSite+="<style>\n";
114 webSite+="#button {\n";
115 webSite+="background-color: #E6E6FA;\n";
116 webSite+="border: none;\n";
117 webSite+="color: white;\n";
118 webSite+="padding: 32px;\n";
119 webSite+=" text-align: center;\n";
120 webSite+=" text-decoration: none;\n";
121 webSite+="display: inline-block;\n";
122 webSite+="font-size: 168px;\n";
123 webSite+="display:block;\n";
124 webSite+="margin:0 auto;\n";
125 webSite+="margin-top:130px;\n";
126 webSite+="cursor: pointer;\n";
127 webSite+="width:524px;\n";
128 webSite+="height:400px;\n";
129 webSite+="}\n";
130
131 webSite+="p.thicker{font-weight:900;}\n";
132 webSite+="#runtime{font-weight:900; font-size: 147%; color:RED;}\n";
133 webSite+="</style>\n";
134 webSite+="<BODY bgcolor='#E6E6FA' onload='process()'>\n";
135
136 webSite+="<button onClick='RunButtonWasClicked()' id='button'></button> ";
137 webSite+="</BODY>\n";
138 webSite+="</HTML>\n";
139 }
140
141 void buildJavascript(){ // Fonction qui contruit le code javascript
142 javaScript="<SCRIPT>\n";
143
144 javaScript+="var xmlHttp=createXmlHttpObject();\n";
145 javaScript+="function createXmlHttpObject(){\n";
146 javaScript+=" if(window.XMLHttpRequest){\n";
147 javaScript+=" xmlHttp=new XMLHttpRequest();\n";
148 javaScript+=" }else{\n";
149 javaScript+=" xmlHttp=new ActiveXObject('Microsoft.XMLHTTP');\n";
150 javaScript+=" }\n";
151 javaScript+=" return xmlHttp;\n";
152 javaScript+="}\n";
153
154 javaScript+="var click;\n";
155
156 javaScript+="function handleServerResponse(){\n";
157 javaScript+=" xmlResponse=xmlHttp.responseXML;\n";
158 javaScript+=" xmldoc = xmlResponse.getElementsByTagName('response');\n";
159 javaScript+=" message = xmldoc[0].firstChild.nodeValue;\n";
160 javaScript+="if(message == 1){click = 1; message = 'ouvert'; document.getElementById('button').style.background='#FFA200';}else{click=0; message='fermé'; document.getElementById('button').style.background='#111111';}\n";
161 javaScript+=" document.getElementById('button').innerHTML=message;\n";
162 javaScript+="}\n";
163
164 javaScript+="function process(){\n";
165 javaScript+=" xmlHttp.open('PUT','xml',true);\n";
166 javaScript+=" xmlHttp.onreadystatechange=handleServerResponse;\n"; // no brackets?????
167 javaScript+=" xmlHttp.send(null);\n";
168 javaScript+=" setTimeout('process()',200);\n";
169 javaScript+="}\n";
170
171 javaScript+="function process2(){\n";
172 javaScript+=" xmlHttp.open('SET','set1ESPval?Start='+click,true);\n";
173 javaScript+=" xmlHttp.send(null);\n";
174 javaScript+=" setTimeout('process2()',400);\n";
175 javaScript+="}\n";
176
177 javaScript+="function RunButtonWasClicked(){\n";
178 javaScript+="click = (click==1)?0:1;\n";
179 javaScript+=" xmlHttp.open('SET','set1ESPval?Start='+click,true);\n";
180 javaScript+=" xmlHttp.send(null);\n";
181 javaScript+="}\n";
182
183 javaScript+="</SCRIPT>\n";
184 }
185
186 uint16_t x;
187 String data; // variable data qui sert à ?
188
189 void buildXML(){
190 XML="<?xml version='1.0'?>";
191 XML+="<response>";
192 XML+=data;
193 XML+="</response>";
194 }
195
196
197 void handleWebsite(){ // génère le site web
198 buildWebsite(); // écriture du html
199 server.send(200,"text/html",webSite); // mise en ligne du site
200 }
201
202 void handleXML(){ // gère le xml (description de l'état du boutton)
203 buildXML();
204 server.send(200,"text/xml",XML);
205 }
206
207 void handle1ESPval(){
208 start = server.arg("Start").toFloat();
209 }
210
211 int start2=0;
212 int inc=0;
213
214 void setup() {
215 Serial.begin(9600);
216 Serial.println();
217 connexion();
218 delay(100);
219 server.on("/",handleWebsite);
220 server.on("/xml",handleXML);
221 server.on("/set1ESPval",handle1ESPval);
222
223 server.begin();
224 prendDonneesMeteo();
225 parapluie ();
226 }
227
228 void loop() {
229 dateCourante = millis();
230 intervalle = dateCourante - dateDernierChangement; // interval de temps depuis la dernière mise à jour du parapluie
231
232 if (intervalle >= 600000) //Récupère de nouvelles données toutes les 10 minutes
233 {
234 dateDernierChangement = millis();
235 prendDonneesMeteo(); // récupère les données météo
236 parapluie(); // met à jour le parapluie
237 //monservo.detach(); // débrancher le servomoteur de la broche D3 (GPIO 0)
238 }
239
240 if (!start == webClic) // si l'état du bouton web à changé
241 {
242 parastock = para; // stock la valeur "para" dans "parastock"
243 if (start)
244 {
245 para = 100; // triche sur la valeur "para" pour un test pluie
246 parapluie(); // met à jour le parapluie
247 Serial.println("parapluie fermé ");
248 }
249 if (!start)
250 {
251 para = 900; // triche sur la valeur "para" pour un test pluie
252 parapluie(); // met à jour le parapluie
253 Serial.println("parapluie ouvert ");
254 }
255 webClic = start; //met à jour webClic
256 para = parastock; // redonne à para sa valeur initiale
257 }
258
259 data =(String)start;
260 server.handleClient();
261
262 if ((intervalle%6000) == 0){ // toutes les 6 secondes, j'écris ces infos sur le moniteur série
263 ecritMeteoGeneral(lieuMeteo, descriptionMeteo);
264 Serial.print("interval modulo 6000 : "); Serial.println((intervalle%60));
265 Serial.print("interval : "); Serial.println(intervalle);
266 Serial.print("date Courante : ");Serial.println(dateCourante);
267 Serial.print("date du Dernier Changement : ");Serial.println(dateDernierChangement);
268 }
269 }
270
271 void prendDonneesMeteo() //Fonction qui utilise le client web du Wemos pour envoyer/recevoir des données de requêtes GET.
272 {
273 if (client.connect(nomDuServeur, 80)) { // Démarre la connexion du client, recherche les connexions
274 client.println("GET /data/2.5/weather?id=" + identifiantVille + "&units=metric&lang=fr&APPID=" + cledAPI);
275 client.println("Host: api.openweathermap.org");
276 client.println("User-Agent: ArduinoWiFi/1.1");
277 client.println("Connection: close");
278 client.println();
279 }
280 else {
281 Serial.println("Echec de la connexion"); //message d'erreur si la connexion échoue
282 Serial.println();
283 }
284
285 while (client.connected() && !client.available()) delay(1); // attend les données
286 while (client.connected() || client.available()) { // soit le client est connecté, soit il a des données disponibles
287 char c = client.read(); // récupère les données
288 resultat = resultat + c; // les agrège dans la chaine de caractère "resultat"
289 }
290
291 client.stop(); // stoppe le client
292 resultat.replace('[', ' ');
293 resultat.replace(']', ' ');
294 Serial.println(resultat); // écrit la chaine de caractère en entier sur le moniteur série
295
296 char tableauJson [resultat.length() + 1];
297 resultat.toCharArray(tableauJson, sizeof(tableauJson));
298 tableauJson[resultat.length() + 1] = '\0';
299
300 StaticJsonDocument<1024> doc;
301
302 DeserializationError error = deserializeJson(doc, tableauJson);
303
304 if (error) {
305 Serial.print(F("deserializeJson() failed with code "));
306 Serial.println(error.c_str());
307 return;
308 }
309
310 String lieu = doc["name"];
311 String pays = doc["sys"]["country"];
312 float temperature = doc["main"]["temp"];
313 float humidite = doc["main"]["humidity"];
314 String meteo = doc["weather"]["main"];
315 String description = doc["weather"]["description"];
316 String id = doc["weather"]["id"]; //récupère le chiffre identifiant "id" de l'état météo sous forme de texte.
317 float pression = doc["main"]["pressure"];
318
319 descriptionMeteo = description;
320 lieuMeteo = lieu;
321 Pays = pays;
322 Temperature = temperature;
323 Humidite = humidite;
324 Pression = pression;
325 para =id.toInt(); //transforme le texte "id" en entier.
326
327 }
328
329 void ecritMeteoGeneral(String lieu, String description)
330 {
331 Serial.println("------------------");
332 Serial.print(lieu);
333 Serial.print(", ");
334 Serial.print(Pays);
335 Serial.print(", ");
336 Serial.print(description);
337 Serial.print(", ");
338 Serial.println(para);
339 }
340
341 void parapluie ()
342 {
343
344 if (para<600) { // Si la valeur de l'indicateur météo est inférieur à 600 c'est qu'il pleut.
345 if (pluie == 0) { // Si avant ça il ne pleuvait pas
346 monservo.attach(0); // brancher le servomoteur sur la broche D3 (GPIO 0)
347 monservo.write(ouvre); // ouvre le parapluie
348 Serial.print("ouvre à : ");Serial.println(ouvre);
349 pluie = 1; // note qu'il pleut
350 Serial.print("pluie à : ");Serial.println(pluie);
351 }
352 }
353 else { // si il ne pleut pas
354 if (pluie == 1) { // et que juste avant il pleuvait (le parapluie était donc ouvert).
355 monservo.attach(0); // brancher le servomoteur sur la broche D3 (GPIO 0)
356 monservo.write(ferme); // ferme le parapluie
357 Serial.print("ferme à : ");Serial.println(ferme);
358 pluie = 0; // note bien qu'il ne pleut pas
359 Serial.print("pluie à : ");Serial.println(pluie);
360 }
361 }
362 delay (200);
363 monservo.detach(); // débrancher le servomoteur de la broche D3 (GPIO 0)
364 }
365
366 void connexion() {
367 WiFi.mode(WIFI_STA); // Le Wemos est en mode station wifi.
368 Serial.println("Connexion");
369 WiFi.begin(nomDuReseau, motDePasse);
370 while (WiFi.status() != WL_CONNECTED) {
371 delay(500);
372 Serial.print(".");
373 }
374 Serial.println("Connecté");
375 Serial.print("Station IP address: ");
376 Serial.println(WiFi.localIP());
377 pinMode(LED_BUILTIN, OUTPUT); // Configure la broche D4 (GPIO 2) sur la quelle est branchée la LED_BUILTIN en sortie
378 digitalWrite(LED_BUILTIN, LOW); // Allume la LED_BUILTIN
379 }
Prototype n°2 du POCL-Parapluie
Les fichiers à imprimer :
Toutes les pièces : Fichier:1-Parapluie-pieces-finales.stl
Le porte-servo : Fichier:1-Porte-servo.stl
Le porte-carte : Fichier:1-Porte-carte.stl
Les rouages : Fichier:1-Rouages.stl
Matériel supplémentaire :
- 1 ombrelle à cocktail
- 1 cure-dents
- 3 câbles Dupont
- De la colle
!!! Le porte-carte maintient le support de la crémaillère droit, afin que les rouages fonctionnent, respecter l'ordre des éléments !!!
Code
- Téléchargement du code :
- Télécharger le fichier zip de la branche principale (main) du dépôt Github : https://github.com/t-vernet/petitParapluieConnecte/tree/v1.0.0
- Télécharger le fichier zip de WiFiManager : https://github.com/tzapu/WiFiManager
- Télécharger le fichier zip de WebSocketsServer : https://github.com/Links2004/arduinoWebSockets
- Paramètres à modifier au besoin :
// ssid AP : concaténation POCL_NAME + "-" + POCL_ID const char *POCL_ID = "1"; // id unique du POCL à paramétrer const char *POCL_NAME = "POCL-Parapluie"; // nom du POCL // mot de pas AP: minimun 8 caractères const char *AP_PW = "lpdgo2021"; #define CLEF_API "remplacemoi" // Clef de l'API par default à remplacer ici ou via le site #define ID_VILLE "3030300" // ID ville par default à remplacer ici ou via le site /*Quelques identifiants d'autres villes françaises : * 3030300, Brest * 6431033, Quimper * 6430976, Morlaix * 6453798, Lannion * 6453805, Saint-Brieuc * 6432801, Rennes * 6437298, Lorient * 2970777, Vannes * 6434483, Nantes * 6456407, Le Mans * 6427109, Caen * 6452361, Angers * 6456577, La Roche sur Yon * 3021411, Dieppe */ #define ANGLE_FERME 0 // angle pour fermer le parapluie #define ANGLE_OUVERT 170 // angle pour ouvrir le parapluie
Boîte pour le Pocl Parapluie :
Murs, haut et bas à découper sur du medium 5mm d'épaisseur, face avant à découper en plexiglas de 5mm :
Erreur lors de la création de la miniature :