1 #include <FS.h> //this needs to be first, or it all crashes and burns...
3 #include <WiFi.h> //https://github.com/esp8266/Arduino
7 #include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
9 #include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
11 #include <time.h> // Built-in
12 #include <SPI.h> // Built-in
14 #include <HTTPClient.h>
16 #include <GxEPD2_BW.h>
17 #include <U8g2_for_Adafruit_GFX.h>
22 #include "weather_types.h"
24 #define SCREEN_WIDTH 800 // Set for landscape mode
25 #define SCREEN_HEIGHT 480
27 enum alignment {LEFT, RIGHT, CENTER};
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;
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
42 U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
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
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";
60 const char server[] = "api.open-meteo.com";
62 int SleepDuration = 10;
64 //flag for saving data
65 bool shouldSaveConfig = false;
67 //callback notifying us of the need to save config
68 void saveConfigCallback () {
69 Serial.println("Should save config");
70 shouldSaveConfig = true;
74 display.init(115200, true, 2); // init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode)
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
83 display.fillScreen(GxEPD_WHITE);
84 display.setFullWindow();
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);
104 //Local intialization. Once its business is done, there is no need to keep it around
105 WiFiManager wifiManager;
107 //set config save notify callback
108 wifiManager.setSaveConfigCallback(saveConfigCallback);
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);
122 wifiManager.setTimeout(300);
124 if (WiFi. status() != WL_CONNECTED) {
130 if (!wifiManager.startConfigPortal()) {
131 Serial.println("failed to connect and hit timeout");
133 //reset and try again, or maybe put it to deep sleep
137 //if you get here you have connected to the WiFi
138 Serial.println("connected...");
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());
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;
168 File configFile = SPIFFS.open("/config.json", "w");
170 Serial.println("failed to open config file for writing");
173 serializeJson(json, Serial);
174 serializeJson(json, configFile);
182 byte need_setup = false;
183 // put your setup code here, to run once:
184 Serial.begin(115200);
187 //read configuration from FS json
188 Serial.println("mounting FS...");
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");
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]);
202 configFile.readBytes(buf.get(), size);
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"]); }
220 Serial.println("failed to load json config");
230 Serial.println("failed to mount FS");
237 WiFiManager wifiManager;
238 wifiManager.setTimeout(10);
239 if(!wifiManager.autoConnect()) {
240 Serial.println("failed to connect and hit timeout");
247 Serial.println("\nlocal ip");
248 Serial.println(WiFi.localIP());
255 // int SunRise, SunSet;
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];
269 String UnixTime(int unix_time) {
270 time_t tm = unix_time;
271 struct tm *now_tm = localtime(&tm);
273 strftime(output, sizeof(output), "%H:%M %d/%m/%y", now_tm);
277 String UnixTimeOnly(int unix_time) {
278 time_t tm = unix_time;
279 struct tm *now_tm = localtime(&tm);
281 strftime(output, sizeof(output), "%H:%M", now_tm);
285 String UnixDate(int unix_time) {
286 time_t tm = unix_time;
287 struct tm *now_tm = localtime(&tm);
289 strftime(output, sizeof(output), "%d/%m/%y", now_tm);
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.
299 Serial.print(F("deserializeJson() failed: "));
300 Serial.println(error.c_str());
303 // convert it to a JsonObject
304 JsonObject root = doc.as<JsonObject>();
306 TimeZoneOffset = root["utc_offset_seconds"].as<int>();
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>();
319 struct timeval now = { .tv_sec = CurrentWeather.dt };
320 settimeofday(&now, NULL);
322 int firstForecastDT = root["hourly"]["time"][0].as<int>();
324 int offset = (CurrentWeather.dt - firstForecastDT) / 3600;
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>();
341 bool GetWeather(WiFiClient& client) {
342 client.stop(); // close connection before sending a new request
345 sprintf(uri,"/v1/forecast?latitude=%s&longitude=%s¤t=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;
356 Serial.printf("Forecast server connection failed, error: %s\n", http.errorToString(httpCode).c_str());
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.
371 Serial.print(F("deserializeJson() failed: "));
372 Serial.println(error.c_str());
375 // convert it to a JsonObject
376 JsonObject root = doc.as<JsonObject>();
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);
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);
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);
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);
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);
401 Serial.printf("Calculated feels like: %f\n",CurrentWeather.feels_like);
407 bool GetCurrentWeather(WiFiClient& client) {
408 if (current_weather_server) {
409 client.stop(); // close connection before sending a new request
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;
421 Serial.printf("Current weather server connection failed, error: %s\n", http.errorToString(httpCode).c_str());
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);
447 void drawString(int x, int y, String text, alignment align) {
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);
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 "СЗ";
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);
476 void DisplayCurrent(int x1, int x2, int y1, int y2, WeatherRecord weather) {
478 DrawBlock(x1,x2,y1,y2);
486 drawString(x, y1+w*2/3, String(weather.temp,1) + "°", CENTER);
488 drawString(x, y1+w*3/2, "ощущается как", CENTER);
490 drawString(x, y1+w*2, String(weather.feels_like,1) + "°", CENTER);
492 drawString(x, y2-w/3, String(weather.humidity,0) + "% " + String(weather.pressure,0) + "мм", CENTER);
495 void DisplayWind(int x1, int x2, int y1, int y2, WeatherRecord weather) {
497 DrawBlock(x1,x2,y1,y2);
499 int r, dxo, dyo, dxi, dyi;
502 int r1 = (x2 - x1)/2;
503 int r2 = (y2 - y1)/2;
504 if (r1>r2) { r = r2; } else { r = r1; }
506 if (w>40) { w = 40; }
509 String speedstr = String(weather.wind_speed,1);
512 arrow (x, y, r-w, weather.wind_deg, w/2, w*7/8);
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);
523 display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK);
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);
536 drawString(x, y-r/8, speedstr, CENTER);
538 drawString(x, y+r*3/8, "м/с", CENTER);
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);
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);
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);
560 void drawcloud(int x, int y, int scale, int linesize) {
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
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
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);
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);
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);
602 void drawtstorm(int x, int y, int scale) {
604 int delta = -scale/5;
605 for (int i = 0; i < 4; i++) {
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);
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);
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);
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);
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);
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);
625 void drawfog(int x, int y, int scale, int linesize) {
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);
637 void Sunny(int x, int y, int scale) {
639 if (scale>15) linesize=3;
640 drawsun(x, y, scale*6/5, linesize);
643 void MostlySunny(int x, int y, int scale) {
644 int linesize = 1, offset = 5;
649 drawcloud(x, y + offset, scale, linesize);
650 drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize);
653 void MostlyCloudy(int x, int y, int scale) {
654 int linesize = 1, offset = 5;
659 drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize);
660 drawcloud(x, y + offset, scale, linesize);
663 void Cloudy(int x, int y, int scale) {
666 drawcloud(x, y+5, scale, linesize);
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
677 void Rain(int x, int y, int scale) {
682 drawcloud(x, y-scale/3, scale, linesize);
683 drawrain(x, y-scale/3, scale);
686 void ExpectRain(int x, int y, int scale) {
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);
696 void Tstorms(int x, int y, int scale) {
701 drawcloud(x, y, scale, linesize);
702 drawtstorm(x, y, scale);
705 void Snow(int x, int y, int scale) {
710 drawcloud(x, y, scale, linesize);
711 drawsnow(x, y, scale);
714 void Fog(int x, int y, int scale) {
719 drawcloud(x, y - 5, scale, linesize);
720 drawfog(x, y - 5, scale, linesize);
723 void Haze(int x, int y, int scale) {
728 drawsun(x, y - 5, scale*6/5, linesize);
729 drawfog(x, y - 5, scale, linesize);
732 void DisplayIcon(int x1, int x2, int y1, int y2, WeatherRecord weather) {
734 DrawBlock(x1,x2,y1,y2);
744 int wmo = weather.wmo;
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) {
758 description = "пасмурно";
759 } else if (wmo == 61) {
761 description = "небольшой дождь";
762 } else if (wmo == 63) {
764 description = "дождь";
765 } else if (wmo == 65) {
767 description = "сильный дождь";
768 } else if (wmo == 51) {
770 description = "небольшая морось";
771 } else if (wmo == 53) {
773 description = "морось";
774 } else if (wmo == 55) {
776 description = "сильная морось";
777 } else if (wmo == 66) {
779 description = "ледяной дождь";
780 } else if (wmo == 67) {
782 description = "сильный ледяной дождь";
783 } else if (wmo == 56) {
785 description = "ледяная морось";
786 } else if (wmo == 57) {
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) {
809 description = "слабый снег";
810 } else if (wmo == 73) {
812 description = "снег";
813 } else if (wmo == 75) {
815 description = "сильный снег";
816 } else if (wmo == 77) {
818 description = "снежная крупа";
819 } else if (wmo == 85) {
821 description = "возможна метель";
822 } else if (wmo == 86) {
824 description = "метель";
825 } else if (wmo == 48) {
827 description = "ледяной туман";
828 } else if (wmo == 45) {
830 description = "туман";
834 drawString(x, y2-fscale*2/3, description, CENTER);
838 void DisplayUpdate(int x1, int x2, int y1, int y2, WeatherRecord weather) {
840 DrawBlock(x1,x2,y1,y2);
845 int w = (x2 - x1)/16;
848 drawString(x, y1+w, "Прогноз от " + UnixTime(weather.dt + TimeZoneOffset), CENTER);
852 void DisplayForecast(int x1, int x2, int y1, int y2, WeatherRecord weather) {
854 DrawBlock(x1,x2,y1,y2);
863 if (fscale<6) fscale = 6;
865 String temperature = String(weather.temp,1);
867 int wmo = weather.wmo;
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) {
877 } else if (wmo == 61) {
879 } else if (wmo == 63) {
881 } else if (wmo == 65) {
883 } else if (wmo == 51) {
885 } else if (wmo == 53) {
887 } else if (wmo == 55) {
889 } else if (wmo == 66) {
891 } else if (wmo == 67) {
893 } else if (wmo == 56) {
895 } else if (wmo == 57) {
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) {
911 } else if (wmo == 73) {
913 } else if (wmo == 75) {
915 } else if (wmo == 77) {
917 } else if (wmo == 85) {
919 } else if (wmo == 86) {
921 } else if (wmo == 48) {
923 } else if (wmo == 45) {
928 drawString(x, y1+fscale*2/3, UnixTimeOnly(weather.dt+ TimeZoneOffset), CENTER);
929 drawString(x, y2-fscale*2/3, temperature, CENTER);
933 void DisplayGraph(int x1, int x2, int y1, int y2, int TimeArray[], float DataArray[], int readings, boolean barchart_mode, String label) {
935 DrawBlock(x1,x2,y1,y2);
942 int scale = (y2 - y1)/10;
945 drawString((x1+x2)/2, y1+scale*2/3, label, CENTER);
947 int x_pos = x1 + 3 + scale*2;
950 int h = y2 - y1 - 6 - scale;
951 int w = x2 - x1 - 8 - scale*2;
955 for (int i = 1; i < readings; i++ ) {
956 if (DataArray[i] >= maxY) maxY = DataArray[i];
957 if (DataArray[i] <= minY) minY = DataArray[i];
959 maxY = round(maxY + 0.5);
963 last_y = y_pos - (constrain(DataArray[0], minY, maxY) - minY) / (maxY - minY) * h -1;
965 display.drawRect(x_pos, y_pos-h, w, h, GxEPD_BLACK);
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;
971 if (DataArray[gx]!=0) {
972 display.fillRect(x_new, y_new, (w / readings) - 1, y_pos - y_new, GxEPD_BLACK);
975 display.drawLine(last_x, last_y, x_new, y_new, GxEPD_BLACK);
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);
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);
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);
999 for (int i=0; i<MaxHourlyFC; i+=3) {
1000 DisplayForecast(x,x+90,235,305,HourlyForecasts[i]);
1002 if (x+95>780) break;
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,"Осадки");
1014 WiFiManager wifiManager;
1015 WiFi.mode(WIFI_STA);
1016 wifiManager.setConnectTimeout(10);
1018 if (WiFi.SSID().isEmpty()) {
1020 wifiManager.setTimeout(600);
1023 } else if (!wifiManager.autoConnect()) {
1025 Serial.println("failed to connect to stored SSID and hit timeout");
1026 wifiManager.setTimeout(600);
1030 } else if (Serial.available() && Serial.read() == 'c') {
1032 wifiManager.setTimeout(600);
1037 // put your main code here, to run repeatedly:
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);
1047 int CurrentMin, CurrentSec;
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");
1059 esp_sleep_enable_timer_wakeup((SleepTimer+5)*1000*1000);
1060 esp_deep_sleep_start();
1066 Serial.println("Going to short sleep for 120 seconds now ");
1069 esp_sleep_enable_timer_wakeup(120*1000*1000);
1070 esp_deep_sleep_start();