aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Kareh <vkareh@redhat.com>2026-01-05 07:12:08 -0500
committerGitHub <noreply@github.com>2026-01-05 13:12:08 +0100
commitf88c69a31aadb0fdfe6555db626ba8f05e756272 (patch)
treef173289f96c89a38afc5198a6b61e192cf372118
parent51a6fb6a7ed7a410bb141966a43a7649d10bbdfc (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.md19
-rw-r--r--src/components/ble/SimpleWeatherService.cpp71
-rw-r--r--src/components/ble/SimpleWeatherService.h12
-rw-r--r--src/displayapp/fonts/fonts.json2
-rw-r--r--src/displayapp/screens/Symbols.h3
-rw-r--r--src/displayapp/screens/WatchFaceDigital.cpp2
-rw-r--r--src/displayapp/screens/WatchFacePineTimeStyle.cpp2
-rw-r--r--src/displayapp/screens/Weather.cpp4
-rw-r--r--src/displayapp/screens/WeatherSymbols.cpp12
-rw-r--r--src/displayapp/screens/WeatherSymbols.h2
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);
}