From f26ae5d8047360d05ba5db6c28d9106be695c304 Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Wed, 1 Nov 2023 17:16:47 -0400 Subject: [PATCH] weather: Add support for conditions Use OpenWeatherMap conditions to determine the correct condition icon. This is more accurate than attempting to calculate the condition based on precipitation and cloud cover, which often yields the wrong condition. Companion PR to Gadgetbridge: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3407 --- src/components/ble/weather/WeatherData.h | 34 ++++++++++++ src/components/ble/weather/WeatherService.cpp | 52 +++++++++++++++++++ src/components/ble/weather/WeatherService.h | 2 + src/displayapp/fonts/fonts.json | 4 +- src/displayapp/screens/Symbols.h | 2 + .../screens/WatchFacePineTimeStyle.cpp | 47 +++++++++++------ .../screens/WatchFacePineTimeStyle.h | 4 +- src/displayapp/screens/Weather.cpp | 39 +++++++++++++- src/displayapp/screens/Weather.h | 4 +- 9 files changed, 165 insertions(+), 23 deletions(-) diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h index 1a995eb96f..700bcd4966 100644 --- a/src/components/ble/weather/WeatherData.h +++ b/src/components/ble/weather/WeatherData.h @@ -126,6 +126,24 @@ namespace Pinetime { Length }; + /** + * List of weather condition codes used to determine display + * (range of thunderstorm, drizzle, rain, snow, clouds, atmosphere including extreme conditions like tornado, hurricane etc.) + * https://openweathermap.org/weather-conditions + */ + enum class conditiontype { + ClearSky = 0, + FewClouds = 1, + ScatteredClouds = 2, + BrokenClouds = 3, + ShowerRain = 4, + Rain = 5, + Thunderstorm = 6, + Snow = 7, + Mist = 8, + Length + }; + /** * These are used for weather timeline manipulation * that isn't just adding to the stack of weather events @@ -165,6 +183,8 @@ namespace Pinetime { Clouds = 8, /** @see humidity */ Humidity = 9, + /** @see condition */ + Condition = 10, Length }; @@ -326,6 +346,20 @@ namespace Pinetime { specialtype type; }; + /** How weather condition is stored */ + class Condition : public TimelineHeader { + public: + /** + * Type of condition + */ + conditiontype type; + + /** + * Condition code + */ + uint16_t code; + }; + /** * How air quality is stored * diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp index b9a6af556c..bb01fb0b13 100644 --- a/src/components/ble/weather/WeatherService.cpp +++ b/src/components/ble/weather/WeatherService.cpp @@ -357,6 +357,34 @@ namespace Pinetime { } break; } + case WeatherData::eventtype::Condition: { + std::unique_ptr condition = std::make_unique(); + condition->timestamp = tmpTimestamp; + condition->eventType = static_cast(tmpEventType); + condition->expires = tmpExpires; + + int64_t tmpType = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType); + if (tmpType < 0 || tmpType >= static_cast(WeatherData::conditiontype::Length)) { + CleanUpQcbor(&decodeContext); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + condition->type = static_cast(tmpType); + + int64_t tmpCode = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "Code", &tmpCode); + if (tmpCode < 0 || tmpCode >= 65535) { + CleanUpQcbor(&decodeContext); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + condition->code = static_cast(tmpCode); + + if (!AddEventToTimeline(std::move(condition))) { + CleanUpQcbor(&decodeContext); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + break; + } default: { CleanUpQcbor(&decodeContext); return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; @@ -509,6 +537,30 @@ namespace Pinetime { return reinterpret_cast&>(*this->nullHeader); } + std::unique_ptr& WeatherService::GetCurrentSpecial() { + uint64_t currentTimestamp = GetCurrentUnixTimestamp(); + for (auto&& header : this->timeline) { + if (header->eventType == WeatherData::eventtype::Special && currentTimestamp >= header->timestamp && + IsEventStillValid(header, currentTimestamp)) { + return reinterpret_cast&>(header); + } + } + + return reinterpret_cast&>(*this->nullHeader); + } + + std::unique_ptr& WeatherService::GetCurrentCondition() { + uint64_t currentTimestamp = GetCurrentUnixTimestamp(); + for (auto&& header : this->timeline) { + if (header->eventType == WeatherData::eventtype::Condition && currentTimestamp >= header->timestamp && + IsEventStillValid(header, currentTimestamp)) { + return reinterpret_cast&>(header); + } + } + + return reinterpret_cast&>(*this->nullHeader); + } + size_t WeatherService::GetTimelineLength() const { return timeline.size(); } diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h index 609e8760d6..19f209b26d 100644 --- a/src/components/ble/weather/WeatherService.h +++ b/src/components/ble/weather/WeatherService.h @@ -58,6 +58,8 @@ namespace Pinetime { std::unique_ptr& GetCurrentHumidity(); std::unique_ptr& GetCurrentPressure(); std::unique_ptr& GetCurrentQuality(); + std::unique_ptr& GetCurrentSpecial(); + std::unique_ptr& GetCurrentCondition(); /** * Searches for the current day's maximum temperature diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 8416fc5ef3..c8193695df 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -7,7 +7,7 @@ }, { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c" + "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf5d7, 0xf6d3, 0xf6be, 0xf4ba, 0xf6c4" } ], "bpp": 1, @@ -68,7 +68,7 @@ "sources": [ { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e" + "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf0e7, 0xf2dc" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index 7154ff44c1..025f1bede4 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -45,6 +45,8 @@ namespace Pinetime { static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80"; static constexpr const char* smog = "\xEF\x9D\x9F"; static constexpr const char* cloud = "\xEF\x83\x82"; + static constexpr const char* bolt = "\xEF\x83\xA7"; + static constexpr const char* snowflake = "\xEF\x8B\x9C"; static constexpr const char* ban = "\xEF\x81\x9E"; // lv_font_sys_48.c diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index 250a745c15..a8e84471f6 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -30,10 +30,10 @@ #include "components/battery/BatteryController.h" #include "components/ble/BleController.h" #include "components/ble/NotificationManager.h" +#include "components/ble/weather/WeatherService.h" #include "components/motion/MotionController.h" #include "components/settings/Settings.h" #include "displayapp/DisplayApp.h" -#include "components/ble/weather/WeatherService.h" using namespace Pinetime::Applications::Screens; @@ -537,24 +537,39 @@ void WatchFacePineTimeStyle::Refresh() { } } - if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 && - weatherService.GetCurrentPrecipitation()->timestamp != 0) { + if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentCondition()->timestamp != 0) { nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100); - clouds = (weatherService.GetCurrentClouds()->amount); - precip = (weatherService.GetCurrentPrecipitation()->amount); if (nowTemp.IsUpdated()) { lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get()); - if ((clouds <= 30) && (precip == 0)) { - lv_label_set_text(weatherIcon, Symbols::sun); - } else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) { - lv_label_set_text(weatherIcon, Symbols::cloudSunRain); - } else if ((clouds > 90) && (precip == 0)) { - lv_label_set_text(weatherIcon, Symbols::cloud); - } else if ((clouds > 70) && (precip >= 2)) { - lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy); - } else { - lv_label_set_text(weatherIcon, Symbols::cloudSun); - }; + switch (weatherService.GetCurrentCondition()->type) { + case Controllers::WeatherData::conditiontype::ClearSky: + lv_label_set_text(weatherIcon, Symbols::sun); + break; + case Controllers::WeatherData::conditiontype::FewClouds: + case Controllers::WeatherData::conditiontype::ScatteredClouds: + lv_label_set_text(weatherIcon, Symbols::cloudSun); + break; + case Controllers::WeatherData::conditiontype::BrokenClouds: + lv_label_set_text(weatherIcon, Symbols::cloud); + break; + case Controllers::WeatherData::conditiontype::ShowerRain: + lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy); + break; + case Controllers::WeatherData::conditiontype::Rain: + lv_label_set_text(weatherIcon, Symbols::cloudSunRain); + break; + case Controllers::WeatherData::conditiontype::Thunderstorm: + lv_label_set_text(weatherIcon, Symbols::bolt); + break; + case Controllers::WeatherData::conditiontype::Snow: + lv_label_set_text(weatherIcon, Symbols::snowflake); + break; + case Controllers::WeatherData::conditiontype::Mist: + lv_label_set_text(weatherIcon, Symbols::smog); + break; + default: + break; + } lv_obj_realign(temperature); lv_obj_realign(weatherIcon); } diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h index e157bb2cd2..42433fe16b 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.h +++ b/src/displayapp/screens/WatchFacePineTimeStyle.h @@ -32,7 +32,7 @@ namespace Pinetime { Controllers::NotificationManager& notificationManager, Controllers::Settings& settingsController, Controllers::MotionController& motionController, - Controllers::WeatherService& weather); + Controllers::WeatherService& weatherService); ~WatchFacePineTimeStyle() override; bool OnTouchEvent(TouchEvents event) override; @@ -61,8 +61,6 @@ namespace Pinetime { Utility::DirtyValue stepCount {}; Utility::DirtyValue notificationState {}; Utility::DirtyValue nowTemp {}; - int16_t clouds = 0; - int16_t precip = 0; static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color); static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color); diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 4921174c7a..3ce6fce0be 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -33,6 +33,9 @@ Weather::Weather(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers: {[this]() -> std::unique_ptr { return CreateScreenTemperature(); }, + [this]() -> std::unique_ptr { + return CreateScreenCondition(); + }, [this]() -> std::unique_ptr { return CreateScreenAir(); }, @@ -60,7 +63,7 @@ void Weather::Refresh() { bool Weather::OnButtonPushed() { running = false; - return true; + return false; } bool Weather::OnTouchEvent(Pinetime::Applications::TouchEvents event) { @@ -100,6 +103,40 @@ std::unique_ptr Weather::CreateScreenTemperature() { return std::unique_ptr(new Screens::Label(0, 5, label)); } +std::unique_ptr Weather::CreateScreenCondition() { + lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_recolor(label, true); + std::unique_ptr& current = weatherService.GetCurrentCondition(); + if (current->timestamp == 0) { + // Do not use the data, it's invalid + lv_label_set_text_fmt(label, + "#FFFF00 Condition#\n\n" + "#444444 %d# \n\n" + "#444444 %d# \n\n" + "%d\n" + "%d\n", + 0, + 0, + 0, + 0, + 0); + } else { + lv_label_set_text_fmt(label, + "#FFFF00 Condition#\n\n" + "#444444 %d# \n\n" + "#444444 %d# \n\n" + "%llu\n" + "%lu\n", + current->type, + current->code, + current->timestamp, + current->expires); + } + lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); + lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0); + return std::unique_ptr(new Screens::Label(0, 5, label)); +} + std::unique_ptr Weather::CreateScreenAir() { lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_recolor(label, true); diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 84177ea6fd..1761d4ad55 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -31,10 +31,12 @@ namespace Pinetime { Controllers::WeatherService& weatherService; - ScreenList<5> screens; + ScreenList<6> screens; std::unique_ptr CreateScreenTemperature(); + std::unique_ptr CreateScreenCondition(); + std::unique_ptr CreateScreenAir(); std::unique_ptr CreateScreenClouds();