diff options
| author | Victor Kareh <vkareh@redhat.com> | 2026-01-05 07:12:08 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-05 13:12:08 +0100 |
| commit | f88c69a31aadb0fdfe6555db626ba8f05e756272 (patch) | |
| tree | f173289f96c89a38afc5198a6b61e192cf372118 | |
| parent | 51a6fb6a7ed7a410bb141966a43a7649d10bbdfc (diff) | |
SimpleWeatherService: Add sunrise and sunset data (#2100)
* SimpleWeatherService: Add sunrise and sunset data
---------
Co-authored-by: mark9064 <30447455+mark9064@users.noreply.github.com>
| -rw-r--r-- | doc/SimpleWeatherService.md | 19 | ||||
| -rw-r--r-- | src/components/ble/SimpleWeatherService.cpp | 71 | ||||
| -rw-r--r-- | src/components/ble/SimpleWeatherService.h | 12 | ||||
| -rw-r--r-- | src/displayapp/fonts/fonts.json | 2 | ||||
| -rw-r--r-- | src/displayapp/screens/Symbols.h | 3 | ||||
| -rw-r--r-- | src/displayapp/screens/WatchFaceDigital.cpp | 2 | ||||
| -rw-r--r-- | src/displayapp/screens/WatchFacePineTimeStyle.cpp | 2 | ||||
| -rw-r--r-- | src/displayapp/screens/Weather.cpp | 4 | ||||
| -rw-r--r-- | src/displayapp/screens/WeatherSymbols.cpp | 12 | ||||
| -rw-r--r-- | src/displayapp/screens/WeatherSymbols.h | 2 |
10 files changed, 112 insertions, 17 deletions
diff --git a/doc/SimpleWeatherService.md b/doc/SimpleWeatherService.md index e0fffba7..64063d75 100644 --- a/doc/SimpleWeatherService.md +++ b/doc/SimpleWeatherService.md @@ -17,23 +17,25 @@ The host uses this characteristic to update the current weather information and This characteristics accepts a byte array with the following 2-Bytes header: - - [0] Message Type : + - [0] Message Type : - `0` : Current weather - `1` : Forecast - - [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases + - [1] Message Version : + - `0` : Currently supported + - `1` : Adds support for sunrise and sunset -### Current Weather +### Current Weather The byte array must contain the following data: - [0] : Message type = `0` - - [1] : Message version = `0` + - [1] : Message version = `1` - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time) - [10, 11] : Current temperature (°C * 100) - [12, 13] : Minimum temperature (°C * 100) - [14, 15] : Maximum temperature (°C * 100) - [16]..[47] : location (string, unused characters should be set to `0`) - - [48] : icon ID + - [48] : icon ID - 0 = Sun, clear sky - 1 = Few clouds - 2 = Clouds @@ -43,6 +45,13 @@ The byte array must contain the following data: - 6 = Thunderstorm - 7 = Snow - 8 = Mist, smog + - [49, 50] : Sunrise (number of minutes elapsed since midnight) + - `0` sun already up when day starts + - `-1` unknown + - `-2` no sunrise (e.g. polar night) + - [51, 52] : Sunset (number of minutes elapsed since midnight) + - `-1` unknown + - `-2` no sunset (e.g. polar day) ### Forecast diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index 51baf543..c2da9305 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -41,12 +41,48 @@ namespace { SimpleWeatherService::Location cityName; std::memcpy(cityName.data(), &dataBuffer[16], 32); cityName[32] = '\0'; + int16_t sunrise = -1; + int16_t sunset = -1; + if (dataBuffer[1] > 0) { + int16_t bufferSunrise = ToInt16(&dataBuffer[49]); + int16_t bufferSunset = ToInt16(&dataBuffer[51]); + + // Sunrise/sunset format + + // Assume sun is down at minute 0 / midnight + + // 0<=x<1440 sunrise happens this many minutes into the day + // (0 day starts with sun up) + // -1 unknown + // -2 sun not rising today + + // 0<x<1440 sunset happens this many minutes into the day + // -1 unknown + // -2 sun not setting today + + // Check if the weather data is well formed + // Disable boolean simplification suggestion, as simplifying it makes it unreadable + if (!( // NOLINT(readability-simplify-boolean-expr) + // Fail if either unknown + (bufferSunrise == -1 || bufferSunset == -1) + // Cannot be out of range + || (bufferSunrise < -2 || bufferSunrise > 1439 || bufferSunset < -2 || bufferSunset > 1439) + // Cannot have sunset without sunrise + || (bufferSunrise == -2 && bufferSunset != -2) + // Cannot have sunset before sunrise + || (bufferSunrise >= bufferSunset && bufferSunrise >= 0 && bufferSunset >= 0))) { + sunrise = bufferSunrise; + sunset = bufferSunset; + } + } return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])), SimpleWeatherService::Icons {dataBuffer[16 + 32]}, - std::move(cityName)); + std::move(cityName), + sunrise, + sunset); } SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) { @@ -94,7 +130,7 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { switch (GetMessageType(dataBuffer)) { case MessageType::CurrentWeather: - if (GetVersion(dataBuffer) == 0) { + if (GetVersion(dataBuffer) <= 1) { currentWeather = CreateCurrentWeather(dataBuffer); NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s", currentWeather->timestamp, @@ -103,6 +139,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { currentWeather->maxTemperature.PreciseCelsius(), currentWeather->iconId, currentWeather->location.data()); + if (GetVersion(dataBuffer) == 1) { + NRF_LOG_INFO("Sunrise: %d\n\tSunset: %d", currentWeather->sunrise, currentWeather->sunset); + } } break; case MessageType::Forecast: @@ -153,10 +192,36 @@ std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast( return {}; } +bool SimpleWeatherService::IsNight() const { + if (currentWeather && currentWeather->sunrise != -1 && currentWeather->sunset != -1) { + auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch(); + + // Get timestamp for last midnight + auto midnight = std::chrono::floor<std::chrono::days>(currentTime); + + // Calculate minutes since midnight + auto currentMinutes = std::chrono::duration_cast<std::chrono::minutes>(currentTime - midnight).count(); + + // Sun not rising today => night all hours + if (currentWeather->sunrise == -2) { + return true; + } + // Sun not setting today => check before sunrise + if (currentWeather->sunset == -2) { + return currentMinutes < currentWeather->sunrise; + } + + // Before sunrise or after sunset + return currentMinutes < currentWeather->sunrise || currentMinutes >= currentWeather->sunset; + } + + return false; +} + bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const { return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature && - std::strcmp(this->location.data(), other.location.data()) == 0; + std::strcmp(this->location.data(), other.location.data()) == 0 && this->sunrise == other.sunrise && this->sunset == other.sunset; } bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const { diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 4d09d662..96933b85 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -113,13 +113,17 @@ namespace Pinetime { Temperature minTemperature, Temperature maxTemperature, Icons iconId, - Location&& location) + Location&& location, + int16_t sunrise, + int16_t sunset) : timestamp {timestamp}, temperature {temperature}, minTemperature {minTemperature}, maxTemperature {maxTemperature}, iconId {iconId}, - location {std::move(location)} { + location {std::move(location)}, + sunrise {sunrise}, + sunset {sunset} { } uint64_t timestamp; @@ -128,6 +132,8 @@ namespace Pinetime { Temperature maxTemperature; Icons iconId; Location location; + int16_t sunrise; + int16_t sunset; bool operator==(const CurrentWeather& other) const; }; @@ -152,6 +158,8 @@ namespace Pinetime { std::optional<CurrentWeather> Current() const; std::optional<Forecast> GetForecast() const; + [[nodiscard]] bool IsNight() const; + private: // 00050000-78fc-48fe-8e23-433b3a1942d0 static constexpr ble_uuid128_t BaseUuid() { diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 90be1feb..3221c2f1 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -68,7 +68,7 @@ "sources": [ { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc" + "range": "0xf185, 0xf186, 0xf6c3, 0xf6c4, 0xf73c, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index fb93e80e..058b2d06 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -45,8 +45,11 @@ namespace Pinetime { // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; + static constexpr const char* moon = "\xEF\x86\x86"; // 0xf186 static constexpr const char* cloudSun = "\xEF\x9B\x84"; + static constexpr const char* cloudMoon = "\xEF\x9B\x83"; // 0xf6c3 static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; + static constexpr const char* cloudMoonRain = "\xEF\x9C\xBC"; // 0xf73c static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80"; static constexpr const char* smog = "\xEF\x9D\x9F"; static constexpr const char* cloud = "\xEF\x83\x82"; diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 3163c6e7..a037abfe 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -183,7 +183,7 @@ void WatchFaceDigital::Refresh() { tempUnit = 'F'; } lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); - lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); } else { lv_label_set_text_static(temperature, ""); lv_label_set_text(weatherIcon, ""); diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index ce8f8f7e..bfbbe24b 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -548,7 +548,7 @@ void WatchFacePineTimeStyle::Refresh() { temp = optCurrentWeather->temperature.Fahrenheit(); } lv_label_set_text_fmt(temperature, "%d°", temp); - lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); } else { lv_label_set_text(temperature, "--"); lv_label_set_text(weatherIcon, Symbols::ban); diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 0e44df03..de32a153 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -118,7 +118,7 @@ void Weather::Refresh() { tempUnit = 'F'; } lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, optCurrentWeather->temperature.Color()); - lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); lv_label_set_text_fmt(minTemperature, "%d°", minTemp); @@ -154,7 +154,7 @@ void Weather::Refresh() { } const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday)); lv_table_set_cell_value(forecast, 0, i, dayOfWeek); - lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId)); + lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId, false)); // Pad cells based on the largest number of digits on each column char maxPadding[3] = " "; char minPadding[3] = " "; diff --git a/src/displayapp/screens/WeatherSymbols.cpp b/src/displayapp/screens/WeatherSymbols.cpp index 0a963bc6..762180c7 100644 --- a/src/displayapp/screens/WeatherSymbols.cpp +++ b/src/displayapp/screens/WeatherSymbols.cpp @@ -1,11 +1,18 @@ #include "displayapp/screens/WeatherSymbols.h" -const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { +const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon, + const bool isNight) { switch (icon) { case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: + if (isNight) { + return Symbols::moon; + } return Symbols::sun; break; case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: + if (isNight) { + return Symbols::cloudMoon; + } return Symbols::cloudSun; break; case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: @@ -24,6 +31,9 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime:: return Symbols::cloudShowersHeavy; break; case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: + if (isNight) { + return Symbols::cloudMoonRain; + } return Symbols::cloudSunRain; break; case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h index 0fed0bdc..15c8a8a3 100644 --- a/src/displayapp/screens/WeatherSymbols.h +++ b/src/displayapp/screens/WeatherSymbols.h @@ -6,7 +6,7 @@ namespace Pinetime { namespace Applications { namespace Screens { namespace Symbols { - const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon); + const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon, const bool isNight); const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon); const char* GetSimpleCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon); } |
