diff options
| author | Reiner Herrmann <reiner@reiner-h.de> | 2020-07-22 20:00:22 +0200 |
|---|---|---|
| committer | Reiner Herrmann <reiner@reiner-h.de> | 2020-07-22 20:00:22 +0200 |
| commit | c297f65f1d95f80df597d0cea49cc92dfdcae0a3 (patch) | |
| tree | dc8a7a49eabcaeab80c50a588e242a51e0175e56 /src | |
| parent | 642df2851dd333178e509731c513480b2adb6278 (diff) | |
add current weather and forecast
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 7 | ||||
| -rw-r--r-- | src/weather.cpp | 226 |
2 files changed, 231 insertions, 2 deletions
diff --git a/src/main.cpp b/src/main.cpp index ee7435a..c987f92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include "calendar.h" #include "draw_qrcode.h" +#include "weather.h" #include "secrets.h" /* @@ -48,8 +49,9 @@ void draw() { do { display.fillScreen(GxEPD_WHITE); - draw_calendar(30, 30); - draw_qrcode_wlan(30, 200, wifi_ssid, wifi_psk); + draw_calendar(30, 10); + draw_weather(10, 180); + //draw_qrcode_wlan(30, 200, wifi_ssid, wifi_psk); } while(display.nextPage()); } @@ -76,6 +78,7 @@ void setup() { wifi_connect(); update_time(); + fetch_weather(); wifi_disconnect(); display_setup(); diff --git a/src/weather.cpp b/src/weather.cpp new file mode 100644 index 0000000..e38e44f --- /dev/null +++ b/src/weather.cpp @@ -0,0 +1,226 @@ +#include <GxEPD2_BW.h> +#include <U8g2_for_Adafruit_GFX.h> +#include <HTTPClient.h> +#include <ArduinoJson.h> + +#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<float>(); + auto temp_max = json["temp"]["max"].as<float>(); + auto pop = (uint8_t) (100 * json["pop"].as<float>()); + auto cloudiness = json["clouds"].as<int>(); + auto description = json["weather"][0]["description"].as<char *>(); + + /* 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<float>(); + auto current_weather_id = weather_json["current"]["weather"][0]["id"].as<int>(); + auto current_weather_desc = weather_json["current"]["weather"][0]["description"].as<char *>(); + auto current_clouds = weather_json["current"]["clouds"].as<int>(); + auto current_humidity = weather_json["current"]["humidity"].as<int>(); + auto current_pressure = weather_json["current"]["pressure"].as<int>(); + + 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); +} |
