From: Roman Bazalevskiy Date: Thu, 14 Jan 2021 06:48:32 +0000 (+0300) Subject: ESP32 e-Paper info screen (client part). X-Git-Url: https://git.rvb.name/weathermon.git/commitdiff_plain/414a39ee4b909dbd7c891ce7a3bff90b75db866c?hp=--cc ESP32 e-Paper info screen (client part). --- 414a39ee4b909dbd7c891ce7a3bff90b75db866c diff --git a/weather_screen/fonts.h b/weather_screen/fonts.h new file mode 100644 index 0000000..e1269c7 --- /dev/null +++ b/weather_screen/fonts.h @@ -0,0 +1,40 @@ +typedef struct { + int maxSize; + const uint8_t* u8g2; +} FontInfo; + +#define CyrFonts 4 + +const FontInfo CyrFontArray[CyrFonts] = { + { 5, u8g2_font_4x6_t_cyrillic }, + { 9, u8g2_font_6x13_t_cyrillic }, + { 12, u8g2_font_9x15_t_cyrillic }, + { 24, u8g2_font_inr24_t_cyrillic }, +}; + +#define NumFonts 8 + +const FontInfo NumFontArray[NumFonts] = { + { 5, u8g2_font_4x6_t_cyrillic }, + { 9, u8g2_font_6x13_t_cyrillic }, + { 12, u8g2_font_9x15_t_cyrillic }, + { 16, u8g2_font_logisoso16_tf }, + { 24, u8g2_font_logisoso24_tf }, + { 32, u8g2_font_logisoso32_tf }, + { 50, u8g2_font_logisoso50_tf }, + { 92, u8g2_font_logisoso92_tn } +}; + +void SetCyrFont(byte size) { + for (byte i=0; i //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 language[4] = "RU"; // NOTE: Only the weather description is translated by OWM + // Examples: Arabic (AR) Czech (CZ) English (EN) Greek (EL) Persian(Farsi) (FA) Galician (GL) Hungarian (HU) Japanese (JA) + // Korean (KR) Latvian (LA) Lithuanian (LT) Macedonian (MK) Slovak (SK) Slovenian (SL) Vietnamese (VI) + +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.openweathermap.org"; + +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_api_key("api_key", "OWN API key", api_key, 40); + WiFiManagerParameter custom_latitude("latitide", "Latitude", latitude, 8); + WiFiManagerParameter custom_longitude("longitude", "Longitude", longitude, 8); + WiFiManagerParameter custom_language("language", "Language", language, 4); + 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_api_key); + wifiManager.addParameter(&custom_latitude); + wifiManager.addParameter(&custom_longitude); + wifiManager.addParameter(&custom_language); + 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 (!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(api_key, custom_api_key.getValue()); + strcpy(latitude, custom_latitude.getValue()); + strcpy(longitude, custom_longitude.getValue()); + strcpy(language, custom_language.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["language"] = language; + 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("api_key")) { strcpy(api_key, json["api_key"]); } + if (json.containsKey("latitude")) { strcpy(latitude, json["latitude"]); } + if (json.containsKey("longitude")) { strcpy(longitude, json["longitude"]); } + if (json.containsKey("language")) { strcpy(language, json["language"]); } + 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"); + 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 HourlyPrecip[MaxHourlyFC]; + +int DailyDT[MaxDailyFC]; +float DailyTempMin[MaxDailyFC]; +float DailyTempMax[MaxDailyFC]; +float DailyFeelsLikeMin[MaxDailyFC]; +float DailyFeelsLikeMax[MaxDailyFC]; +float DailyPressure[MaxDailyFC]; +float DailyHumidity[MaxDailyFC]; +float DailyClouds[MaxDailyFC]; +float DailyWindSpeed[MaxDailyFC]; +float DailyRain[MaxDailyFC]; +float DailySnow[MaxDailyFC]; +float DailyPrecip[MaxDailyFC]; + +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["timezone_offset"].as(); + + SunRise = root["current"]["sunrise"].as(); + SunSet = root["current"]["sunset"].as(); + + CurrentWeather.dt = root["current"]["dt"].as(); + CurrentWeather.temp = root["current"]["temp"].as(); + CurrentWeather.feels_like = root["current"]["feels_like"].as(); + CurrentWeather.pressure = 0.75 * root["current"]["pressure"].as(); + CurrentWeather.humidity = root["current"]["humidity"].as(); + CurrentWeather.clouds = root["current"]["clouds"].as(); + CurrentWeather.wind_speed = root["current"]["wind_speed"].as(); + CurrentWeather.wind_deg = root["current"]["wind_deg"].as(); + CurrentWeather.rain = root["current"]["rain"]["1h"].as(); + CurrentWeather.snow = root["current"]["snow"]["1h"].as(); + CurrentWeather.description = root["current"]["weather"][0]["description"].as(); + CurrentWeather.icon = root["current"]["weather"][0]["icon"].as(); + + struct timeval now = { .tv_sec = CurrentWeather.dt }; + settimeofday(&now, NULL); + + JsonArray hourly = root["hourly"]; + for (byte i=0; i(); + HourlyTemp[i] = HourlyForecasts[i].temp = hourly[i]["temp"].as(); + HourlyFeelsLike[i] = HourlyForecasts[i].feels_like = hourly[i]["feels_like"].as(); + HourlyPressure[i] = HourlyForecasts[i].pressure = 0.75 * hourly[i]["pressure"].as(); + HourlyHumidity[i] = HourlyForecasts[i].humidity = hourly[i]["humidity"].as(); + HourlyClouds[i] = HourlyForecasts[i].clouds = hourly[i]["clouds"].as(); + HourlyWindSpeed[i] = HourlyForecasts[i].wind_speed = hourly[i]["wind_speed"].as(); + HourlyForecasts[i].wind_deg = hourly[i]["wind_deg"].as(); + HourlyRain[i] = HourlyForecasts[i].rain = hourly[i]["rain"]["1h"].as(); + HourlySnow[i] = HourlyForecasts[i].snow = hourly[i]["snow"]["1h"].as(); + HourlyPrecip[i] = HourlyRain[i] + HourlySnow[i]; + HourlyForecasts[i].description = hourly[i]["weather"][0]["description"].as(); + HourlyForecasts[i].icon = hourly[i]["weather"][0]["icon"].as(); + } + + JsonArray daily = root["daily"]; + for (byte i=0; i(); + DailyTempMin[i] = DailyForecasts[i].temp_min = daily[i]["temp"]["min"].as(); + DailyFeelsLikeMin[i] = DailyForecasts[i].feels_like_min = daily[i]["feels_like"]["min"].as(); + DailyTempMax[i] = DailyForecasts[i].temp_max = daily[i]["temp"]["max"].as(); + DailyFeelsLikeMax[i] = DailyForecasts[i].feels_like_max = daily[i]["feels_like"]["max"].as(); + DailyPressure[i] = DailyForecasts[i].pressure = 0.75 * daily[i]["pressure"].as(); + DailyHumidity[i] = DailyForecasts[i].humidity = daily[i]["humidity"].as(); + DailyClouds[i] = DailyForecasts[i].clouds = daily[i]["clouds"].as(); + DailyWindSpeed[i] = DailyForecasts[i].wind_speed = daily[i]["wind_speed"].as(); + DailyForecasts[i].wind_deg = daily[i]["wind_deg"].as(); + DailyRain[i] = DailyForecasts[i].rain = daily[i]["rain"].as(); + DailySnow[i] = DailyForecasts[i].snow = daily[i]["snow"].as(); + DailyPrecip[i] = DailyRain[i] + DailySnow[i]; + DailyForecasts[i].description = daily[i]["weather"][0]["description"].as(); + DailyForecasts[i].icon = daily[i]["weather"][0]["icon"].as(); + } + + return true; +} + +bool GetWeather(WiFiClient& client) { + client.stop(); // close connection before sending a new request + HTTPClient http; + char uri[128]; + sprintf(uri,"/data/2.5/onecall?lat=%s&lon=%s&appid=%s&lang=%s&units=metric",latitude,longitude,api_key,language); + http.useHTTP10(true); + Serial.println("Connecting..."); + 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("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(); + } + if (current_weather_temperature[0] && root.containsKey(current_weather_temperature)) { + CurrentWeather.temp = root[current_weather_temperature].as(); + } + if (current_weather_humidity[0] && root.containsKey(current_weather_humidity)) { + CurrentWeather.humidity = root[current_weather_humidity].as(); + } + if (current_weather_windspeed[0] && root.containsKey(current_weather_windspeed)) { + CurrentWeather.wind_speed = root[current_weather_windspeed].as(); + } + + 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.print("Calculated feels like: "); + Serial.println(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.println("Connecting..."); + 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("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; + + String icon = weather.icon; + String description = weather.description; + + if (icon == "01d" || icon == "01n") + Sunny(x, y, scale); + else if (icon == "02d" || icon == "02n") + MostlySunny(x, y, scale); + else if (icon == "03d" || icon == "03n") + MostlyCloudy(x, y, scale); + else if (icon == "04d" || icon == "04n") + Cloudy(x, y, scale); + else if (icon == "09d" || icon == "09n") + Rain(x, y, scale); + else if (icon == "10d" || icon == "10n") + ExpectRain(x, y, scale); + else if (icon == "11d" || icon == "11n") + Tstorms(x, y, scale); + else if (icon == "13d" || icon == "13n") + Snow(x, y, scale); + else if (icon == "50d") + Haze(x, y, scale); + else if (icon == "50n") + Fog(x, y, scale); + + 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; + wifiManager.setTimeout(10); + if(!wifiManager.autoConnect()) { + + Serial.println("failed to connect and hit timeout"); + + } else { + + if (Serial.available() && Serial.read() == 'c') { + WiFiSetup(); + } + + // 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(); + +} + + diff --git a/weather_screen/weather_types.h b/weather_screen/weather_types.h new file mode 100644 index 0000000..6a49dff --- /dev/null +++ b/weather_screen/weather_types.h @@ -0,0 +1,26 @@ +typedef struct { // For current Day and Day 1, 2, 3, etc + int dt; + float temp; + float temp_min; + float temp_max; + float feels_like; + float feels_like_min; + float feels_like_max; + float pressure; + float humidity; + float clouds; + float wind_speed; + float wind_deg; + float rain; + float snow; + String description; + String icon; +} WeatherRecord; + +WeatherRecord CurrentWeather; + +#define MaxHourlyFC 48 +#define MaxDailyFC 7 +WeatherRecord HourlyForecasts[MaxHourlyFC]; +WeatherRecord DailyForecasts[MaxDailyFC]; +