#include //this needs to be first, or it all crashes and burns... #include "SPIFFS.h" #include //https://github.com/esp8266/Arduino //needed for library #include #include #include //https://github.com/tzapu/WiFiManager #include //https://github.com/bblanchon/ArduinoJson #include // Built-in #include // Built-in #include #include #include #include #include #include "weather_types.h" #define SCREEN_WIDTH 800 // Set for landscape mode #define SCREEN_HEIGHT 480 enum alignment {LEFT, RIGHT, CENTER}; // Connections for e.g. Waveshare ESP32 e-Paper Driver Board static const uint8_t EPD_BUSY = 25; static const uint8_t EPD_CS = 15; static const uint8_t EPD_RST = 26; static const uint8_t EPD_DC = 27; static const uint8_t EPD_SCK = 13; static const uint8_t EPD_MISO = 12; // Master-In Slave-Out not used, as no data from display static const uint8_t EPD_MOSI = 14; GxEPD2_BW display(GxEPD2_750_T7(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // B/W display //GxEPD2_3C display(GxEPD2_750(/*CS=*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // 3-colour displays // use GxEPD_BLACK or GxEPD_WHITE or GxEPD_RED or GxEPD_YELLOW depending on display type U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; #include "fonts.h" //define your default values here, if there are different values in config.json, they are overwritten. //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! char latitude[8] = "55.5"; char longitude[8] = "37.5"; char timezone[40] = "Europe/Moscow"; // Forecast beginning of day char current_weather_server[255] = ""; char current_weather_uri[255] = ""; char current_weather_pressure[40] = "PRESSURE"; char current_weather_temperature[40] = "TEMPERATURE_C"; char current_weather_humidity[40] = "HUMIDITY"; char current_weather_windspeed[40] = ""; char current_weather_lux[40] = "LUX"; const char server[] = "api.open-meteo.com"; int SleepDuration = 10; //flag for saving data bool shouldSaveConfig = false; //callback notifying us of the need to save config void saveConfigCallback () { Serial.println("Should save config"); shouldSaveConfig = true; } void DisplaySetup() { display.init(115200, true, 2); // init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode) SPI.end(); SPI.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); u8g2Fonts.begin(display); // connect u8g2 procedures to Adafruit GFX u8g2Fonts.setFontMode(1); // use u8g2 transparent mode (this is default) u8g2Fonts.setFontDirection(0); // left to right (this is default) u8g2Fonts.setForegroundColor(GxEPD_BLACK); // apply Adafruit GFX color u8g2Fonts.setBackgroundColor(GxEPD_WHITE); // apply Adafruit GFX color SetCyrFont(10); display.fillScreen(GxEPD_WHITE); display.setFullWindow(); } void WiFiSetup() { // The extra parameters to be configured (can be either global or just in the setup) // After connecting, parameter.getValue() will get you the configured value // id/name placeholder/prompt default length WiFiManagerParameter custom_latitude("latitide", "Latitude", latitude, 8); WiFiManagerParameter custom_longitude("longitude", "Longitude", longitude, 8); WiFiManagerParameter custom_timezone("timezone", "Time zone", timezone, 40); WiFiManagerParameter custom_current_weather_server("current_weather_server", "Weather server", current_weather_server, 255); WiFiManagerParameter custom_current_weather_uri("current_weather_uri", "Weather URI", current_weather_uri, 255); WiFiManagerParameter custom_current_weather_pressure("current_weather_pressure", "Pressure key", current_weather_pressure, 40); WiFiManagerParameter custom_current_weather_temperature("current_weather_temperature", "Temperature key", current_weather_temperature, 40); WiFiManagerParameter custom_current_weather_humidity("current_weather_humidity", "Humidity key", current_weather_humidity, 40); WiFiManagerParameter custom_current_weather_windspeed("current_weather_windspeed", "Windspeed key", current_weather_windspeed, 40); WiFiManagerParameter custom_current_weather_lux("current_weather_lux", "Lux key", current_weather_lux, 40); //WiFiManager //Local intialization. Once its business is done, there is no need to keep it around WiFiManager wifiManager; //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); //add all your parameters here wifiManager.addParameter(&custom_latitude); wifiManager.addParameter(&custom_longitude); wifiManager.addParameter(&custom_timezone); wifiManager.addParameter(&custom_current_weather_server); wifiManager.addParameter(&custom_current_weather_uri); wifiManager.addParameter(&custom_current_weather_pressure); wifiManager.addParameter(&custom_current_weather_temperature); wifiManager.addParameter(&custom_current_weather_humidity); wifiManager.addParameter(&custom_current_weather_windspeed); wifiManager.addParameter(&custom_current_weather_lux); wifiManager.setTimeout(300); if (WiFi. status() != WL_CONNECTED) { WiFi.disconnect(); WiFi.mode(WIFI_AP); WiFi.begin(); } if (!wifiManager.startConfigPortal()) { Serial.println("failed to connect and hit timeout"); delay(3000); //reset and try again, or maybe put it to deep sleep ESP.restart(); } //if you get here you have connected to the WiFi Serial.println("connected..."); //read updated parameters strcpy(latitude, custom_latitude.getValue()); strcpy(longitude, custom_longitude.getValue()); strcpy(timezone, custom_timezone.getValue()); strcpy(current_weather_server, custom_current_weather_server.getValue()); strcpy(current_weather_uri, custom_current_weather_uri.getValue()); strcpy(current_weather_pressure, custom_current_weather_pressure.getValue()); strcpy(current_weather_temperature, custom_current_weather_temperature.getValue()); strcpy(current_weather_humidity, custom_current_weather_humidity.getValue()); strcpy(current_weather_windspeed, custom_current_weather_windspeed.getValue()); strcpy(current_weather_lux, custom_current_weather_lux.getValue()); //save the custom parameters to FS if (shouldSaveConfig) { Serial.println("saving config"); DynamicJsonDocument json(1024); // json["api_key"] = api_key; json["latitude"] = latitude; json["longitude"] = longitude; json["timezone"] = timezone; json["current_weather_server"] = current_weather_server; json["current_weather_uri"] = current_weather_uri; json["current_weather_pressure"] = current_weather_pressure; json["current_weather_temperature"] = current_weather_temperature; json["current_weather_humidity"] = current_weather_humidity; json["current_weather_windspeed"] = current_weather_windspeed; json["current_weather_lux"] = current_weather_lux; File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { Serial.println("failed to open config file for writing"); } serializeJson(json, Serial); serializeJson(json, configFile); configFile.close(); //end save } } void setup() { byte need_setup = false; // put your setup code here, to run once: Serial.begin(115200); Serial.println(); //read configuration from FS json Serial.println("mounting FS..."); if (SPIFFS.begin(true)) { Serial.println("mounted file system"); if (SPIFFS.exists("/config.json")) { //file exists, reading and loading Serial.println("reading config file"); File configFile = SPIFFS.open("/config.json", "r"); if (configFile) { Serial.println("opened config file"); size_t size = configFile.size(); // Allocate a buffer to store contents of the file. std::unique_ptr buf(new char[size]); configFile.readBytes(buf.get(), size); DynamicJsonDocument json(1024); auto deserializeError = deserializeJson(json, buf.get()); serializeJson(json, Serial); if ( ! deserializeError ) { Serial.println("\nparsed json"); if (json.containsKey("latitude")) { strcpy(latitude, json["latitude"]); } if (json.containsKey("longitude")) { strcpy(longitude, json["longitude"]); } if (json.containsKey("timezone")) { strcpy(timezone, json["timezone"]); } if (json.containsKey("current_weather_server")) { strcpy(current_weather_server, json["current_weather_server"]); } if (json.containsKey("current_weather_uri")) { strcpy(current_weather_uri, json["current_weather_uri"]); } if (json.containsKey("current_weather_pressure")) { strcpy(current_weather_pressure, json["current_weather_pressure"]); } if (json.containsKey("current_weather_temperature")) { strcpy(current_weather_temperature, json["current_weather_temperature"]); } if (json.containsKey("current_weather_humidity")) { strcpy(current_weather_humidity, json["current_weather_humidity"]); } if (json.containsKey("current_weather_windspeed")) { strcpy(current_weather_windspeed, json["current_weather_windspeed"]); } if (json.containsKey("current_weather_lux")) { strcpy(current_weather_lux, json["current_weather_lux"]); } } else { Serial.println("failed to load json config"); } configFile.close(); } else { need_setup = true; } } else { need_setup = true; } } else { Serial.println("failed to mount FS"); } //end read if (need_setup) { WiFiSetup(); } else { WiFiManager wifiManager; wifiManager.setTimeout(10); if(!wifiManager.autoConnect()) { Serial.println("failed to connect and hit timeout"); WiFiSetup(); delay(3000); ESP.restart(); } } Serial.println("\nlocal ip"); Serial.println(WiFi.localIP()); DisplaySetup(); } int TimeZoneOffset; // int SunRise, SunSet; int HourlyDT[MaxHourlyFC]; float HourlyTemp[MaxHourlyFC]; float HourlyFeelsLike[MaxHourlyFC]; float HourlyPressure[MaxHourlyFC]; float HourlyHumidity[MaxHourlyFC]; // float HourlyClouds[MaxHourlyFC]; float HourlyWindSpeed[MaxHourlyFC]; // float HourlyRain[MaxHourlyFC]; // float HourlySnow[MaxHourlyFC]; // float HourlyShowers[MaxHourlyFC]; float HourlyPrecip[MaxHourlyFC]; String UnixTime(int unix_time) { time_t tm = unix_time; struct tm *now_tm = localtime(&tm); char output[40]; strftime(output, sizeof(output), "%H:%M %d/%m/%y", now_tm); return output; } String UnixTimeOnly(int unix_time) { time_t tm = unix_time; struct tm *now_tm = localtime(&tm); char output[40]; strftime(output, sizeof(output), "%H:%M", now_tm); return output; } String UnixDate(int unix_time) { time_t tm = unix_time; struct tm *now_tm = localtime(&tm); char output[40]; strftime(output, sizeof(output), "%d/%m/%y", now_tm); return output; } bool DecodeWeather(WiFiClient& json) { DynamicJsonDocument doc(35 * 1024); // Deserialize the JSON document DeserializationError error = deserializeJson(doc, json); // Test if parsing succeeds. if (error) { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.c_str()); return false; } // convert it to a JsonObject JsonObject root = doc.as(); TimeZoneOffset = root["utc_offset_seconds"].as(); CurrentWeather.dt = root["current"]["time"].as(); CurrentWeather.temp = root["current"]["temperature_2m"].as(); CurrentWeather.feels_like = root["current"]["apparent_temperature"].as(); CurrentWeather.pressure = 0.75 * root["current"]["surface_pressure"].as(); CurrentWeather.humidity = root["current"]["relative_humidity_2m"].as(); CurrentWeather.wind_speed = root["current"]["wind_speed_10m"].as(); CurrentWeather.wind_deg = root["current"]["wind_direction_10m"].as(); CurrentWeather.precip = root["current"]["precipitation"].as(); CurrentWeather.wmo = root["current"]["weather_code"].as(); struct timeval now = { .tv_sec = CurrentWeather.dt }; settimeofday(&now, NULL); int firstForecastDT = root["hourly"]["time"][0].as(); int offset = (CurrentWeather.dt - firstForecastDT) / 3600; for (byte i=0; i(); HourlyTemp[i] = HourlyForecasts[i].temp = root["hourly"]["temperature_2m"][i+offset].as(); HourlyFeelsLike[i] = HourlyForecasts[i].feels_like = root["hourly"]["apparent_temperature"][i+offset].as(); HourlyPressure[i] = HourlyForecasts[i].pressure = 0.75 * root["hourly"]["surface_pressure"][i+offset].as(); HourlyHumidity[i] = HourlyForecasts[i].humidity = root["hourly"]["relative_humidity_2m"][i+offset].as(); HourlyWindSpeed[i] = HourlyForecasts[i].wind_speed = root["hourly"]["wind_speed_10m"][i+offset].as(); HourlyForecasts[i].wind_deg = root["hourly"]["wind_direction_10m"][i+offset].as(); HourlyPrecip[i] = HourlyForecasts[i].precip = root["hourly"]["precipitation"][i+offset].as(); HourlyForecasts[i].wmo = root["hourly"]["weather_code"][i+offset].as(); } return true; } bool GetWeather(WiFiClient& client) { client.stop(); // close connection before sending a new request HTTPClient http; char uri[512]; 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); http.useHTTP10(true); Serial.printf("Connecting http://%s%s ...\n", server, uri); http.begin(client, server, 80, uri); int httpCode = http.GET(); if(httpCode == HTTP_CODE_OK) { if (!DecodeWeather(http.getStream())) return false; client.stop(); http.end(); return true; } else { Serial.printf("Forecast server connection failed, error: %s\n", http.errorToString(httpCode).c_str()); client.stop(); http.end(); return false; } http.end(); return true; } bool DecodeCurrentWeather(WiFiClient& json) { DynamicJsonDocument doc(35 * 1024); // Deserialize the JSON document DeserializationError error = deserializeJson(doc, json); // Test if parsing succeeds. if (error) { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.c_str()); return false; } // convert it to a JsonObject JsonObject root = doc.as(); if (current_weather_pressure[0] && root.containsKey(current_weather_pressure)) { CurrentWeather.pressure = root[current_weather_pressure].as(); Serial.printf("Current pressure: %f\n",CurrentWeather.pressure); } if (current_weather_temperature[0] && root.containsKey(current_weather_temperature)) { CurrentWeather.temp = root[current_weather_temperature].as(); Serial.printf("Current temperature: %f\n",CurrentWeather.temp); } if (current_weather_humidity[0] && root.containsKey(current_weather_humidity)) { CurrentWeather.humidity = root[current_weather_humidity].as(); Serial.printf("Current humidity: %f\n",CurrentWeather.humidity); } if (current_weather_windspeed[0] && root.containsKey(current_weather_windspeed)) { CurrentWeather.wind_speed = root[current_weather_windspeed].as(); Serial.printf("Current windspeed: %f\n",CurrentWeather.wind_speed); } float Ro = (CurrentWeather.humidity/100) * 6.105 * pow(2.712828, 17.27 * CurrentWeather.temp/(237.7+CurrentWeather.temp)); CurrentWeather.feels_like = CurrentWeather.temp + 0.348*Ro - 0.70*CurrentWeather.wind_speed - 4.25; if (current_weather_lux[0] && root.containsKey(current_weather_lux)) { float wm2 = root[current_weather_lux].as()/685; CurrentWeather.feels_like = CurrentWeather.feels_like + 0.70*wm2/(CurrentWeather.wind_speed + 10); } Serial.printf("Calculated feels like: %f\n",CurrentWeather.feels_like); return true; } bool GetCurrentWeather(WiFiClient& client) { if (current_weather_server) { client.stop(); // close connection before sending a new request HTTPClient http; http.useHTTP10(true); Serial.printf("Connecting http://%s%s ...\n",current_weather_server,current_weather_uri); http.begin(client, current_weather_server, 80, current_weather_uri); int httpCode = http.GET(); if(httpCode == HTTP_CODE_OK) { if (!DecodeCurrentWeather(http.getStream())) return false; client.stop(); http.end(); return true; } else { Serial.printf("Current weather server connection failed, error: %s\n", http.errorToString(httpCode).c_str()); client.stop(); http.end(); return false; } http.end(); } return true; } void arrow(int x, int y, int asize, float aangle, int pwidth, int plength) { float dx = (asize) * cos((aangle - 90) * PI / 180) + x; // calculate X position float dy = (asize) * sin((aangle - 90) * PI / 180) + y; // calculate Y position float x1 = 0; float y1 = plength; float x2 = pwidth / 2; float y2 = pwidth / 2; float x3 = -pwidth / 2; float y3 = pwidth / 2; float angle = aangle * PI / 180 - 135; float xx1 = x1 * cos(angle) - y1 * sin(angle) + dx; float yy1 = y1 * cos(angle) + x1 * sin(angle) + dy; float xx2 = x2 * cos(angle) - y2 * sin(angle) + dx; float yy2 = y2 * cos(angle) + x2 * sin(angle) + dy; float xx3 = x3 * cos(angle) - y3 * sin(angle) + dx; float yy3 = y3 * cos(angle) + x3 * sin(angle) + dy; display.fillTriangle(xx1, yy1, xx3, yy3, xx2, yy2, GxEPD_BLACK); } void drawString(int x, int y, String text, alignment align) { uint16_t w, h; display.setTextWrap(false); w = u8g2Fonts.getUTF8Width(text.c_str()); h = u8g2Fonts.getFontAscent(); if (align == RIGHT) x = x - w; if (align == CENTER) x = x - w / 2; u8g2Fonts.setCursor(x, y + h / 2); u8g2Fonts.print(text); } String WindDegToDirection(float winddirection) { if (winddirection >= 337.5 || winddirection < 22.5) return "С"; if (winddirection >= 22.5 && winddirection < 67.5) return "СВ"; if (winddirection >= 67.5 && winddirection < 112.5) return "В"; if (winddirection >= 112.5 && winddirection < 157.5) return "ЮВ"; if (winddirection >= 157.5 && winddirection < 202.5) return "Ю"; if (winddirection >= 202.5 && winddirection < 247.5) return "ЮЗ"; if (winddirection >= 247.5 && winddirection < 292.5) return "З"; if (winddirection >= 292.5 && winddirection < 337.5) return "СЗ"; return "?"; } void DrawBlock(int x1, int x2, int y1, int y2) { display.fillRect(x1+3, y1+3, x2-x1, y2-y1, GxEPD_BLACK); display.fillRect(x1, y1, x2-x1, y2-y1, GxEPD_WHITE); display.drawRect(x1, y1, x2-x1, y2-y1, GxEPD_BLACK); } void DisplayCurrent(int x1, int x2, int y1, int y2, WeatherRecord weather) { DrawBlock(x1,x2,y1,y2); int x = (x1 + x2)/2; int y = (y1 + y2)/2; int h = (y2 - y1)/3; int w = (x2 - x1)/4; if (hr2) { r = r2; } else { r = r1; } int w = r/5; if (w>40) { w = 40; } r = r - w; String speedstr = String(weather.wind_speed,1); speedstr.trim(); arrow (x, y, r-w, weather.wind_deg, w/2, w*7/8); display.drawCircle(x, y, r, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, r + 1, GxEPD_BLACK); // Draw compass circle display.drawCircle(x, y, r - w, GxEPD_BLACK); // Draw compass inner circle display.drawCircle(x, y, r - w + 1, GxEPD_BLACK); // Draw compass inner circle for (float a = 0; a < 360; a = a + 30) { dxo = r * cos((a - 90) * PI / 180); dyo = r * sin((a - 90) * PI / 180); dxi = dxo * 0.9; dyi = dyo * 0.9; display.drawLine(dxo + x, dyo + y, dxi + x, dyi + y, GxEPD_BLACK); } SetCyrFont(w*2/3); drawString(x, y - r - w/2, "С", CENTER); drawString(x, y + r + w/2, "Ю", CENTER); drawString(x - r - w/2, y, "З", CENTER); drawString(x + r + w/2, y, "В", CENTER); drawString(x - (r + w/2)*10/14, y - (r + w/2)*10/14, "сз", CENTER); drawString(x - (r + w/2)*10/14, y + (r + w/2)*10/14, "юз", CENTER); drawString(x + (r + w/2)*10/14, y + (r + w/2)*10/14, "юв", CENTER); drawString(x + (r + w/2)*10/14, y - (r + w/2)*10/14, "св", CENTER); SetNumFont(r*5/8); drawString(x, y-r/8, speedstr, CENTER); SetCyrFont(w); drawString(x, y+r*3/8, "м/с", CENTER); } void drawsun(int x, int y, int scale, int linesize) { display.fillRect(x - scale * 2, y, scale * 4, linesize, GxEPD_BLACK); display.fillRect(x, y - scale * 2, linesize, scale * 4, GxEPD_BLACK); display.drawLine(x - scale * 1.3, y - scale * 1.3, x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK); display.drawLine(x - scale * 1.3, y + scale * 1.3, x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK); if (linesize>1) { display.drawLine(1 + x - scale * 1.3, y - scale * 1.3, 1 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK); display.drawLine(2 + x - scale * 1.3, y - scale * 1.3, 2 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK); display.drawLine(3 + x - scale * 1.3, y - scale * 1.3, 3 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK); display.drawLine(1 + x - scale * 1.3, y + scale * 1.3, 1 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK); display.drawLine(2 + x - scale * 1.3, y + scale * 1.3, 2 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK); display.drawLine(3 + x - scale * 1.3, y + scale * 1.3, 3 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK); } display.fillCircle(x, y, scale * 1.3, GxEPD_WHITE); display.fillCircle(x, y, scale, GxEPD_BLACK); display.fillCircle(x, y, scale - linesize, GxEPD_WHITE); } void drawcloud(int x, int y, int scale, int linesize) { //Draw cloud outer display.fillCircle(x - scale * 3, y, scale, GxEPD_BLACK); // Left most circle display.fillCircle(x + scale * 3, y, scale, GxEPD_BLACK); // Right most circle display.fillCircle(x - scale, y - scale, scale * 1.4, GxEPD_BLACK); // left middle upper circle display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75, GxEPD_BLACK); // Right middle upper circle display.fillRect(x - scale * 3 - 1, y - scale, scale * 6, scale * 2 + 1, GxEPD_BLACK); // Upper and lower lines //Clear cloud inner display.fillCircle(x - scale * 3, y, scale - linesize, GxEPD_WHITE); // Clear left most circle display.fillCircle(x + scale * 3, y, scale - linesize, GxEPD_WHITE); // Clear right most circle display.fillCircle(x - scale, y - scale, scale * 1.4 - linesize, GxEPD_WHITE); // left middle upper circle display.fillCircle(x + scale * 1.5, y - scale * 1.3, scale * 1.75 - linesize, GxEPD_WHITE); // Right middle upper circle display.fillRect(x - scale * 3 + 2, y - scale + linesize - 1, scale * 5.9, scale * 2 - linesize * 2 + 2, GxEPD_WHITE); // Upper and lower lines } void drawraindrop(int x, int y, int scale) { display.fillCircle(x, y, scale / 4, GxEPD_BLACK); display.fillTriangle(x - scale / 4, y, x, y - scale, x + scale / 4, y , GxEPD_BLACK); x = x + scale * 1.5; y = y + scale / 2; display.fillCircle(x, y, scale / 4, GxEPD_BLACK); display.fillTriangle(x - scale / 4, y, x, y - scale, x + scale / 4, y , GxEPD_BLACK); } void drawrain(int x, int y, int scale) { for (int d = 0; d < 4; d++) { drawraindrop(x + scale * (7.8 - d * 1.85) - scale * 5.2, y + scale * 2 - scale / 6, scale / 1.6); } } void drawsnow(int x, int y, int scale) { int dxo, dyo, dxi, dyi; int delta = -scale/5; for (int flakes = 0; flakes < 5; flakes++) { for (int i = 0; i < 360; i = i + 60) { dxo = 0.5 * scale * cos((i - 90) * 3.14 / 180); dxi = dxo * 0.1; dyo = 0.5 * scale * sin((i - 90) * 3.14 / 180); dyi = dyo * 0.1; 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); } delta = - delta; } } void drawtstorm(int x, int y, int scale) { y = y + scale / 2; int delta = -scale/5; for (int i = 0; i < 4; i++) { delta = - delta; 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); if (scale > 15) { 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); 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); } 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); if (scale > 15) { 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); 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); } 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); if (scale > 15) { 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); 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); } } } void drawfog(int x, int y, int scale, int linesize) { if (scale < 15) { y -= 10; linesize = 1; } for (int i = 0; i < 6; i++) { display.fillRect(x - scale * 3, y + scale * 1.5, scale * 6, linesize, GxEPD_BLACK); display.fillRect(x - scale * 3, y + scale * 2.0, scale * 6, linesize, GxEPD_BLACK); display.fillRect(x - scale * 3, y + scale * 2.5, scale * 6, linesize, GxEPD_BLACK); } } void Sunny(int x, int y, int scale) { int linesize = 1; if (scale>15) linesize=3; drawsun(x, y, scale*6/5, linesize); } void MostlySunny(int x, int y, int scale) { int linesize = 1, offset = 5; if (scale>15) { linesize = 3; offset = 15; } drawcloud(x, y + offset, scale, linesize); drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize); } void MostlyCloudy(int x, int y, int scale) { int linesize = 1, offset = 5; if (scale>15) { linesize = 3; offset = 15; } drawsun(x - scale * 1.8, y - scale * 1.8 + offset, scale, linesize); drawcloud(x, y + offset, scale, linesize); } void Cloudy(int x, int y, int scale) { int linesize = 1; if (scale<=15) { drawcloud(x, y+5, scale, linesize); } else { linesize = 5; y += scale/2; drawcloud(x + scale * 2.5, y - scale * 1.8, scale/1.6, linesize); // Cloud top right drawcloud(x - scale * 2, y - scale * 2, scale/2, linesize); // Cloud top left drawcloud(x, y, scale, linesize); // Main cloud } } void Rain(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawcloud(x, y-scale/3, scale, linesize); drawrain(x, y-scale/3, scale); } void ExpectRain(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawsun(x - scale * 1.8, y - scale * 1.6, scale, linesize); drawcloud(x, y+scale/5, scale, linesize); drawrain(x, y, scale); } void Tstorms(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawcloud(x, y, scale, linesize); drawtstorm(x, y, scale); } void Snow(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawcloud(x, y, scale, linesize); drawsnow(x, y, scale); } void Fog(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawcloud(x, y - 5, scale, linesize); drawfog(x, y - 5, scale, linesize); } void Haze(int x, int y, int scale) { int linesize = 3; if (scale<15) { linesize = 1; } drawsun(x, y - 5, scale*6/5, linesize); drawfog(x, y - 5, scale, linesize); } void DisplayIcon(int x1, int x2, int y1, int y2, WeatherRecord weather) { DrawBlock(x1,x2,y1,y2); int x = (x1 + x2)/2; int y = (y1 + y2)/2; int h = (y2 - y1); int w = (x2 - x1); int scale = w/14; int fscale = w/12; int wmo = weather.wmo; String description; if (wmo == 0) { Sunny(x, y, scale); description = "ясно"; } else if (wmo == 1) { MostlySunny(x, y, scale); description = "премущественно ясно"; } else if (wmo == 2) { MostlyCloudy(x, y, scale); description = "переменная облачность"; } else if (wmo == 3) { Cloudy(x, y, scale); description = "пасмурно"; } else if (wmo == 61) { Rain(x, y, scale); description = "небольшой дождь"; } else if (wmo == 63) { Rain(x, y, scale); description = "дождь"; } else if (wmo == 65) { Rain(x, y, scale); description = "сильный дождь"; } else if (wmo == 51) { Rain(x, y, scale); description = "небольшая морось"; } else if (wmo == 53) { Rain(x, y, scale); description = "морось"; } else if (wmo == 55) { Rain(x, y, scale); description = "сильная морось"; } else if (wmo == 66) { Rain(x, y, scale); description = "ледяной дождь"; } else if (wmo == 67) { Rain(x, y, scale); description = "сильный ледяной дождь"; } else if (wmo == 56) { Rain(x, y, scale); description = "ледяная морось"; } else if (wmo == 57) { Rain(x, y, scale); description = "сильная ледяная морось"; } else if (wmo == 80) { ExpectRain(x, y, scale); description = "возможны ливни"; } else if (wmo == 81) { ExpectRain(x, y, scale); description = "ливни"; } else if (wmo == 82) { ExpectRain(x, y, scale); description = "сильные ливни"; } else if (wmo == 95) { Tstorms(x, y, scale); description = "гроза"; } else if (wmo == 96) { Tstorms(x, y, scale); description = "гроза с градом"; } else if (wmo == 97) { Tstorms(x, y, scale); description = "сильная гроза"; } else if (wmo == 71) { Snow(x, y, scale); description = "слабый снег"; } else if (wmo == 73) { Snow(x, y, scale); description = "снег"; } else if (wmo == 75) { Snow(x, y, scale); description = "сильный снег"; } else if (wmo == 77) { Snow(x, y, scale); description = "снежная крупа"; } else if (wmo == 85) { Snow(x, y, scale); description = "возможна метель"; } else if (wmo == 86) { Snow(x, y, scale); description = "метель"; } else if (wmo == 48) { Haze(x, y, scale); description = "ледяной туман"; } else if (wmo == 45) { Fog(x, y, scale); description = "туман"; } SetCyrFont(fscale); drawString(x, y2-fscale*2/3, description, CENTER); } void DisplayUpdate(int x1, int x2, int y1, int y2, WeatherRecord weather) { DrawBlock(x1,x2,y1,y2); int x = (x1 + x2)/2; int y = (y1 + y2)/2; int h = (y2 - y1)/2; int w = (x2 - x1)/16; if (h= maxY) maxY = DataArray[i]; if (DataArray[i] <= minY) minY = DataArray[i]; } maxY = round(maxY + 0.5); minY = round(minY); last_x = x_pos + 1; last_y = y_pos - (constrain(DataArray[0], minY, maxY) - minY) / (maxY - minY) * h -1; display.drawRect(x_pos, y_pos-h, w, h, GxEPD_BLACK); for (int gx = 1; gx < readings; gx++) { x_new = x_pos + gx * w / (readings - 1) - 1 ; y_new = y_pos - (constrain(DataArray[gx], minY, maxY) - minY) / (maxY - minY) * h -1; if (barchart_mode) { if (DataArray[gx]!=0) { display.fillRect(x_new, y_new, (w / readings) - 1, y_pos - y_new, GxEPD_BLACK); } } else { display.drawLine(last_x, last_y, x_new, y_new, GxEPD_BLACK); } last_x = x_new; last_y = y_new; } #define y_minor_axis 5 #define number_of_dashes 20 SetNumFont(scale/1.3); for (int spacing = 1; spacing < y_minor_axis; spacing++) { for (int j = 0; j < number_of_dashes; j++) { 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); } drawString(x_pos - 1, y_pos - h * spacing / y_minor_axis, String((minY + (float)(maxY - minY) / y_minor_axis * spacing + 0.01), 1), RIGHT); } } void DisplayWeather() { Serial.println("Displaying..."); DisplayWind(20,235,20,225, CurrentWeather); DisplayCurrent(240,515,20,225,CurrentWeather); DisplayIcon(520,775,60,225,CurrentWeather); DisplayUpdate(520,775,20,55,CurrentWeather); int x=20; for (int i=0; i780) break; } DisplayGraph(20,268,315,460,HourlyDT,HourlyTemp,MaxHourlyFC,false,"Температура"); DisplayGraph(273,522,315,460,HourlyDT,HourlyPressure,MaxHourlyFC,false,"Давление"); DisplayGraph(527,775,315,460,HourlyDT,HourlyPrecip,MaxHourlyFC,true,"Осадки"); display.display(); } void loop() { delay(2000); WiFiManager wifiManager; WiFi.mode(WIFI_STA); wifiManager.setConnectTimeout(10); if (WiFi.SSID().isEmpty()) { wifiManager.setTimeout(600); WiFiSetup(); } else if (!wifiManager.autoConnect()) { Serial.println("failed to connect to stored SSID and hit timeout"); wifiManager.setTimeout(600); WiFi.mode(WIFI_AP); WiFiSetup(); } else if (Serial.available() && Serial.read() == 'c') { wifiManager.setTimeout(600); WiFiSetup(); } else { // put your main code here, to run repeatedly: byte Attempts = 1; WiFiClient client; // wifi client object for (byte i=1; i<=3; i++) { if (GetWeather(client)) { GetCurrentWeather(client); WiFi.mode(WIFI_OFF); btStop(); DisplayWeather(); int CurrentMin, CurrentSec; struct tm timeinfo; getLocalTime(&timeinfo, 10000); CurrentMin = timeinfo.tm_min; CurrentSec = timeinfo.tm_sec; long SleepTimer = (SleepDuration * 60 - ((CurrentMin % SleepDuration) * 60 + CurrentSec)); Serial.print("Going to long sleep for "); Serial.print(SleepTimer); Serial.println(" seconds now"); delay(1000); Serial.flush(); esp_sleep_enable_timer_wakeup((SleepTimer+5)*1000*1000); esp_deep_sleep_start(); } } } Serial.println("Going to short sleep for 120 seconds now "); delay(1000); Serial.flush(); esp_sleep_enable_timer_wakeup(120*1000*1000); esp_deep_sleep_start(); }