· 6 years ago · Oct 10, 2019, 06:16 PM
1#include <Adafruit_BME280.h>
2
3/*
4**********************************************************************
5* Weather station with a Nextion display, showing date, time, tempe-
6* rature, humidity, airpressure and weather forecast using
7* openweathermap.org
8*
9* Setup used:
10* - NodeMCU (8266 microprocessor)
11* - BME280 sensor
12* - Nextion 3,2" 400x240 display
13*
14**********************************************************************
15* Last updated 20171229 by ericBcreator
16*
17* This code is free for personal use, not for commercial purposes.
18* Please leave this header intact.
19*
20* contact: ericBcreator@gmail.com
21**********************************************************************
22*/
23
24//#define DEBUG
25#define DEBUG_NO_PAGE_FADE
26#define DEBUG_NO_TOUCH
27
28#define nexSerial Serial1
29
30//
31// include libraries
32//
33
34#include <Wire.h>
35#include <SPI.h> // I2C library
36#include <Adafruit_BME280.h> // BME280 library
37#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
38#include <ESP8266WiFi.h> // WiFi library
39//#include <WiFi.h> // WiFi library
40#include <WiFiUdp.h> // Udp library
41#include <TimeLib.h> // Time library
42
43//extern "C" {
44#include "user_interface.h"
45//}
46
47//
48// settings for WiFi connection
49//
50
51char * ssid = "Hobo"; // WiFi SSID
52char * password = "nyugdijas5358"; // WiFi password
53
54unsigned int localPort = 2390; // local port to listen for UDP packets
55IPAddress timeServerIP; // IP address of random server
56const char* ntpServerName = "pool.ntp.org"; // server pool
57byte packetBuffer[48]; // buffer to hold incoming and outgoing packets
58int timeZoneoffsetGMT = 3600; // offset from Greenwich Meridan Time
59boolean DST = false; // daylight saving time
60WiFiUDP clockUDP; // initialize a UDP instance
61
62//
63// settings for the openweathermap connection
64// sign up, get your api key and insert it below
65//
66
67char * servername ="api.openweathermap.org"; // remote server with weather info
68String APIKEY = "cda8a226420d9723e708a51c2c8d1d84"; // personal api key for retrieving the weather data
69
70const int httpPort = 80;
71String result;
72int cityIDLoop = 0;
73
74// a list of cities you want to display the forecast for
75// get the ID at https://openweathermap.org/
76// type the city, click search and click on the town
77// then check the link, like this: https://openweathermap.org/city/5128581
78// 5128581 is the ID for New York
79
80String cityIDs[] = {
81 "2759794", // Amsterdam
82 "264371", // Athens
83 "3513090", // Willemstad
84 "524901", // Moscow
85 "5128581", // New York
86 "3369157", // Cape Town
87 "" // end the list with an empty string
88};
89
90//
91// settings
92//
93
94int startupDelay = 1000; // startup delay
95int loopDelay = 3000; // main loop delay between sensor updates
96
97int timeServerDelay = 1000; // delay for the time server to reply
98int timeServerPasses = 4; // number of tries to connect to the time server before timing out
99int timeServerResyncNumOfLoops = 3000; // number of loops before refreshing the time. one loop takes approx. 28 seconds
100int timeServerResyncNumOfLoopsCounter = 0;
101boolean timeServerConnected = false; // is set to true when the time is read from the server
102
103int maxForecastLoop = 10; // number of main loops before the forecast is refreshed, looping through all cities
104int weatherForecastLoop = 0;
105int weatherForecastLoopInc = 1;
106
107int displayStartupDimValue = 30; // startup display backlight level
108int displayDimValue = 150; // main display backlight level
109int displayDimStep = 1; // dim step
110int dimStartupDelay = 50; // delay for fading in
111int dimPageDelay = 0; // delay for fading between pages
112
113//
114// initialize variables
115//
116
117int page = 0;
118
119float bmeAltitude = 0;
120float bmeHumidity = 0;
121float bmePressure = 0;
122float bmeTemperature = 0;
123
124String command;
125String doubleQuote = "\"\"";
126
127//
128// initialize sensor
129//
130
131Adafruit_BME280 BME; // initialize BME280 over I2C
132
133//
134// initialize timer
135//
136
137os_timer_t secTimer;
138
139void timerDisplayTime(void *pArg) {
140 displayTime();
141}
142
143//
144// setup
145//
146
147void setup() {
148 #ifdef DEBUG
149 Serial.begin(9600);
150 #endif
151
152 nexSerial.begin(9600);
153
154 printNextionCommand("dims=" + String(0)); // set initial startup backlight value of the Nextion display to 0
155 printNextionCommand("dim=" + String(0)); // also set the current backlight value to 0
156 printNextionCommand("page 0");
157
158 delay(startupDelay);
159
160 BME.begin();
161
162 displayFadeIn(0, displayStartupDimValue, dimStartupDelay);
163
164 connectToWifi();
165 clockUDP.begin(localPort);
166 getTimeFromServer();
167
168 if (timeServerConnected)
169 displayTime();
170
171 displayDate();
172 displayBMESensor();
173
174 os_timer_setfn(&secTimer, timerDisplayTime, NULL);
175 if (timeServerConnected)
176 os_timer_arm(&secTimer, 1000, true);
177
178 displayFadeIn(displayStartupDimValue, displayDimValue, dimStartupDelay / 2);
179
180 #ifdef DEBUG
181 Serial.println("Starting main loop");
182 #endif
183}
184
185//
186// main loop
187//
188
189void loop() {
190 if (!timeServerConnected) {
191 getTimeFromServer();
192 if (timeServerConnected)
193 os_timer_arm(&secTimer, 1000, true);
194 }
195
196 if (page == 0) {
197 displayBMESensor();
198
199 if (weatherForecastLoop == maxForecastLoop) {
200 timeServerResyncNumOfLoopsCounter +=1;
201 if (timeServerResyncNumOfLoopsCounter == timeServerResyncNumOfLoops) {
202 getTimeFromServer();
203 timeServerResyncNumOfLoopsCounter = 0;
204 }
205
206 page = 1;
207 getWeatherData();
208 weatherForecastLoopInc = -weatherForecastLoopInc;
209 displayDate();
210 }
211
212 delayCheckTouch(loopDelay);
213 }
214
215 else if (page == 1) {
216 if (weatherForecastLoop == 0) {
217 page = 0;
218
219 #if !defined (DEBUG_NO_PAGE_FADE)
220 displayFadeOut(displayDimValue, dimPageDelay);
221 #endif
222
223 printNextionCommand("page 0");
224
225 #if !defined (DEBUG_NO_PAGE_FADE)
226 displayFadeIn(0, displayDimValue, dimPageDelay);
227 #endif
228
229 weatherForecastLoopInc = -weatherForecastLoopInc;
230
231 displayTime();
232 displayDate();
233 }
234 else
235 delayCheckTouch(loopDelay);
236 }
237
238 weatherForecastLoop += weatherForecastLoopInc;
239
240 #ifdef DEBUG
241 Serial.print(page);
242 Serial.print(" ");
243 Serial.print(weatherForecastLoop);
244 Serial.print(" ");
245 Serial.print(timeServerResyncNumOfLoopsCounter);
246 Serial.print(" ");
247 Serial.print(timeServerResyncNumOfLoops);
248 Serial.println("");
249 #endif
250}
251
252//
253// functions
254//
255
256void displayBMESensor() {
257 bmePressure = BME.readPressure() / 100;
258 bmeHumidity = BME.readHumidity();
259 bmeTemperature = BME.readTemperature();
260 //bmpAltitude = BME.readAltitude(102450);
261
262 if (bmePressure < 1000)
263 command = "bmppressure.txt=\" " + String(bmePressure) + "\"";
264 else
265 command = "bmppressure.txt=\"" + String(bmePressure) + "\"";
266 printNextionCommand(command);
267
268 command = "humidity.txt=\"" + String(bmeHumidity) + "\"";
269 printNextionCommand(command);
270
271 command = "temperature.txt=\"" + String(bmeTemperature, 1) + "\"";
272 printNextionCommand(command);
273
274 //command = "bmpaltitude.txt=\"" + String(bmpAltitude,1) + "\"";
275 //printNextionCommand(command);
276}
277
278void displayDate() {
279 time_t t = now();
280
281 command = "date.txt=\"" + String(day(t)) + " " + monthAsString(month(t)) + "\"";
282 printNextionCommand(command);
283 command = "year.txt=\"" + String(year(t)) + "\"";
284 printNextionCommand(command);
285}
286
287void displayTime() {
288 time_t t = now();
289 char timeString[9];
290
291 snprintf(timeString, sizeof(timeString), "%02d:%02d:%02d", hour(t), minute(t), second(t));
292 command = "time.txt=\"" + String(timeString) + "\"";
293 printNextionCommand(command);
294}
295
296//
297// Nextion commands
298//
299
300void printNextionCommand (String command) {
301 nexSerial.print(command);
302 endNextionCommand();
303}
304
305void sendToLCD(uint8_t type,String index, String cmd) {
306 if (type == 1 ) {
307 nexSerial.print(index);
308 nexSerial.print(".txt=");
309 nexSerial.print("\"");
310 nexSerial.print(cmd);
311 nexSerial.print("\"");
312 }
313 else if (type == 2) {
314 nexSerial.print(index);
315 nexSerial.print(".val=");
316 nexSerial.print(cmd);
317 }
318 else if (type == 3) {
319 nexSerial.print(index);
320 nexSerial.print(".pic=");
321 nexSerial.print(cmd);
322 }
323 else if (type ==4 ) {
324 nexSerial.print("page ");
325 nexSerial.print(cmd);
326 }
327
328 endNextionCommand();
329}
330
331void endNextionCommand() {
332 nexSerial.write(0xff);
333 nexSerial.write(0xff);
334 nexSerial.write(0xff);
335}
336
337void displayFadeIn(int fromValue, int toValue, int fadeDelay) {
338 for (int i = fromValue; i <= toValue; i += displayDimStep) {
339 if (i > toValue)
340 i = toValue;
341 printNextionCommand("dim=" + String(i));
342 delay(fadeDelay);
343 }
344}
345
346void displayFadeOut(int fromValue, int fadeDelay) {
347 for (int i = fromValue; i >= 0; i -= displayDimStep) {
348 if (i < 0)
349 i = 0;
350 printNextionCommand("dim=" + String(i));
351 delay(fadeDelay);
352 }
353}
354
355//
356// network functions
357//
358
359void connectToWifi() {
360 int wifiBlink = 0;
361
362// WiFi.enableSTA(true);
363 WiFi.mode(WIFI_STA);
364 WiFi.disconnect();
365
366 WiFi.begin(ssid, password);
367
368 while (WiFi.status() != WL_CONNECTED) {
369 if (wifiBlink == 0) {
370 printNextionCommand("wifi_connect.txt=\"connect...\"");
371 wifiBlink = 1;
372 }
373 else {
374 printNextionCommand("wifi_connect.txt=" + doubleQuote);
375 wifiBlink = 0;
376 }
377 delay(500);
378 }
379
380 printNextionCommand("wifi_connect.txt=" + doubleQuote);
381}
382
383void getTimeFromServer() {
384 #ifdef DEBUG
385 Serial.print("Getting time from server...");
386 #endif
387
388 int connectStatus = 0, i = 0;
389 unsigned long unixTime;
390
391 while (i < timeServerPasses && !connectStatus) {
392 #ifdef DEBUG
393 Serial.print(i);
394 Serial.print("...");
395 #endif
396
397 printNextionCommand("time.txt=\"get time..\"");
398 WiFi.hostByName(ntpServerName, timeServerIP);
399 sendNTPpacket(timeServerIP);
400 delay(timeServerDelay / 2);
401 connectStatus = clockUDP.parsePacket();
402 printNextionCommand("time.txt=" + doubleQuote);
403 delay(timeServerDelay / 2);
404 i++;
405 }
406
407 if (connectStatus) {
408 #ifdef DEBUG
409 Serial.print(i);
410 Serial.println("...connected");
411 #endif
412
413 timeServerConnected = true;
414 clockUDP.read(packetBuffer,48);
415
416 // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long.
417 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
418 unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
419 // the timestamp is in seconds from 1900, add 70 years to get Unixtime
420 unixTime = (highWord << 16 | lowWord) - 2208988800 + timeZoneoffsetGMT;
421
422 if (DST)
423 unixTime = unixTime + 3600;
424
425 setTime(unixTime);
426 }
427 else {
428 #ifdef DEBUG
429 Serial.print(i);
430 Serial.println("...failed...");
431 #endif
432
433 printNextionCommand("time.txt=\"failed....\"");
434 delay(timeServerDelay);
435 printNextionCommand("time.txt=" + doubleQuote);
436 }
437}
438
439unsigned long sendNTPpacket(IPAddress& address) {
440 memset(packetBuffer, 0, 48);
441 packetBuffer[0] = 0b11100011; // LI, Version, Mode
442 packetBuffer[1] = 0; // Stratum, or type of clock
443 packetBuffer[2] = 6; // Polling Interval
444 packetBuffer[3] = 0xEC; // Peer Clock Precision
445 // 8 bytes of zero for Root Delay & Root Dispersion
446 packetBuffer[12] = 49;
447 packetBuffer[13] = 0x4E;
448 packetBuffer[14] = 49;
449 packetBuffer[15] = 52;
450
451 clockUDP.beginPacket(address, 123); //NTP requests are to port 123
452 clockUDP.write(packetBuffer, 48);
453 clockUDP.endPacket();
454}
455
456//
457// get and display weather data
458//
459
460void getWeatherData() //client function to send/receive GET request data.
461{
462 WiFiClient client;
463 if (!client.connect(servername, httpPort))
464 return;
465
466 String cityID = cityIDs[cityIDLoop];
467 cityIDLoop++;
468
469 if (cityIDs[cityIDLoop] == "")
470 cityIDLoop = 0;
471
472 String url = "/data/2.5/forecast?id=" + cityID + "&units=metric&cnt=1&APPID=" + APIKEY;
473 //String url = "/data/2.5/weather?id=" + cityID + "&units=metric&cnt=1&APPID=" + APIKEY;
474 //check weather properties at https://openweathermap.org/current
475
476 // This will send the request to the server
477 client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + servername + "\r\n" + "Connection: close\r\n\r\n");
478 unsigned long timeout = millis();
479 while (client.available() == 0) {
480 if (millis() - timeout > 5000) {
481 client.stop();
482 return;
483 }
484 }
485
486 result = "";
487 // Read all the lines of the reply from server
488 while(client.available()) {
489 result = client.readStringUntil('\r');
490 }
491
492 result.replace('[', ' ');
493 result.replace(']', ' ');
494
495 char jsonArray [result.length()+1];
496 result.toCharArray(jsonArray,sizeof(jsonArray));
497 jsonArray[result.length() + 1] = '\0';
498
499 StaticJsonBuffer<1024> json_buf;
500 JsonObject &root = json_buf.parseObject(jsonArray);
501 if (!root.success())
502 nexSerial.println("parseObject() failed");
503
504 //check properties forecasts at https://openweathermap.org/forecast5
505
506 int weatherID = root["list"]["weather"]["id"];
507
508 String tmp0 = root["city"]["name"];
509 String tmp1 = root["list"]["weather"]["main"];
510 String tmp2 = root["list"]["weather"]["description"];
511 float tmp3 = root["list"]["main"]["temp_min"];
512 float tmp4 = root["list"]["main"]["temp_max"];
513 float tmp5 = root["list"]["main"]["humidity"];
514 float tmp6 = root["list"]["clouds"]["all"];
515 float tmp7 = root["list"]["rain"]["3h"];
516 float tmp8 = root["list"]["snow"]["3h"];
517 float tmp9 = root["list"]["wind"]["speed"];
518 int tmp10 = root["list"]["wind"]["deg"];
519 float tmp11 = root["list"]["main"]["pressure"];
520//String tmp12 = root["list"]["dt_text"]; command = command + tmp12;
521
522 #if !defined (DEBUG_NO_PAGE_FADE)
523 displayFadeOut(displayDimValue, dimPageDelay);
524 #endif
525
526 printNextionCommand("page 1");
527
528 #if !defined (DEBUG_NO_PAGE_FADE)
529 displayFadeIn(0, displayDimValue, dimPageDelay );
530 #endif
531
532 setWeatherPicture(weatherID);
533 sendToLCD(1, "city", tmp0);
534 sendToLCD(1, "description", tmp2);
535 sendToLCD(1, "humidity", String(tmp5, 0));
536 sendToLCD(1, "rain", String(tmp7, 1));
537 sendToLCD(1, "wind_dir", getShortWindDirection(tmp10));
538 sendToLCD(1, "wind_speed", String(tmp9,1));
539 sendToLCD(1, "pressure", String(tmp11, 0));
540 sendToLCD(1, "clouds", String(tmp6, 0));
541 sendToLCD(1, "temp_min", String(tmp3, 1));
542 sendToLCD(1, "temp_max", String(tmp4, 1));
543
544 //sendToLCD(1, "weather_ID", String(weatherID, 0));
545}
546
547String getWindDirection (int degrees) {
548 int sector = ((degrees + 11) / 22.5 - 1);
549 switch (sector) {
550 case 0: return "north";
551 case 1: return "nort-northeast";
552 case 2: return "northeast";
553 case 3: return "east-northeast";
554 case 4: return "east";
555 case 5: return "east-southeast";
556 case 6: return "southeast";
557 case 7: return "south-southeast";
558 case 8: return "south";
559 case 9: return "south-southwest";
560 case 10: return "southwest";
561 case 11: return "west-southwest";
562 case 12: return "west";
563 case 13: return "west-northwest";
564 case 14: return "northwest";
565 case 15: return "north-northwest";
566 }
567}
568
569String getShortWindDirection (int degrees) {
570 int sector = ((degrees + 11) / 22.5 - 1);
571 switch (sector) {
572 case 0: return "N";
573 case 1: return "NNE";
574 case 2: return "NE";
575 case 3: return "ENE";
576 case 4: return "E";
577 case 5: return "ESE";
578 case 6: return "SE";
579 case 7: return "SSE";
580 case 8: return "S";
581 case 9: return "SSW";
582 case 10: return "SW";
583 case 11: return "WSW";
584 case 12: return "W";
585 case 13: return "WNW";
586 case 14: return "NW";
587 case 15: return "NNW";
588 }
589}
590
591void setWeatherPicture(int weatherID) {
592 switch(weatherID)
593 {
594 case 200:
595 case 201:
596 case 202:
597 case 210: sendToLCD(3, "weatherpic", "26"); break; // tstorm1
598 case 211: sendToLCD(3, "weatherpic", "27"); break; // tstorm2
599 case 212: sendToLCD(3, "weatherpic", "28"); break; // tstorm3
600 case 221:
601 case 230:
602 case 231:
603 case 232: sendToLCD(3, "weatherpic", "27"); break; // tstorm2
604
605 case 300:
606 case 301:
607 case 302:
608 case 310:
609 case 311:
610 case 312:
611 case 313:
612 case 314:
613 case 321: sendToLCD(3, "weatherpic", "15"); break; // rain1
614
615 case 500:
616 case 501: sendToLCD(3, "weatherpic", "15"); break; // rain1
617 case 502:
618 case 503:
619 case 504: sendToLCD(3, "weatherpic", "16"); break; // rain2
620 case 511:
621 case 520:
622 case 521: sendToLCD(3, "weatherpic", "17"); break; // shower1
623 case 522:
624 case 531: sendToLCD(3, "weatherpic", "18"); break; // shower2
625
626 case 600: sendToLCD(3, "weatherpic", "20"); break; // snow1
627 case 601: sendToLCD(3, "weatherpic", "22"); break; // snow3
628 case 602: sendToLCD(3, "weatherpic", "24"); break; // snow5
629 case 611:
630 case 612: sendToLCD(3, "weatherpic", "14"); break; // sleet
631 case 615: sendToLCD(3, "weatherpic", "20"); break; // snow1
632 case 616: sendToLCD(3, "weatherpic", "22"); break; // snow3
633 case 620: sendToLCD(3, "weatherpic", "20"); break; // snow1
634 case 621: sendToLCD(3, "weatherpic", "22"); break; // snow3
635 case 622: sendToLCD(3, "weatherpic", "24"); break; // snow5
636
637 case 701:
638 case 711:
639 case 721: sendToLCD(3, "weatherpic", "13"); break; // mist
640 case 731: sendToLCD(3, "weatherpic", "10"); break; // dunno
641 case 741: sendToLCD(3, "weatherpic", "11"); break; // fog
642 case 751:
643 case 761:
644 case 762:
645 case 771:
646 case 781: sendToLCD(3, "weatherpic", "10"); break; // dunno
647
648 case 800: sendToLCD(3, "weatherpic", "25"); break; // sunny
649 case 801: sendToLCD(3, "weatherpic", "5"); break; // cloud1
650 case 802: sendToLCD(3, "weatherpic", "7"); break; // cloud3
651 case 803: sendToLCD(3, "weatherpic", "8"); break; // cloud4
652 case 804: sendToLCD(3, "weatherpic", "14"); break; // overcast
653
654 case 906: sendToLCD(3, "weatherpic", "12"); break; // hail
655
656 default: sendToLCD(3, "weatherpic", "10"); break; // dunno
657 }
658}
659
660void delayCheckTouch (int delayTime) {
661 unsigned long startMillis = millis();
662
663 while (millis() - startMillis < delayTime) {
664 delay(1000);
665 }
666}
667
668String dayAsString(int day) {
669 switch (day) {
670 case 1: return "Sunday";
671 case 2: return "Monday";
672 case 3: return "Tuesday";
673 case 4: return "Wednessday";
674 case 5: return "Thursday";
675 case 6: return "Friday";
676 case 7: return "Saturday";
677 }
678 return "" ;
679}
680
681String monthAsString(int month) {
682 switch (month) {
683 case 1: return "January";
684 case 2: return "February";
685 case 3: return "March";
686 case 4: return "April";
687 case 5: return "May";
688 case 6: return "June";
689 case 7: return "July";
690 case 8: return "August";
691 case 9: return "September";
692 case 10: return "October";
693 case 11: return "November";
694 case 12: return "December";
695 }
696 return "" ;
697}