Merge remote-tracking branch 'refs/remotes/origin/master'
[weathermon.git] / weather_screen / weather_screen.ino
1 #include <FS.h>                   //this needs to be first, or it all crashes and burns...
2 #include "SPIFFS.h"
3 #include <WiFi.h>          //https://github.com/esp8266/Arduino
4 //needed for library
5 #include <DNSServer.h>
6 #include <WebServer.h>
7 #include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
8
9 #include <ArduinoJson.h>          //https://github.com/bblanchon/ArduinoJson
10
11 #include <time.h>                     // Built-in
12 #include <SPI.h>                      // Built-in 
13
14 #include <HTTPClient.h>
15
16 #include <GxEPD2_BW.h>
17 #include <U8g2_for_Adafruit_GFX.h>
18
19 #include <time.h>
20 #include <sys/time.h>
21
22 #include "weather_types.h"
23
24 #define SCREEN_WIDTH  800             // Set for landscape mode
25 #define SCREEN_HEIGHT 480
26
27 enum alignment {LEFT, RIGHT, CENTER};
28
29 // Connections for e.g. Waveshare ESP32 e-Paper Driver Board
30 static const uint8_t EPD_BUSY = 25;
31 static const uint8_t EPD_CS   = 15;
32 static const uint8_t EPD_RST  = 26; 
33 static const uint8_t EPD_DC   = 27; 
34 static const uint8_t EPD_SCK  = 13;
35 static const uint8_t EPD_MISO = 12; // Master-In Slave-Out not used, as no data from display
36 static const uint8_t EPD_MOSI = 14;
37
38 GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(GxEPD2_750_T7(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY));   // B/W display
39 //GxEPD2_3C<GxEPD2_750c, GxEPD2_750c::HEIGHT> display(GxEPD2_750(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // 3-colour displays
40 // use GxEPD_BLACK or GxEPD_WHITE or GxEPD_RED or GxEPD_YELLOW depending on display type
41
42 U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
43
44 #include "fonts.h"
45
46 //define your default values here, if there are different values in config.json, they are overwritten.
47 //char api_key[40]      = "";                      // See: https://openweathermap.org/  // It's free to get an API key, but don't take more than 60 readings/minute!
48 char latitude[8]      = "55.5";
49 char longitude[8]     = "37.5";
50 char timezone[40]     = "Europe/Moscow";         // Forecast beginning of day
51
52 char current_weather_server[255] = "";
53 char current_weather_uri[255] = "";
54 char current_weather_pressure[40] = "PRESSURE";
55 char current_weather_temperature[40] = "TEMPERATURE_C";
56 char current_weather_humidity[40] = "HUMIDITY";
57 char current_weather_windspeed[40] = "";
58 char current_weather_lux[40] = "LUX";
59
60 const char server[] = "api.open-meteo.com";
61
62 int SleepDuration = 10;
63
64 //flag for saving data
65 bool shouldSaveConfig = false;
66
67 //callback notifying us of the need to save config
68 void saveConfigCallback () {
69   Serial.println("Should save config");
70   shouldSaveConfig = true;
71 }
72
73 void DisplaySetup() {
74   display.init(115200, true, 2); // init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode)
75   SPI.end();
76   SPI.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS);
77   u8g2Fonts.begin(display); // connect u8g2 procedures to Adafruit GFX
78   u8g2Fonts.setFontMode(1);                  // use u8g2 transparent mode (this is default)
79   u8g2Fonts.setFontDirection(0);             // left to right (this is default)
80   u8g2Fonts.setForegroundColor(GxEPD_BLACK); // apply Adafruit GFX color
81   u8g2Fonts.setBackgroundColor(GxEPD_WHITE); // apply Adafruit GFX color
82   SetCyrFont(10); 
83   display.fillScreen(GxEPD_WHITE);
84   display.setFullWindow();
85 }
86
87 void WiFiSetup() {
88
89   // The extra parameters to be configured (can be either global or just in the setup)
90   // After connecting, parameter.getValue() will get you the configured value
91   // id/name placeholder/prompt default length
92   WiFiManagerParameter custom_latitude("latitide", "Latitude", latitude, 8);
93   WiFiManagerParameter custom_longitude("longitude", "Longitude", longitude, 8);
94   WiFiManagerParameter custom_timezone("timezone", "Time zone", timezone, 40);
95   WiFiManagerParameter custom_current_weather_server("current_weather_server", "Weather server", current_weather_server, 255);
96   WiFiManagerParameter custom_current_weather_uri("current_weather_uri", "Weather URI", current_weather_uri, 255);
97   WiFiManagerParameter custom_current_weather_pressure("current_weather_pressure", "Pressure key", current_weather_pressure, 40);
98   WiFiManagerParameter custom_current_weather_temperature("current_weather_temperature", "Temperature key", current_weather_temperature, 40);
99   WiFiManagerParameter custom_current_weather_humidity("current_weather_humidity", "Humidity key", current_weather_humidity, 40);
100   WiFiManagerParameter custom_current_weather_windspeed("current_weather_windspeed", "Windspeed key", current_weather_windspeed, 40);
101   WiFiManagerParameter custom_current_weather_lux("current_weather_lux", "Lux key", current_weather_lux, 40);
102  
103   //WiFiManager
104   //Local intialization. Once its business is done, there is no need to keep it around
105   WiFiManager wifiManager;
106
107   //set config save notify callback
108   wifiManager.setSaveConfigCallback(saveConfigCallback);
109
110   //add all your parameters here
111   wifiManager.addParameter(&custom_latitude);
112   wifiManager.addParameter(&custom_longitude);
113   wifiManager.addParameter(&custom_timezone);
114   wifiManager.addParameter(&custom_current_weather_server);
115   wifiManager.addParameter(&custom_current_weather_uri);
116   wifiManager.addParameter(&custom_current_weather_pressure);
117   wifiManager.addParameter(&custom_current_weather_temperature);
118   wifiManager.addParameter(&custom_current_weather_humidity);
119   wifiManager.addParameter(&custom_current_weather_windspeed);
120   wifiManager.addParameter(&custom_current_weather_lux);
121
122   wifiManager.setTimeout(300);
123
124   if (WiFi. status() != WL_CONNECTED) {
125     WiFi.disconnect();
126     WiFi.mode(WIFI_AP);
127     WiFi.begin();
128   } 
129   
130   if (!wifiManager.startConfigPortal()) {
131     Serial.println("failed to connect and hit timeout");
132     delay(3000);
133     //reset and try again, or maybe put it to deep sleep
134     ESP.restart();
135   }
136
137   //if you get here you have connected to the WiFi
138   Serial.println("connected...");
139
140   //read updated parameters
141   strcpy(latitude, custom_latitude.getValue());
142   strcpy(longitude, custom_longitude.getValue());
143   strcpy(timezone, custom_timezone.getValue());
144   strcpy(current_weather_server, custom_current_weather_server.getValue());
145   strcpy(current_weather_uri, custom_current_weather_uri.getValue());
146   strcpy(current_weather_pressure, custom_current_weather_pressure.getValue());
147   strcpy(current_weather_temperature, custom_current_weather_temperature.getValue());
148   strcpy(current_weather_humidity, custom_current_weather_humidity.getValue());
149   strcpy(current_weather_windspeed, custom_current_weather_windspeed.getValue());
150   strcpy(current_weather_lux, custom_current_weather_lux.getValue());
151
152   //save the custom parameters to FS
153   if (shouldSaveConfig) {
154     Serial.println("saving config");
155     DynamicJsonDocument json(1024);
156     // json["api_key"] = api_key;
157     json["latitude"] = latitude;
158     json["longitude"] = longitude;
159     json["timezone"] = timezone;
160     json["current_weather_server"] = current_weather_server;
161     json["current_weather_uri"] = current_weather_uri;
162     json["current_weather_pressure"] = current_weather_pressure;
163     json["current_weather_temperature"] = current_weather_temperature;
164     json["current_weather_humidity"] = current_weather_humidity;
165     json["current_weather_windspeed"] = current_weather_windspeed;
166     json["current_weather_lux"] = current_weather_lux;
167
168     File configFile = SPIFFS.open("/config.json", "w");
169     if (!configFile) {
170       Serial.println("failed to open config file for writing");
171     }
172
173     serializeJson(json, Serial);
174     serializeJson(json, configFile);
175     configFile.close();
176     //end save
177   }
178  
179 }
180
181 void setup() {
182   byte need_setup = false;
183   // put your setup code here, to run once:
184   Serial.begin(115200);
185   Serial.println();
186
187   //read configuration from FS json
188   Serial.println("mounting FS...");
189
190   if (SPIFFS.begin(true)) {
191     Serial.println("mounted file system");
192     if (SPIFFS.exists("/config.json")) {
193       //file exists, reading and loading
194       Serial.println("reading config file");
195       File configFile = SPIFFS.open("/config.json", "r");
196       if (configFile) {
197         Serial.println("opened config file");
198         size_t size = configFile.size();
199         // Allocate a buffer to store contents of the file.
200         std::unique_ptr<char[]> buf(new char[size]);
201
202         configFile.readBytes(buf.get(), size);
203
204         DynamicJsonDocument json(1024);
205         auto deserializeError = deserializeJson(json, buf.get());
206         serializeJson(json, Serial);
207         if ( ! deserializeError ) {
208           Serial.println("\nparsed json");
209           if (json.containsKey("latitude")) { strcpy(latitude, json["latitude"]); }
210           if (json.containsKey("longitude")) { strcpy(longitude, json["longitude"]); }
211           if (json.containsKey("timezone")) { strcpy(timezone, json["timezone"]); }
212           if (json.containsKey("current_weather_server")) { strcpy(current_weather_server, json["current_weather_server"]); }
213           if (json.containsKey("current_weather_uri")) { strcpy(current_weather_uri, json["current_weather_uri"]); }
214           if (json.containsKey("current_weather_pressure")) { strcpy(current_weather_pressure, json["current_weather_pressure"]); }
215           if (json.containsKey("current_weather_temperature")) { strcpy(current_weather_temperature, json["current_weather_temperature"]); }
216           if (json.containsKey("current_weather_humidity")) { strcpy(current_weather_humidity, json["current_weather_humidity"]); }
217           if (json.containsKey("current_weather_windspeed")) { strcpy(current_weather_windspeed, json["current_weather_windspeed"]); }
218           if (json.containsKey("current_weather_lux")) { strcpy(current_weather_lux, json["current_weather_lux"]); }
219         } else {
220           Serial.println("failed to load json config");
221         }
222         configFile.close();
223       } else {
224         need_setup = true;
225       }
226     } else {
227       need_setup = true;
228     }
229   } else {
230     Serial.println("failed to mount FS");
231   }
232   //end read
233
234   if (need_setup) {
235     WiFiSetup(); 
236   } else {
237     WiFiManager wifiManager;
238     wifiManager.setTimeout(10);
239     if(!wifiManager.autoConnect()) {
240       Serial.println("failed to connect and hit timeout");
241       WiFiSetup();
242       delay(3000);
243       ESP.restart();
244     }  
245   }
246
247   Serial.println("\nlocal ip");
248   Serial.println(WiFi.localIP());
249
250   DisplaySetup();
251   
252 }
253
254 int TimeZoneOffset;
255 // int SunRise, SunSet;
256
257 int HourlyDT[MaxHourlyFC];
258 float HourlyTemp[MaxHourlyFC];
259 float HourlyFeelsLike[MaxHourlyFC];
260 float HourlyPressure[MaxHourlyFC];
261 float HourlyHumidity[MaxHourlyFC];
262 // float HourlyClouds[MaxHourlyFC];
263 float HourlyWindSpeed[MaxHourlyFC];
264 // float HourlyRain[MaxHourlyFC];
265 // float HourlySnow[MaxHourlyFC];
266 // float HourlyShowers[MaxHourlyFC];
267 float HourlyPrecip[MaxHourlyFC];
268
269 String UnixTime(int unix_time) {
270   time_t tm = unix_time;
271   struct tm *now_tm = localtime(&tm);
272   char output[40];
273   strftime(output, sizeof(output), "%H:%M %d/%m/%y", now_tm);
274   return output;
275 }
276
277 String UnixTimeOnly(int unix_time) {
278   time_t tm = unix_time;
279   struct tm *now_tm = localtime(&tm);
280   char output[40];
281   strftime(output, sizeof(output), "%H:%M", now_tm);
282   return output;
283 }
284
285 String UnixDate(int unix_time) {
286   time_t tm = unix_time;
287   struct tm *now_tm = localtime(&tm);
288   char output[40];
289   strftime(output, sizeof(output), "%d/%m/%y", now_tm);
290   return output;
291 }
292
293 bool DecodeWeather(WiFiClient& json) {
294   DynamicJsonDocument doc(35 * 1024);
295   // Deserialize the JSON document
296   DeserializationError error = deserializeJson(doc, json);
297   // Test if parsing succeeds.
298   if (error) {
299     Serial.print(F("deserializeJson() failed: "));
300     Serial.println(error.c_str());
301     return false;
302   }
303   // convert it to a JsonObject
304   JsonObject root = doc.as<JsonObject>();
305
306   TimeZoneOffset = root["utc_offset_seconds"].as<int>();
307   
308
309   CurrentWeather.dt           = root["current"]["time"].as<int>();
310   CurrentWeather.temp         = root["current"]["temperature_2m"].as<float>();
311   CurrentWeather.feels_like   = root["current"]["apparent_temperature"].as<float>();
312   CurrentWeather.pressure     = 0.75 * root["current"]["surface_pressure"].as<float>();
313   CurrentWeather.humidity     = root["current"]["relative_humidity_2m"].as<float>();
314   CurrentWeather.wind_speed   = root["current"]["wind_speed_10m"].as<float>();
315   CurrentWeather.wind_deg     = root["current"]["wind_direction_10m"].as<float>();
316   CurrentWeather.precip       = root["current"]["precipitation"].as<float>();
317   CurrentWeather.wmo          = root["current"]["weather_code"].as<int>();
318
319   struct timeval now = { .tv_sec =  CurrentWeather.dt };
320   settimeofday(&now, NULL);
321
322   int firstForecastDT = root["hourly"]["time"][0].as<int>();
323
324   int offset = (CurrentWeather.dt - firstForecastDT) / 3600;
325
326   for (byte i=0; i<MaxHourlyFC; i++) {
327     HourlyDT[i] = HourlyForecasts[i].dt                  = root["hourly"]["time"][i+offset].as<int>();
328     HourlyTemp[i] = HourlyForecasts[i].temp              = root["hourly"]["temperature_2m"][i+offset].as<float>();
329     HourlyFeelsLike[i] = HourlyForecasts[i].feels_like   = root["hourly"]["apparent_temperature"][i+offset].as<float>();
330     HourlyPressure[i] = HourlyForecasts[i].pressure      = 0.75 * root["hourly"]["surface_pressure"][i+offset].as<float>();
331     HourlyHumidity[i] = HourlyForecasts[i].humidity      = root["hourly"]["relative_humidity_2m"][i+offset].as<float>();
332     HourlyWindSpeed[i] = HourlyForecasts[i].wind_speed   = root["hourly"]["wind_speed_10m"][i+offset].as<float>();
333     HourlyForecasts[i].wind_deg                          = root["hourly"]["wind_direction_10m"][i+offset].as<float>();
334     HourlyPrecip[i] = HourlyForecasts[i].precip          = root["hourly"]["precipitation"][i+offset].as<float>();
335     HourlyForecasts[i].wmo                               = root["hourly"]["weather_code"][i+offset].as<int>();
336   }
337
338   return true;
339 }
340
341 bool GetWeather(WiFiClient& client) {
342   client.stop(); // close connection before sending a new request
343   HTTPClient http;
344   char uri[512];
345   sprintf(uri,"/v1/forecast?latitude=%s&longitude=%s&current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,surface_pressure,wind_speed_10m,wind_direction_10m&hourly=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,surface_pressure,wind_speed_10m,wind_direction_10m&wind_speed_unit=ms&timeformat=unixtime&forecast_days=3&timezone=%s",latitude,longitude,timezone);
346   http.useHTTP10(true);
347   Serial.printf("Connecting http://%s%s ...\n", server, uri);
348   http.begin(client, server, 80, uri);
349   int httpCode = http.GET();
350   if(httpCode == HTTP_CODE_OK) {
351     if (!DecodeWeather(http.getStream())) return false;
352     client.stop();
353     http.end();
354     return true;
355   } else {
356     Serial.printf("Forecast server connection failed, error: %s\n", http.errorToString(httpCode).c_str());
357     client.stop();
358     http.end();
359     return false;
360   }
361   http.end();
362   return true;
363 }
364
365 bool DecodeCurrentWeather(WiFiClient& json) {
366   DynamicJsonDocument doc(35 * 1024);
367   // Deserialize the JSON document
368   DeserializationError error = deserializeJson(doc, json);
369   // Test if parsing succeeds.
370   if (error) {
371     Serial.print(F("deserializeJson() failed: "));
372     Serial.println(error.c_str());
373     return false;
374   }
375   // convert it to a JsonObject
376   JsonObject root = doc.as<JsonObject>();
377
378   if (current_weather_pressure[0] && root.containsKey(current_weather_pressure)) {
379     CurrentWeather.pressure     = root[current_weather_pressure].as<float>();
380     Serial.printf("Current pressure: %f\n",CurrentWeather.pressure);
381   }
382   if (current_weather_temperature[0] && root.containsKey(current_weather_temperature)) {
383     CurrentWeather.temp     = root[current_weather_temperature].as<float>();
384     Serial.printf("Current temperature: %f\n",CurrentWeather.temp);
385   }
386   if (current_weather_humidity[0] && root.containsKey(current_weather_humidity)) {
387     CurrentWeather.humidity     = root[current_weather_humidity].as<float>();
388     Serial.printf("Current humidity: %f\n",CurrentWeather.humidity);
389   }
390   if (current_weather_windspeed[0] && root.containsKey(current_weather_windspeed)) {
391     CurrentWeather.wind_speed     = root[current_weather_windspeed].as<float>();
392     Serial.printf("Current windspeed: %f\n",CurrentWeather.wind_speed);
393   }
394
395   float Ro = (CurrentWeather.humidity/100) * 6.105 * pow(2.712828, 17.27 * CurrentWeather.temp/(237.7+CurrentWeather.temp));
396   CurrentWeather.feels_like = CurrentWeather.temp + 0.348*Ro - 0.70*CurrentWeather.wind_speed - 4.25;
397   if (current_weather_lux[0] && root.containsKey(current_weather_lux)) {
398     float wm2 = root[current_weather_lux].as<float>()/685;
399     CurrentWeather.feels_like = CurrentWeather.feels_like + 0.70*wm2/(CurrentWeather.wind_speed + 10);
400   }
401   Serial.printf("Calculated feels like: %f\n",CurrentWeather.feels_like);
402   
403   return true;
404 }
405
406
407 bool GetCurrentWeather(WiFiClient& client) {
408   if (current_weather_server) {
409     client.stop(); // close connection before sending a new request
410     HTTPClient http;
411     http.useHTTP10(true);
412     Serial.printf("Connecting http://%s%s ...\n",current_weather_server,current_weather_uri);
413     http.begin(client, current_weather_server, 80, current_weather_uri);
414     int httpCode = http.GET();
415     if(httpCode == HTTP_CODE_OK) {
416       if (!DecodeCurrentWeather(http.getStream())) return false;
417       client.stop();
418       http.end();
419       return true;
420     } else {
421       Serial.printf("Current weather server connection failed, error: %s\n", http.errorToString(httpCode).c_str());
422       client.stop();
423       http.end();
424       return false;
425     }
426     http.end();
427   }
428   return true;
429 }
430
431 void arrow(int x, int y, int asize, float aangle, int pwidth, int plength) {
432   float dx = (asize) * cos((aangle - 90) * PI / 180) + x; // calculate X position
433   float dy = (asize) * sin((aangle - 90) * PI / 180) + y; // calculate Y position
434   float x1 = 0;         float y1 = plength;
435   float x2 = pwidth / 2;  float y2 = pwidth / 2;
436   float x3 = -pwidth / 2; float y3 = pwidth / 2;
437   float angle = aangle * PI / 180 - 135;
438   float xx1 = x1 * cos(angle) - y1 * sin(angle) + dx;
439   float yy1 = y1 * cos(angle) + x1 * sin(angle) + dy;
440   float xx2 = x2 * cos(angle) - y2 * sin(angle) + dx;
441   float yy2 = y2 * cos(angle) + x2 * sin(angle) + dy;
442   float xx3 = x3 * cos(angle) - y3 * sin(angle) + dx;
443   float yy3 = y3 * cos(angle) + x3 * sin(angle) + dy;
444   display.fillTriangle(xx1, yy1, xx3, yy3, xx2, yy2, GxEPD_BLACK);
445 }
446
447 void drawString(int x, int y, String text, alignment align) {
448   uint16_t w, h;
449   display.setTextWrap(false);
450   w = u8g2Fonts.getUTF8Width(text.c_str());
451   h = u8g2Fonts.getFontAscent();
452   if (align == RIGHT)  x = x - w;
453   if (align == CENTER) x = x - w / 2;
454   u8g2Fonts.setCursor(x, y + h / 2);
455   u8g2Fonts.print(text);
456 }
457
458 String WindDegToDirection(float winddirection) {
459   if (winddirection >= 337.5 || winddirection < 22.5)  return "С";
460   if (winddirection >=  22.5 && winddirection < 67.5)  return "СВ";
461   if (winddirection >=  67.5 && winddirection < 112.5) return "В";
462   if (winddirection >= 112.5 && winddirection < 157.5) return "ЮВ";
463   if (winddirection >= 157.5 && winddirection < 202.5) return "Ю";
464   if (winddirection >= 202.5 && winddirection < 247.5) return "ЮЗ";
465   if (winddirection >= 247.5 && winddirection < 292.5) return "З";
466   if (winddirection >= 292.5 && winddirection < 337.5) return "СЗ";
467   return "?";
468 }
469
470 void DrawBlock(int x1, int x2, int y1, int y2) {
471   display.fillRect(x1+3, y1+3, x2-x1, y2-y1, GxEPD_BLACK);
472   display.fillRect(x1, y1, x2-x1, y2-y1, GxEPD_WHITE);
473   display.drawRect(x1, y1, x2-x1, y2-y1, GxEPD_BLACK);
474 }
475
476 void DisplayCurrent(int x1, int x2, int y1, int y2, WeatherRecord weather) {
477
478   DrawBlock(x1,x2,y1,y2);
479
480   int x = (x1 + x2)/2;
481   int y = (y1 + y2)/2;
482   int h = (y2 - y1)/3;
483   int w = (x2 - x1)/4;
484   if (h<w) { w = h; }
485   SetNumFont(w);
486   drawString(x, y1+w*2/3, String(weather.temp,1) + "°", CENTER);
487   SetCyrFont(w/4);
488   drawString(x, y1+w*3/2, "ощущается как", CENTER);
489   SetNumFont(w*2/3);
490   drawString(x, y1+w*2, String(weather.feels_like,1) + "°", CENTER);
491   SetCyrFont(w/2);
492   drawString(x, y2-w/3, String(weather.humidity,0) + "% " + String(weather.pressure,0) + "мм", CENTER);
493 }
494
495 void DisplayWind(int x1, int x2, int y1, int y2, WeatherRecord weather) {
496
497   DrawBlock(x1,x2,y1,y2);
498
499   int r, dxo, dyo, dxi, dyi;
500   int x = (x1 + x2)/2;
501   int y = (y1 + y2)/2;
502   int r1 = (x2 - x1)/2;
503   int r2 = (y2 - y1)/2;
504   if (r1>r2) { r = r2; } else { r = r1; }
505   int w = r/5;
506   if (w>40) { w = 40; }
507   r = r - w;
508
509   String speedstr = String(weather.wind_speed,1);
510   speedstr.trim();
511
512   arrow (x, y, r-w, weather.wind_deg, w/2, w*7/8);
513
514   display.drawCircle(x, y, r, GxEPD_BLACK);     // Draw compass circle
515   display.drawCircle(x, y, r + 1, GxEPD_BLACK); // Draw compass circle
516   display.drawCircle(x, y, r - w, GxEPD_BLACK); // Draw compass inner circle
517   display.drawCircle(x, y, r - w + 1, GxEPD_BLACK); // Draw compass inner circle
518   for (float a = 0; a < 360; a = a + 30) {
519     dxo = r * cos((a - 90) * PI / 180);
520     dyo = r * sin((a - 90) * PI / 180);
521     dxi = dxo * 0.9;
522     dyi = dyo * 0.9;
523     display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK);
524   }
525
526   SetCyrFont(w*2/3);
527   drawString(x, y - r - w/2, "С", CENTER);
528   drawString(x, y + r + w/2, "Ю", CENTER);
529   drawString(x - r - w/2, y, "З", CENTER);
530   drawString(x + r + w/2, y, "В", CENTER);
531   drawString(x - (r + w/2)*10/14, y - (r + w/2)*10/14, "сз", CENTER);
532   drawString(x - (r + w/2)*10/14, y + (r + w/2)*10/14, "юз", CENTER);
533   drawString(x + (r + w/2)*10/14, y + (r + w/2)*10/14, "юв", CENTER);
534   drawString(x + (r + w/2)*10/14, y - (r + w/2)*10/14, "св", CENTER);
535   SetNumFont(r*5/8);
536   drawString(x, y-r/8, speedstr, CENTER);
537   SetCyrFont(w);
538   drawString(x, y+r*3/8, "м/с", CENTER);
539
540 }
541
542 void drawsun(int x, int y, int scale, int linesize) {
543   display.fillRect(x - scale * 2, y, scale * 4, linesize, GxEPD_BLACK);
544   display.fillRect(x, y - scale * 2, linesize, scale * 4, GxEPD_BLACK);
545   display.drawLine(x - scale * 1.3, y - scale * 1.3, x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
546   display.drawLine(x - scale * 1.3, y + scale * 1.3, x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
547   if (linesize>1) {
548     display.drawLine(1 + x - scale * 1.3, y - scale * 1.3, 1 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
549     display.drawLine(2 + x - scale * 1.3, y - scale * 1.3, 2 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
550     display.drawLine(3 + x - scale * 1.3, y - scale * 1.3, 3 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
551     display.drawLine(1 + x - scale * 1.3, y + scale * 1.3, 1 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
552     display.drawLine(2 + x - scale * 1.3, y + scale * 1.3, 2 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
553     display.drawLine(3 + x - scale * 1.3, y + scale * 1.3, 3 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
554   }
555   display.fillCircle(x, y, scale * 1.3, GxEPD_WHITE);
556   display.fillCircle(x, y, scale, GxEPD_BLACK);
557   display.fillCircle(x, y, scale - linesize, GxEPD_WHITE);
558 }
559
560 void drawcloud(int x, int y, int scale, int linesize) {
561   //Draw cloud outer
562   display.fillCircle(x - scale * 3, y, scale, GxEPD_BLACK);                // Left most circle
563   display.fillCircle(x + scale * 3, y, scale, GxEPD_BLACK);                // Right most circle
564   display.fillCircle(x - scale, y - scale, scale * 1.4, GxEPD_BLACK);    // left middle upper circle
565   display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75, GxEPD_BLACK); // Right middle upper circle
566   display.fillRect(x - scale * 3 - 1, y - scale, scale * 6, scale * 2 + 1, GxEPD_BLACK); // Upper and lower lines
567   //Clear cloud inner
568   display.fillCircle(x - scale * 3, y, scale - linesize, GxEPD_WHITE);            // Clear left most circle
569   display.fillCircle(x + scale * 3, y, scale - linesize, GxEPD_WHITE);            // Clear right most circle
570   display.fillCircle(x - scale, y - scale, scale * 1.4 - linesize, GxEPD_WHITE);  // left middle upper circle
571   display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75 - linesize, GxEPD_WHITE); // Right middle upper circle
572   display.fillRect(x - scale * 3 + 2, y - scale + linesize - 1, scale * 5.9, scale * 2 - linesize * 2 + 2, GxEPD_WHITE); // Upper and lower lines
573 }
574
575 void drawraindrop(int x, int y, int scale) {
576   display.fillCircle(x, y, scale / 4, GxEPD_BLACK);
577   display.fillTriangle(x - scale / 4, y, x, y - scale, x + scale / 4, y , GxEPD_BLACK);
578   x = x + scale * 1.5; y = y + scale / 2;
579   display.fillCircle(x, y, scale / 4, GxEPD_BLACK);
580   display.fillTriangle(x - scale / 4, y, x, y - scale, x + scale / 4, y , GxEPD_BLACK);
581 }
582
583 void drawrain(int x, int y, int scale) {
584   for (int d = 0; d < 4; d++) {
585     drawraindrop(x + scale * (7.8 - d * 1.85) - scale * 5.2, y + scale * 2 - scale / 6, scale / 1.6);
586   }
587 }
588
589 void drawsnow(int x, int y, int scale) {
590   int dxo, dyo, dxi, dyi;
591   int delta = -scale/5;
592   for (int flakes = 0; flakes < 5; flakes++) {
593     for (int i = 0; i < 360; i = i + 60) {
594       dxo = 0.5 * scale * cos((i - 90) * 3.14 / 180); dxi = dxo * 0.1;
595       dyo = 0.5 * scale * sin((i - 90) * 3.14 / 180); dyi = dyo * 0.1;
596       display.drawLine(dxo + x + flakes * 1.5 * scale - scale * 3, dyo + y + delta + scale * 1.8, dxi + x + 0 + flakes * 1.5 * scale - scale * 3, dyi + y + delta + scale * 1.8, GxEPD_BLACK);
597     }
598     delta = - delta;
599   }
600 }
601
602 void drawtstorm(int x, int y, int scale) {
603   y = y + scale / 2;
604   int delta = -scale/5;
605   for (int i = 0; i < 4; i++) {
606     delta = - delta;
607     display.drawLine(x - scale * 3 + scale * i * 1.5 + 0, y + delta + scale * 1.4, x - scale * 2.5 + scale * i * 1.5 + 0, y + delta + scale * 0.9, GxEPD_BLACK);
608     if (scale > 15) {
609       display.drawLine(x - scale * 3 + scale * i * 1.5 + 1, y + delta + scale * 1.4, x - scale * 2.5 + scale * i * 1.5 + 1, y + delta + scale * 0.9, GxEPD_BLACK);
610       display.drawLine(x - scale * 3 + scale * i * 1.5 + 2, y + delta + scale * 1.4, x - scale * 2.5 + scale * i * 1.5 + 2, y + delta + scale * 0.9, GxEPD_BLACK);
611     }
612     display.drawLine(x - scale * 3 + scale * i * 1.5, y + delta + scale * 1.4 + 0, x - scale * 2 + scale * i * 1.5 + 0, y + delta + scale * 1.4 + 0, GxEPD_BLACK);
613     if (scale > 15) {
614       display.drawLine(x - scale * 3 + scale * i * 1.5, y + delta + scale * 1.4 + 1, x - scale * 2 + scale * i * 1.5 + 0, y + delta + scale * 1.4 + 1, GxEPD_BLACK);
615       display.drawLine(x - scale * 3 + scale * i * 1.5, y + delta + scale * 1.4 + 2, x - scale * 2 + scale * i * 1.5 + 0, y + delta + scale * 1.4 + 2, GxEPD_BLACK);
616     }
617     display.drawLine(x - scale * 2.5 + scale * i * 1.4 + 0, y + delta + scale * 2.4, x - scale * 2 + scale * i * 1.5 + 0, y + delta + scale * 1.4, GxEPD_BLACK);
618     if (scale > 15) {
619       display.drawLine(x - scale * 2.5 + scale * i * 1.4 + 1, y + delta + scale * 2.4, x - scale * 2 + scale * i * 1.5 + 1, y + delta + scale * 1.4, GxEPD_BLACK);
620       display.drawLine(x - scale * 2.5 + scale * i * 1.4 + 2, y + delta + scale * 2.4, x - scale * 2 + scale * i * 1.5 + 2, y + delta + scale * 1.4, GxEPD_BLACK);
621     }
622   }
623 }
624
625 void drawfog(int x, int y, int scale, int linesize) {
626   if (scale < 15) {
627     y -= 10;
628     linesize = 1;
629   }
630   for (int i = 0; i < 6; i++) {
631     display.fillRect(x - scale * 3, y + scale * 1.5, scale * 6, linesize, GxEPD_BLACK);
632     display.fillRect(x - scale * 3, y + scale * 2.0, scale * 6, linesize, GxEPD_BLACK);
633     display.fillRect(x - scale * 3, y + scale * 2.5, scale * 6, linesize, GxEPD_BLACK);
634   }
635 }
636
637 void Sunny(int x, int y, int scale) {
638   int linesize = 1;
639   if (scale>15) linesize=3;
640   drawsun(x, y, scale*6/5, linesize);
641 }
642
643 void MostlySunny(int x, int y, int scale) {
644   int linesize = 1, offset = 5;
645   if (scale>15) {
646     linesize = 3;
647     offset = 15;
648   }
649   drawcloud(x, y + offset, scale, linesize);
650   drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize);
651 }
652
653 void MostlyCloudy(int x, int y, int scale) {
654   int linesize = 1, offset = 5;
655   if (scale>15) {
656     linesize = 3;
657     offset = 15;
658   }
659   drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize);
660   drawcloud(x, y + offset, scale, linesize);
661 }
662
663 void Cloudy(int x, int y, int scale) {
664   int linesize = 1;
665   if (scale<=15) {
666     drawcloud(x, y+5, scale, linesize);
667   }
668   else {
669     linesize = 5;
670     y += scale/2;
671     drawcloud(x + scale * 2.5, y - scale * 1.8, scale/1.6, linesize); // Cloud top right
672     drawcloud(x - scale * 2, y - scale * 2, scale/2, linesize); // Cloud top left
673     drawcloud(x, y, scale, linesize);       // Main cloud
674   }
675 }
676
677 void Rain(int x, int y, int scale) {
678   int linesize = 3;
679   if (scale<15) {
680     linesize = 1;
681   }
682   drawcloud(x, y-scale/3, scale, linesize);
683   drawrain(x, y-scale/3, scale);
684 }
685
686 void ExpectRain(int x, int y, int scale) {
687   int linesize = 3;
688   if (scale<15) {
689     linesize = 1;
690   }
691   drawsun(x - scale * 1.8, y - scale * 1.6, scale, linesize);
692   drawcloud(x, y+scale/5, scale, linesize);
693   drawrain(x, y, scale);
694 }
695
696 void Tstorms(int x, int y, int scale) {
697   int linesize = 3;
698   if (scale<15) {
699     linesize = 1;
700   }
701   drawcloud(x, y, scale, linesize);
702   drawtstorm(x, y, scale);
703 }
704
705 void Snow(int x, int y, int scale) {
706   int linesize = 3;
707   if (scale<15) {
708     linesize = 1;
709   }
710   drawcloud(x, y, scale, linesize);
711   drawsnow(x, y, scale);
712 }
713
714 void Fog(int x, int y, int scale) {
715   int linesize = 3;
716   if (scale<15) {
717     linesize = 1;
718   }
719   drawcloud(x, y - 5, scale, linesize);
720   drawfog(x, y - 5, scale, linesize);
721 }
722
723 void Haze(int x, int y, int scale) {
724   int linesize = 3;
725   if (scale<15) {
726     linesize = 1;
727   }
728   drawsun(x, y - 5, scale*6/5, linesize);
729   drawfog(x, y - 5, scale, linesize);
730 }
731
732 void DisplayIcon(int x1, int x2, int y1, int y2, WeatherRecord weather) {
733
734   DrawBlock(x1,x2,y1,y2);
735
736   int x = (x1 + x2)/2;
737   int y = (y1 + y2)/2;
738   int h = (y2 - y1);
739   int w = (x2 - x1);
740
741   int scale = w/14;
742   int fscale = w/12;
743
744   int wmo = weather.wmo;
745   String description;
746
747   if (wmo == 0) { 
748     Sunny(x, y, scale);
749     description = "ясно";
750   } else if (wmo == 1) {
751     MostlySunny(x, y, scale);
752     description = "премущественно ясно";
753   } else if (wmo == 2) {
754     MostlyCloudy(x, y, scale);
755     description = "переменная облачность";
756   } else if (wmo == 3) {
757     Cloudy(x, y, scale);
758     description = "пасмурно";
759   } else if (wmo == 61) {
760     Rain(x, y, scale);
761     description = "небольшой дождь";
762   } else if (wmo == 63) {
763     Rain(x, y, scale);
764     description = "дождь";
765   } else if (wmo == 65) {
766     Rain(x, y, scale);
767     description = "сильный дождь";
768   } else if (wmo == 51) {
769     Rain(x, y, scale);
770     description = "небольшая морось";
771   } else if (wmo == 53) {
772     Rain(x, y, scale);
773     description = "морось";
774   } else if (wmo == 55) {
775     Rain(x, y, scale);
776     description = "сильная морось";
777   } else if (wmo == 66) {
778     Rain(x, y, scale);
779     description = "ледяной дождь";
780   } else if (wmo == 67) {
781     Rain(x, y, scale);
782     description = "сильный ледяной дождь";
783   } else if (wmo == 56) {
784     Rain(x, y, scale);
785     description = "ледяная морось";
786   } else if (wmo == 57) {
787     Rain(x, y, scale);
788     description = "сильная ледяная морось";
789   } else if (wmo == 80) {
790     ExpectRain(x, y, scale);
791     description = "возможны ливни";
792   } else if (wmo == 81) {
793     ExpectRain(x, y, scale);
794     description = "ливни";
795   } else if (wmo == 82) {
796     ExpectRain(x, y, scale);
797     description = "сильные ливни";
798   } else if (wmo == 95) {
799     Tstorms(x, y, scale);
800     description = "гроза";
801   } else if (wmo == 96) {
802     Tstorms(x, y, scale);
803     description = "гроза с градом";
804   } else if (wmo == 97) {
805     Tstorms(x, y, scale);
806     description = "сильная гроза";
807   } else if (wmo == 71) {
808     Snow(x, y, scale);
809     description = "слабый снег";
810   } else if (wmo == 73) {
811     Snow(x, y, scale);
812     description = "снег";
813   } else if (wmo == 75) {
814     Snow(x, y, scale);
815     description = "сильный снег";
816   } else if (wmo == 77) {
817     Snow(x, y, scale);
818     description = "снежная крупа";
819   } else if (wmo == 85) {
820     Snow(x, y, scale);
821     description = "возможна метель";
822   } else if (wmo == 86) {
823     Snow(x, y, scale);
824     description = "метель";
825   } else if (wmo == 48) {
826     Haze(x, y, scale);
827     description = "ледяной туман";
828   } else if (wmo == 45) {
829     Fog(x, y, scale);
830     description = "туман";
831   }
832   
833   SetCyrFont(fscale);
834   drawString(x, y2-fscale*2/3, description, CENTER); 
835
836 }
837
838 void DisplayUpdate(int x1, int x2, int y1, int y2, WeatherRecord weather) {
839
840   DrawBlock(x1,x2,y1,y2);
841
842   int x = (x1 + x2)/2;
843   int y = (y1 + y2)/2;
844   int h = (y2 - y1)/2;
845   int w = (x2 - x1)/16;
846   if (h<w) { w = h; }
847   SetCyrFont(w);
848   drawString(x, y1+w, "Прогноз от " + UnixTime(weather.dt + TimeZoneOffset), CENTER);
849   
850 }
851
852 void DisplayForecast(int x1, int x2, int y1, int y2, WeatherRecord weather) {
853
854   DrawBlock(x1,x2,y1,y2);
855
856   int x = (x1 + x2)/2;
857   int y = (y1 + y2)/2;
858   int h = (y2 - y1);
859   int w = (x2 - x1);
860
861   int scale = w/12;
862   int fscale = w/6;
863   if (fscale<6) fscale = 6;
864
865   String temperature = String(weather.temp,1);
866
867   int wmo = weather.wmo;
868
869   if (wmo == 0) { 
870     Sunny(x, y, scale);
871   } else if (wmo == 1) {
872     MostlySunny(x, y, scale);
873   } else if (wmo == 2) {
874     MostlyCloudy(x, y, scale);
875   } else if (wmo == 3) {
876     Cloudy(x, y, scale);
877   } else if (wmo == 61) {
878     Rain(x, y, scale);
879   } else if (wmo == 63) {
880     Rain(x, y, scale);
881   } else if (wmo == 65) {
882     Rain(x, y, scale);
883   } else if (wmo == 51) {
884     Rain(x, y, scale);
885   } else if (wmo == 53) {
886     Rain(x, y, scale);
887   } else if (wmo == 55) {
888     Rain(x, y, scale);
889   } else if (wmo == 66) {
890     Rain(x, y, scale);
891   } else if (wmo == 67) {
892     Rain(x, y, scale);
893   } else if (wmo == 56) {
894     Rain(x, y, scale);
895   } else if (wmo == 57) {
896     Rain(x, y, scale);
897   } else if (wmo == 80) {
898     ExpectRain(x, y, scale);
899   } else if (wmo == 81) {
900     ExpectRain(x, y, scale);
901   } else if (wmo == 82) {
902     ExpectRain(x, y, scale);
903   } else if (wmo == 95) {
904     Tstorms(x, y, scale);
905   } else if (wmo == 96) {
906     Tstorms(x, y, scale);
907   } else if (wmo == 97) {
908     Tstorms(x, y, scale);
909   } else if (wmo == 71) {
910     Snow(x, y, scale);
911   } else if (wmo == 73) {
912     Snow(x, y, scale);
913   } else if (wmo == 75) {
914     Snow(x, y, scale);
915   } else if (wmo == 77) {
916     Snow(x, y, scale);
917   } else if (wmo == 85) {
918     Snow(x, y, scale);
919   } else if (wmo == 86) {
920     Snow(x, y, scale);
921   } else if (wmo == 48) {
922     Haze(x, y, scale);
923   } else if (wmo == 45) {
924     Fog(x, y, scale);
925   }
926
927   SetCyrFont(fscale);
928   drawString(x, y1+fscale*2/3, UnixTimeOnly(weather.dt+ TimeZoneOffset), CENTER); 
929   drawString(x, y2-fscale*2/3, temperature, CENTER); 
930
931 }
932
933 void DisplayGraph(int x1, int x2, int y1, int y2, int TimeArray[], float DataArray[], int readings, boolean barchart_mode, String label) {
934
935   DrawBlock(x1,x2,y1,y2);
936
937   float maxY = -10000;
938   float minY =  10000;
939
940   int last_x, last_y;
941
942   int scale = (y2 - y1)/10;
943
944   SetCyrFont(scale);
945   drawString((x1+x2)/2, y1+scale*2/3, label, CENTER); 
946   
947   int x_pos = x1 + 3 + scale*2;
948   int y_pos = y2 - 3;
949
950   int h = y2 - y1 - 6 - scale;
951   int w = x2 - x1 - 8 - scale*2;
952
953   float x_new, y_new;
954   
955   for (int i = 1; i < readings; i++ ) {
956     if (DataArray[i] >= maxY) maxY = DataArray[i];
957     if (DataArray[i] <= minY) minY = DataArray[i];
958   }
959   maxY = round(maxY + 0.5);
960   minY = round(minY);
961
962   last_x = x_pos + 1;
963   last_y = y_pos - (constrain(DataArray[0], minY, maxY) - minY) / (maxY - minY) * h -1;
964   
965   display.drawRect(x_pos, y_pos-h, w, h, GxEPD_BLACK);
966
967   for (int gx = 1; gx < readings; gx++) {
968     x_new = x_pos + gx * w / (readings - 1) - 1 ;
969     y_new = y_pos - (constrain(DataArray[gx], minY, maxY) - minY) / (maxY - minY) * h -1;
970     if (barchart_mode) {
971       if (DataArray[gx]!=0) {
972         display.fillRect(x_new, y_new, (w / readings) - 1, y_pos - y_new, GxEPD_BLACK);
973       }
974     } else {
975       display.drawLine(last_x, last_y, x_new, y_new, GxEPD_BLACK);
976     }
977     last_x = x_new;
978     last_y = y_new;
979   }
980
981 #define y_minor_axis 5
982 #define number_of_dashes 20
983   SetNumFont(scale/1.3);
984   for (int spacing = 1; spacing < y_minor_axis; spacing++) {
985     for (int j = 0; j < number_of_dashes; j++) {
986       if (spacing < y_minor_axis) display.drawFastHLine((x_pos + 3 + j * w / number_of_dashes), y_pos - (h * spacing / y_minor_axis), w / (2 * number_of_dashes), GxEPD_BLACK);
987     }
988     drawString(x_pos - 1, y_pos - h * spacing / y_minor_axis, String((minY + (float)(maxY - minY) / y_minor_axis * spacing + 0.01), 1), RIGHT);
989   }
990 }
991
992 void DisplayWeather() {
993   Serial.println("Displaying...");
994   DisplayWind(20,235,20,225, CurrentWeather);
995   DisplayCurrent(240,515,20,225,CurrentWeather);
996   DisplayIcon(520,775,60,225,CurrentWeather);
997   DisplayUpdate(520,775,20,55,CurrentWeather);
998   int x=20;
999   for (int i=0; i<MaxHourlyFC; i+=3) {
1000     DisplayForecast(x,x+90,235,305,HourlyForecasts[i]);
1001     x = x + 95;
1002     if (x+95>780) break;
1003   }
1004   DisplayGraph(20,268,315,460,HourlyDT,HourlyTemp,MaxHourlyFC,false,"Температура");
1005   DisplayGraph(273,522,315,460,HourlyDT,HourlyPressure,MaxHourlyFC,false,"Давление");
1006   DisplayGraph(527,775,315,460,HourlyDT,HourlyPrecip,MaxHourlyFC,true,"Осадки");
1007   display.display();
1008 }
1009
1010 void loop() {
1011
1012   delay(2000);
1013
1014   WiFiManager wifiManager;
1015   WiFi.mode(WIFI_STA);
1016   wifiManager.setConnectTimeout(10);
1017
1018   if (WiFi.SSID().isEmpty()) {
1019   
1020     wifiManager.setTimeout(600);
1021     WiFiSetup();
1022   
1023   } else if (!wifiManager.autoConnect()) {
1024
1025     Serial.println("failed to connect to stored SSID and hit timeout");
1026     wifiManager.setTimeout(600);
1027     WiFi.mode(WIFI_AP);
1028     WiFiSetup();
1029   
1030   } else if (Serial.available() && Serial.read() == 'c') {
1031       
1032     wifiManager.setTimeout(600);
1033     WiFiSetup();
1034   
1035   } else {
1036   
1037     // put your main code here, to run repeatedly:
1038     byte Attempts = 1;
1039     WiFiClient client;   // wifi client object
1040     for (byte i=1; i<=3; i++) {
1041       if (GetWeather(client)) {
1042         GetCurrentWeather(client);
1043         WiFi.mode(WIFI_OFF);
1044         btStop();
1045         DisplayWeather();
1046
1047         int CurrentMin, CurrentSec;
1048         struct tm timeinfo;
1049         getLocalTime(&timeinfo, 10000);
1050         CurrentMin  = timeinfo.tm_min;
1051         CurrentSec  = timeinfo.tm_sec;
1052         long SleepTimer = (SleepDuration * 60 - ((CurrentMin % SleepDuration) * 60 + CurrentSec));
1053         Serial.print("Going to long sleep for ");
1054         Serial.print(SleepTimer);
1055         Serial.println(" seconds now");
1056         delay(1000);
1057         Serial.flush(); 
1058
1059         esp_sleep_enable_timer_wakeup((SleepTimer+5)*1000*1000);
1060         esp_deep_sleep_start();
1061       }
1062     }
1063
1064   }  
1065
1066   Serial.println("Going to short sleep for 120 seconds now ");
1067   delay(1000);
1068   Serial.flush(); 
1069   esp_sleep_enable_timer_wakeup(120*1000*1000);
1070   esp_deep_sleep_start();
1071
1072 }
1073
1074