diff --git a/platformio/include/_locale.h b/platformio/include/_locale.h index bb16702eb..f3ee4b373 100644 --- a/platformio/include/_locale.h +++ b/platformio/include/_locale.h @@ -97,6 +97,12 @@ extern const char *TXT_UNITS_PRECIP_MILLIMETERS; extern const char *TXT_UNITS_PRECIP_CENTIMETERS; extern const char *TXT_UNITS_PRECIP_INCHES; +// NEXT HOUR RAIN FORECAST +extern const char *TXT_RAIN_CONTINUOUS; +extern const char *TXT_RAIN_UNTIL; +extern const char *TXT_RAIN_FROM; +extern const char *TXT_RAIN_FROM_TO; + // MISCELLANEOUS MESSAGES // Title Case extern const char *TXT_LOW_BATTERY; diff --git a/platformio/include/api_response.h b/platformio/include/api_response.h index 8391dec2f..0f1a3316c 100644 --- a/platformio/include/api_response.h +++ b/platformio/include/api_response.h @@ -25,7 +25,7 @@ #include #include -#define OWM_NUM_MINUTELY 1 // 61 +#define OWM_NUM_MINUTELY 60 // 60 #define OWM_NUM_HOURLY 48 // 48 #define OWM_NUM_DAILY 8 // 8 #define OWM_NUM_ALERTS 8 // OpenWeatherMaps does not specify a limit, but if you need more alerts you are probably doomed. @@ -93,7 +93,7 @@ typedef struct owm_current typedef struct owm_minutely { int64_t dt; // Time of the forecasted data, unix, UTC - float precipitation; // Precipitation volume, mm + float precipitation; // Precipitation volume, mm/h } owm_minutely_t; /* @@ -172,8 +172,7 @@ typedef struct owm_resp_onecall String timezone; // Timezone name for the requested location int timezone_offset; // Shift in seconds from UTC owm_current_t current; - // owm_minutely_t minutely[OWM_NUM_MINUTELY]; - + owm_minutely_t minutely[OWM_NUM_MINUTELY]; owm_hourly_t hourly[OWM_NUM_HOURLY]; owm_daily_t daily[OWM_NUM_DAILY]; std::vector alerts; diff --git a/platformio/include/config.h b/platformio/include/config.h index fe67b213d..54fbb7469 100644 --- a/platformio/include/config.h +++ b/platformio/include/config.h @@ -249,6 +249,16 @@ // Disable alerts by changing the DISPLAY_ALERTS macro to 0. #define DISPLAY_ALERTS 1 +// When there are no alerts to display (either DISPLAY_ALERTS is set to 0 or +// there are no current alerts), the precipirations for the next hour can be +// displayed in the alerts area. +// Disable by changing the DISPLAY_NEXT_HOUR_PRECIP macro to 0. +#define DISPLAY_NEXT_HOUR_PRECIP 1 +// Threshold to consider precipitation as "rain" for a period (mm/h) +#define PRECIP_THRESHOLD_PERIOD 0.1f +// Maximum gap in minutes to group precipitations as a single period +#define PRECIP_MAX_GAP_MINUTES 5 + // STATUS BAR EXTRAS // Extra information that can be displayed on the status bar. Set to 1 to // enable. diff --git a/platformio/include/locales/locale_de_DE.inc b/platformio/include/locales/locale_de_DE.inc index 43ed970b9..892dd3365 100644 --- a/platformio/include/locales/locale_de_DE.inc +++ b/platformio/include/locales/locale_de_DE.inc @@ -133,6 +133,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Low Battery"; diff --git a/platformio/include/locales/locale_en_GB.inc b/platformio/include/locales/locale_en_GB.inc index 957a51ea4..626ca8bde 100644 --- a/platformio/include/locales/locale_en_GB.inc +++ b/platformio/include/locales/locale_en_GB.inc @@ -125,6 +125,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Low Battery"; diff --git a/platformio/include/locales/locale_en_US.inc b/platformio/include/locales/locale_en_US.inc index ecee9e3f8..c4bc4f918 100644 --- a/platformio/include/locales/locale_en_US.inc +++ b/platformio/include/locales/locale_en_US.inc @@ -125,6 +125,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Low Battery"; diff --git a/platformio/include/locales/locale_et_EE.inc b/platformio/include/locales/locale_et_EE.inc index be7d8795d..d70acc299 100644 --- a/platformio/include/locales/locale_et_EE.inc +++ b/platformio/include/locales/locale_et_EE.inc @@ -132,6 +132,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Aku t\xFChi"; diff --git a/platformio/include/locales/locale_fi_FI.inc b/platformio/include/locales/locale_fi_FI.inc index 566c3f89a..d852cca8d 100644 --- a/platformio/include/locales/locale_fi_FI.inc +++ b/platformio/include/locales/locale_fi_FI.inc @@ -133,6 +133,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Low Battery"; diff --git a/platformio/include/locales/locale_fr_FR.inc b/platformio/include/locales/locale_fr_FR.inc index c7841bb9f..7550b0a54 100644 --- a/platformio/include/locales/locale_fr_FR.inc +++ b/platformio/include/locales/locale_fr_FR.inc @@ -133,6 +133,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Pluie sans interruption"; +const char *TXT_RAIN_UNTIL = "Pluie jusqu'\340 %s"; +const char *TXT_RAIN_FROM = "Pluie \340 partir de %s"; +const char *TXT_RAIN_FROM_TO = "Pluie de %s \340 %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Batterie Faible"; diff --git a/platformio/include/locales/locale_it_IT.inc b/platformio/include/locales/locale_it_IT.inc index 3d31ae935..94fd043e1 100644 --- a/platformio/include/locales/locale_it_IT.inc +++ b/platformio/include/locales/locale_it_IT.inc @@ -133,6 +133,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Batteria quasi scarica"; diff --git a/platformio/include/locales/locale_nl_BE.inc b/platformio/include/locales/locale_nl_BE.inc index ef8bbcfbb..188a6b4b1 100644 --- a/platformio/include/locales/locale_nl_BE.inc +++ b/platformio/include/locales/locale_nl_BE.inc @@ -132,6 +132,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Low Battery"; diff --git a/platformio/include/locales/locale_pt_BR.inc b/platformio/include/locales/locale_pt_BR.inc index bb69b9539..705070a7d 100644 --- a/platformio/include/locales/locale_pt_BR.inc +++ b/platformio/include/locales/locale_pt_BR.inc @@ -133,6 +133,12 @@ const char *TXT_UNITS_PRECIP_MILLIMETERS = "mm"; const char *TXT_UNITS_PRECIP_CENTIMETERS = "cm"; const char *TXT_UNITS_PRECIP_INCHES = "in"; +// NEXT HOUR RAIN FORECAST +const char *TXT_RAIN_CONTINUOUS = "Continuous rain"; +const char *TXT_RAIN_UNTIL = "Rain until %s"; +const char *TXT_RAIN_FROM = "Rain from %s"; +const char *TXT_RAIN_FROM_TO = "Rain from %s to %s"; + // MISCELLANEOUS MESSAGES // Title Case const char *TXT_LOW_BATTERY = "Bateria Baixa"; diff --git a/platformio/include/renderer.h b/platformio/include/renderer.h index 798a8d109..05322dc81 100644 --- a/platformio/include/renderer.h +++ b/platformio/include/renderer.h @@ -77,6 +77,8 @@ void drawCurrentConditions(const owm_current_t ¤t, void drawForecast(const owm_daily_t *daily, tm timeInfo); void drawAlerts(std::vector &alerts, const String &city, const String &date); +void drawNextHourPrecip(const owm_minutely_t *minutely, + const String &city, const String &date); void drawLocationDate(const String &city, const String &date); void drawOutlookGraph(const owm_hourly_t *hourly, const owm_daily_t *daily, tm timeInfo); diff --git a/platformio/src/api_response.cpp b/platformio/src/api_response.cpp index 27f7b9a32..9549e7c89 100644 --- a/platformio/src/api_response.cpp +++ b/platformio/src/api_response.cpp @@ -27,7 +27,7 @@ DeserializationError deserializeOneCall(WiFiClient &json, JsonDocument filter; filter["current"] = true; - filter["minutely"] = false; + filter["minutely"] = DISPLAY_NEXT_HOUR_PRECIP; filter["hourly"] = true; filter["daily"] = true; #if !DISPLAY_ALERTS @@ -89,19 +89,20 @@ DeserializationError deserializeOneCall(WiFiClient &json, r.current.weather.description = current_weather["description"].as(); r.current.weather.icon = current_weather["icon"] .as(); - // minutely forecast is currently unused - // i = 0; - // for (JsonObject minutely : doc["minutely"].as()) - // { - // r.minutely[i].dt = minutely["dt"] .as(); - // r.minutely[i].precipitation = minutely["precipitation"].as(); - - // if (i == OWM_NUM_MINUTELY - 1) - // { - // break; - // } - // ++i; - // } +#if DISPLAY_NEXT_HOUR_PRECIP + i = 0; + for (JsonObject minutely : doc["minutely"].as()) + { + r.minutely[i].dt = minutely["dt"] .as(); + r.minutely[i].precipitation = minutely["precipitation"].as(); + + if (i == OWM_NUM_MINUTELY - 1) + { + break; + } + ++i; + } +#endif i = 0; for (JsonObject hourly : doc["hourly"].as()) diff --git a/platformio/src/client_utils.cpp b/platformio/src/client_utils.cpp index 36b5a6cbf..c0ef06e26 100644 --- a/platformio/src/client_utils.cpp +++ b/platformio/src/client_utils.cpp @@ -154,10 +154,13 @@ bool waitForSNTPSync(tm *timeInfo) DeserializationError jsonErr = {}; String uri = "/data/" + OWM_ONECALL_VERSION + "/onecall?lat=" + LAT + "&lon=" + LON + "&lang=" + OWM_LANG - + "&units=standard&exclude=minutely"; -#if !DISPLAY_ALERTS - // exclude alerts - uri += ",alerts"; + + "&units=standard"; +#if !DISPLAY_NEXT_HOUR_PRECIP && !DISPLAY_ALERTS + uri += "&exclude=minutely,alerts"; +#elif !DISPLAY_NEXT_HOUR_PRECIP + uri += "&exclude=minutely"; +#elif !DISPLAY_ALERTS + uri += "&exclude=alerts"; #endif // This string is printed to terminal to help with debugging. The API key is diff --git a/platformio/src/main.cpp b/platformio/src/main.cpp index ec74ebc80..1c35d03b2 100644 --- a/platformio/src/main.cpp +++ b/platformio/src/main.cpp @@ -350,6 +350,13 @@ void setup() drawLocationDate(CITY_STRING, dateStr); #if DISPLAY_ALERTS drawAlerts(owm_onecall.alerts, CITY_STRING, dateStr); +#endif +#if DISPLAY_NEXT_HOUR_PRECIP + // Display next hour precipitations at the alerts location if none + if (owm_onecall.alerts.size() == 0) + { + drawNextHourPrecip(owm_onecall.minutely, CITY_STRING, dateStr); + } #endif drawStatusBar(statusStr, refreshTimeStr, wifiRSSI, batteryVoltage); } while (display.nextPage()); diff --git a/platformio/src/renderer.cpp b/platformio/src/renderer.cpp index 27b6daff6..095b22ac9 100644 --- a/platformio/src/renderer.cpp +++ b/platformio/src/renderer.cpp @@ -872,6 +872,118 @@ void drawForecast(const owm_daily_t *daily, tm timeInfo) return; } // end drawAlerts + +void drawNextHourPrecip(const owm_minutely_t *minutely, + const String &city, const String &date) +{ + float period_precipitation_mm = 0; + + // Find continuous rain periods with gap tolerance + int rain_start_idx = -1; + int rain_end_idx = -1; + int gap_counter = 0; + + for (int i = 0; i < OWM_NUM_MINUTELY; i++) + { + if (minutely[i].precipitation > 0) + { + // Value is given in mm/h + period_precipitation_mm += (minutely[i].precipitation / 60); + + // Start of rain period + if (rain_start_idx == -1) { + rain_start_idx = i; + } + + // Reset gap counter while it rains + gap_counter = 0; + rain_end_idx = i; + } + else if (rain_start_idx != -1) + { + // We're in a potential gap + gap_counter++; + + // If gap exceeds threshold, close the rain period + if (gap_counter > PRECIP_MAX_GAP_MINUTES) + { + rain_end_idx = i - gap_counter; + break; + } + } + } + + if (rain_start_idx == -1 || period_precipitation_mm < PRECIP_THRESHOLD_PERIOD) + { + #if DEBUG_LEVEL >= 1 + Serial.println("[debug] no rain forecasted within the next hour"); + #endif + return; + } + + + char timeBufferStart[12] = {}; // big enough to accommodate "hh:mm:ss am" + if (rain_start_idx >= 0 && rain_start_idx < OWM_NUM_MINUTELY) { + time_t ts = minutely[rain_start_idx].dt; + tm *timeInfo = localtime(&ts); + _strftime(timeBufferStart, sizeof(timeBufferStart), TIME_FORMAT, timeInfo); + } + + char timeBufferEnd[12] = {}; // big enough to accommodate "hh:mm:ss am" + if (rain_end_idx >= 0 && rain_end_idx < OWM_NUM_MINUTELY) { + time_t ts = minutely[rain_end_idx].dt; + tm *timeInfo = localtime(&ts); + _strftime(timeBufferEnd, sizeof(timeBufferEnd), TIME_FORMAT, timeInfo); + } + + char rain_alert_msg[100]; // big enough to accommodate full message based on the language + + if (rain_start_idx == 0) + { + if (rain_end_idx == OWM_NUM_MINUTELY - 1) + { + sprintf(rain_alert_msg, TXT_RAIN_CONTINUOUS); + } + else + { + sprintf(rain_alert_msg, TXT_RAIN_UNTIL, timeBufferEnd); + } + } + else + { + if (rain_end_idx == OWM_NUM_MINUTELY - 1) + { + sprintf(rain_alert_msg, TXT_RAIN_FROM, timeBufferStart); + } + else + { + sprintf(rain_alert_msg, TXT_RAIN_FROM_TO, timeBufferStart, timeBufferEnd); + } + } + + // TODO: Share this render code with drawAlerts ? + // But is expects owm_alerts_t and uses an icom the the event message which is not localized. + + display.setFont(&FONT_16pt8b); + int city_w = getStringWidth(city); + display.setFont(&FONT_12pt8b); + int date_w = getStringWidth(date); + int max_w = DISP_WIDTH - 2 - std::max(city_w, date_w) - (196 + 4) - 8; + + max_w -= 48; + display.drawInvertedBitmap(196, 8, wi_rain_48x48, 48, 48, GxEPD_BLACK); + + display.setFont(&FONT_12pt8b); + if (getStringWidth(rain_alert_msg) <= max_w) + { // Fits on a single line with smaller font, draw along bottom + drawString(196 + 48 + 4, 24 + 8 - 12 + 17 + 1, rain_alert_msg, LEFT); + } + else + { // Does not fit on a single line, draw higher to allow room for 2nd line + drawMultiLnString(196 + 48 + 4, 24 + 8 - 12 + 17 - 11, rain_alert_msg, LEFT, max_w, 2, 23); + } +} + /* This function is responsible for drawing the city string and date * information in the top right corner. */