#include #include #include #include #include "weather-icons.h" #include "secrets.h" /* Position for Durlach */ #define lat 48.999 #define lon 8.474 #define FONTSIZE 10 #define SPACING 5 #define DIM(w, h) (((w) << 16) | (h)) #define GETW(dim) ((dim) >> 16) #define GETH(dim) ((dim) & 0xffff) extern GxEPD2_GFX_BASE_CLASS &get_display(); extern U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; static StaticJsonDocument<16000> weather_json; static DeserializationError json_state = DeserializationError::IncompleteInput; void fetch_weather() { /* use HTTP intentionally for simplicity don't use on insecure networks */ String url = "http://api.openweathermap.org/data/2.5/onecall?units=metric&lang=de&exclude=minutely,hourly"; url += "&appid=" + String(openweathermap_apikey); url += "&lat=" + String(lat); url += "&lon=" + String(lon); HTTPClient http; http.begin(url); int status = http.GET(); String weather_info = ""; if (status == HTTP_CODE_OK) { weather_info = http.getString(); } else { Serial.println("http error\r\n"); } http.end(); json_state = deserializeJson(weather_json, weather_info); if (json_state != DeserializationError::Ok) { Serial.println("json deserialization error\r\n"); } } static uint32_t draw_icon(int16_t x, int16_t y, const unsigned char *image, uint16_t width, uint16_t height) { get_display().drawXBitmap(x, y, image, width, height, GxEPD_BLACK); return DIM(width, height); } static uint32_t draw_weather_icon(int16_t x, int16_t y, int code) { const unsigned char *image = NULL; uint16_t width = 0, height = 0; int icon_group = code / 100; switch (icon_group) { case 2: image = thunderstorm_bits; width = thunderstorm_width; height = thunderstorm_height; break; case 3: case 5: image = rain_bits; width = rain_width; height = rain_height; break; case 6: image = snow_bits; width = snow_width; height = snow_height; break; case 7: image = fog_bits; width = fog_width; height = fog_height; break; case 8: image = cloudy_bits; width = cloudy_width; height = cloudy_height; break; } if (code == 800 || !image) { /* special case, clear sky / no clouds */ image = sunny_bits; width = sunny_width; height = sunny_height; } return draw_icon(x, y, image, width, height); } static uint32_t draw_temperature(int16_t x, int16_t y, float temp, float temp_max = 0.0) { uint16_t w = thermometer_width, h = thermometer_height; int text_width = 0; draw_icon(x, y, thermometer_bits, w, h); u8g2Fonts.setCursor(x + w + SPACING, y + h/2 + FONTSIZE/2); if (temp_max > 0) { u8g2Fonts.printf("%.1f - %.1f °C", temp, temp_max); text_width = u8g2Fonts.getUTF8Width("88.8 - 88.8 °C"); } else { u8g2Fonts.printf("%.1f °C", temp); text_width = u8g2Fonts.getUTF8Width("88.8 °C"); } return DIM(w + SPACING + text_width, h); } static uint32_t draw_humidity(int16_t x, int16_t y, int humidity) { uint16_t w = humidity_width, h = humidity_height; draw_icon(x, y, humidity_bits, w, h); u8g2Fonts.setCursor(x + w + SPACING, y + h/2 + FONTSIZE/2); u8g2Fonts.printf("%d %%", humidity); return DIM(w, h); } static uint32_t draw_rain_prob(int16_t x, int16_t y, uint8_t rain) { uint16_t w = raindrops_width, h = raindrops_width; draw_icon(x, y, raindrops_bits, w, h); u8g2Fonts.setCursor(x + w + SPACING, y + h/2 + FONTSIZE/2); u8g2Fonts.printf("%d %%", rain); int text_width = u8g2Fonts.getUTF8Width("88 %"); return DIM(w + SPACING + text_width, h); } static uint32_t draw_pressure(int16_t x, int16_t y, int pressure) { uint16_t w = barometer_width, h = barometer_height; draw_icon(x, y, barometer_bits, w, h); u8g2Fonts.setCursor(x + w + SPACING, y + h/2 + FONTSIZE/2); u8g2Fonts.printf("%d hPa", pressure); return DIM(w, h); } static uint32_t draw_clouds(int16_t x, int16_t y, int clouds) { uint16_t w = cloud_width, h = cloud_height; draw_icon(x, y, cloud_bits, w, h); u8g2Fonts.setCursor(x + w + SPACING, y + h/2 + FONTSIZE/2); u8g2Fonts.printf("%d %%", clouds); int text_width = u8g2Fonts.getUTF8Width("88 %"); return DIM(w + SPACING + text_width, h); } static int draw_weather_forecast(int16_t x, int16_t y, const char *title, const JsonObject &json) { auto temp_min = json["temp"]["min"].as(); auto temp_max = json["temp"]["max"].as(); auto pop = (uint8_t) (100 * json["pop"].as()); auto cloudiness = json["clouds"].as(); auto description = json["weather"][0]["description"].as(); /* line 1 */ u8g2Fonts.setFont(u8g2_font_helvB10_tf); u8g2Fonts.setCursor(x, y); u8g2Fonts.print(title); u8g2Fonts.setFont(u8g2_font_helvR10_tf); u8g2Fonts.print(" "); u8g2Fonts.print(description); /* line 2 */ uint32_t dim = 0; y += SPACING; dim = draw_temperature(x, y, temp_min, temp_max); x += GETW(dim) + SPACING; dim = draw_clouds(x, y, cloudiness); x += GETW(dim) + SPACING; dim = draw_rain_prob(x, y, pop); return DIM(0, FONTSIZE + GETH(dim) + 2 * SPACING); } void draw_weather(int16_t x0, int16_t y0) { if (json_state != DeserializationError::Ok) return; /* current weather */ auto current_temp = weather_json["current"]["temp"].as(); auto current_weather_id = weather_json["current"]["weather"][0]["id"].as(); auto current_weather_desc = weather_json["current"]["weather"][0]["description"].as(); auto current_clouds = weather_json["current"]["clouds"].as(); auto current_humidity = weather_json["current"]["humidity"].as(); auto current_pressure = weather_json["current"]["pressure"].as(); if (!current_weather_desc) current_weather_desc = "N/A"; u8g2Fonts.setFont(u8g2_font_helvR10_tf); uint32_t dim = 0; int16_t x1 = x0, y1 = y0; dim = draw_weather_icon(x0, y0, current_weather_id); x1 += GETW(dim) + SPACING; y1 += GETH(dim) + SPACING; /* line 1 */ int16_t x = x1, y = y0 + 2 * SPACING; uint32_t temp_dim = draw_temperature(x, y, current_temp); x += GETW(temp_dim) + 2 * SPACING; dim = draw_clouds(x, y, current_clouds); y += GETH(dim); /* line 2 */ x = x1; dim = draw_humidity(x, y, current_humidity); x += GETW(temp_dim) + 2 * SPACING; dim = draw_pressure(x, y, current_pressure); y += GETH(dim); /* forecasts */ x = x0; y = y1; dim = draw_weather_forecast(x, y, "Heute:", weather_json["daily"][0]); y += GETH(dim); dim = draw_weather_forecast(x, y, "Morgen:", weather_json["daily"][1]); y += GETH(dim); dim = draw_weather_forecast(x, y, "Übermorgen:", weather_json["daily"][2]); y += GETH(dim); }