mirror of
https://github.com/InfiniTimeOrg/InfiniTime.git
synced 2024-10-22 15:11:51 +02:00
SimpleWeatherService: Add sunrise and sunset data
This commit is contained in:
parent
a2356f2f4a
commit
d6ab2e9245
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
||||||
|
|
||||||
project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM)
|
project(pinetime VERSION 1.15.0 LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
|
@ -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:
|
This characteristics accepts a byte array with the following 2-Bytes header:
|
||||||
|
|
||||||
- [0] Message Type :
|
- [0] Message Type :
|
||||||
- `0` : Current weather
|
- `0` : Current weather
|
||||||
- `1` : Forecast
|
- `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:
|
The byte array must contain the following data:
|
||||||
|
|
||||||
- [0] : Message type = `0`
|
- [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)
|
- [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)
|
- [10, 11] : Current temperature (°C * 100)
|
||||||
- [12, 13] : Minimum temperature (°C * 100)
|
- [12, 13] : Minimum temperature (°C * 100)
|
||||||
- [14, 15] : Maximum temperature (°C * 100)
|
- [14, 15] : Maximum temperature (°C * 100)
|
||||||
- [16]..[47] : location (string, unused characters should be set to `0`)
|
- [16]..[47] : location (string, unused characters should be set to `0`)
|
||||||
- [48] : icon ID
|
- [48] : icon ID
|
||||||
- 0 = Sun, clear sky
|
- 0 = Sun, clear sky
|
||||||
- 1 = Few clouds
|
- 1 = Few clouds
|
||||||
- 2 = Clouds
|
- 2 = Clouds
|
||||||
|
@ -43,6 +45,8 @@ The byte array must contain the following data:
|
||||||
- 6 = Thunderstorm
|
- 6 = Thunderstorm
|
||||||
- 7 = Snow
|
- 7 = Snow
|
||||||
- 8 = Mist, smog
|
- 8 = Mist, smog
|
||||||
|
- [49, 50] : Sunrise (16 bits, number of minutes elapsed since midnight)
|
||||||
|
- [51, 52] : Sunset (16 bits, number of minutes elapsed since midnight)
|
||||||
|
|
||||||
### Forecast
|
### Forecast
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,10 @@ namespace {
|
||||||
(static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
|
(static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t ToUInt16(const uint8_t* data) {
|
||||||
|
return data[0] + (data[1] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
int16_t ToInt16(const uint8_t* data) {
|
int16_t ToInt16(const uint8_t* data) {
|
||||||
return data[0] + (data[1] << 8);
|
return data[0] + (data[1] << 8);
|
||||||
}
|
}
|
||||||
|
@ -41,12 +45,20 @@ namespace {
|
||||||
SimpleWeatherService::Location cityName;
|
SimpleWeatherService::Location cityName;
|
||||||
std::memcpy(cityName.data(), &dataBuffer[16], 32);
|
std::memcpy(cityName.data(), &dataBuffer[16], 32);
|
||||||
cityName[32] = '\0';
|
cityName[32] = '\0';
|
||||||
|
uint16_t sunrise = 0;
|
||||||
|
uint16_t sunset = 0;
|
||||||
|
if (dataBuffer[1] > 0) {
|
||||||
|
sunrise = ToUInt16(&dataBuffer[49]);
|
||||||
|
sunset = ToUInt16(&dataBuffer[51]);
|
||||||
|
}
|
||||||
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
|
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
|
||||||
ToInt16(&dataBuffer[10]),
|
ToInt16(&dataBuffer[10]),
|
||||||
ToInt16(&dataBuffer[12]),
|
ToInt16(&dataBuffer[12]),
|
||||||
ToInt16(&dataBuffer[14]),
|
ToInt16(&dataBuffer[14]),
|
||||||
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
|
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
|
||||||
std::move(cityName));
|
std::move(cityName),
|
||||||
|
sunrise,
|
||||||
|
sunset);
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
|
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
|
||||||
|
@ -94,7 +106,7 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
||||||
|
|
||||||
switch (GetMessageType(dataBuffer)) {
|
switch (GetMessageType(dataBuffer)) {
|
||||||
case MessageType::CurrentWeather:
|
case MessageType::CurrentWeather:
|
||||||
if (GetVersion(dataBuffer) == 0) {
|
if (GetVersion(dataBuffer) <= 1) {
|
||||||
currentWeather = CreateCurrentWeather(dataBuffer);
|
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",
|
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,
|
currentWeather->timestamp,
|
||||||
|
@ -103,6 +115,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
||||||
currentWeather->maxTemperature,
|
currentWeather->maxTemperature,
|
||||||
currentWeather->iconId,
|
currentWeather->iconId,
|
||||||
currentWeather->location.data());
|
currentWeather->location.data());
|
||||||
|
if (GetVersion(dataBuffer) == 1) {
|
||||||
|
NRF_LOG_INFO("Sunrise: %d\n\tSunset: %d", currentWeather->sunrise, currentWeather->sunset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MessageType::Forecast:
|
case MessageType::Forecast:
|
||||||
|
@ -153,10 +168,26 @@ std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SimpleWeatherService::IsNight() const {
|
||||||
|
if (currentWeather && currentWeather->sunrise > 0 && currentWeather->sunset > 0) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
return currentMinutes < currentWeather->sunrise || currentMinutes > currentWeather->sunset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
|
bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
|
||||||
return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp &&
|
return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp &&
|
||||||
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
|
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 {
|
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
|
||||||
|
|
|
@ -69,13 +69,17 @@ namespace Pinetime {
|
||||||
int16_t minTemperature,
|
int16_t minTemperature,
|
||||||
int16_t maxTemperature,
|
int16_t maxTemperature,
|
||||||
Icons iconId,
|
Icons iconId,
|
||||||
Location&& location)
|
Location&& location,
|
||||||
|
uint16_t sunrise,
|
||||||
|
uint16_t sunset)
|
||||||
: timestamp {timestamp},
|
: timestamp {timestamp},
|
||||||
temperature {temperature},
|
temperature {temperature},
|
||||||
minTemperature {minTemperature},
|
minTemperature {minTemperature},
|
||||||
maxTemperature {maxTemperature},
|
maxTemperature {maxTemperature},
|
||||||
iconId {iconId},
|
iconId {iconId},
|
||||||
location {std::move(location)} {
|
location {std::move(location)},
|
||||||
|
sunrise {sunrise},
|
||||||
|
sunset {sunset} {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t timestamp;
|
uint64_t timestamp;
|
||||||
|
@ -84,6 +88,8 @@ namespace Pinetime {
|
||||||
int16_t maxTemperature;
|
int16_t maxTemperature;
|
||||||
Icons iconId;
|
Icons iconId;
|
||||||
Location location;
|
Location location;
|
||||||
|
uint16_t sunrise;
|
||||||
|
uint16_t sunset;
|
||||||
|
|
||||||
bool operator==(const CurrentWeather& other) const;
|
bool operator==(const CurrentWeather& other) const;
|
||||||
};
|
};
|
||||||
|
@ -108,6 +114,8 @@ namespace Pinetime {
|
||||||
std::optional<CurrentWeather> Current() const;
|
std::optional<CurrentWeather> Current() const;
|
||||||
std::optional<Forecast> GetForecast() const;
|
std::optional<Forecast> GetForecast() const;
|
||||||
|
|
||||||
|
bool IsNight() const;
|
||||||
|
|
||||||
static int16_t CelsiusToFahrenheit(int16_t celsius) {
|
static int16_t CelsiusToFahrenheit(int16_t celsius) {
|
||||||
return celsius * 9 / 5 + 3200;
|
return celsius * 9 / 5 + 3200;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
"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,
|
"bpp": 1,
|
||||||
|
|
|
@ -42,8 +42,11 @@ namespace Pinetime {
|
||||||
|
|
||||||
// fontawesome_weathericons.c
|
// fontawesome_weathericons.c
|
||||||
// static constexpr const char* sun = "\xEF\x86\x85";
|
// 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* 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* 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* cloudShowersHeavy = "\xEF\x9D\x80";
|
||||||
static constexpr const char* smog = "\xEF\x9D\x9F";
|
static constexpr const char* smog = "\xEF\x9D\x9F";
|
||||||
static constexpr const char* cloud = "\xEF\x83\x82";
|
static constexpr const char* cloud = "\xEF\x83\x82";
|
||||||
|
|
|
@ -182,7 +182,7 @@ void WatchFaceDigital::Refresh() {
|
||||||
}
|
}
|
||||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||||
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
|
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 {
|
} else {
|
||||||
lv_label_set_text_static(temperature, "");
|
lv_label_set_text_static(temperature, "");
|
||||||
lv_label_set_text(weatherIcon, "");
|
lv_label_set_text(weatherIcon, "");
|
||||||
|
|
|
@ -549,7 +549,7 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||||
}
|
}
|
||||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||||
lv_label_set_text_fmt(temperature, "%d°", temp);
|
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 {
|
} else {
|
||||||
lv_label_set_text(temperature, "--");
|
lv_label_set_text(temperature, "--");
|
||||||
lv_label_set_text(weatherIcon, Symbols::ban);
|
lv_label_set_text(weatherIcon, Symbols::ban);
|
||||||
|
|
|
@ -131,7 +131,7 @@ void Weather::Refresh() {
|
||||||
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
|
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
|
||||||
tempUnit = 'F';
|
tempUnit = 'F';
|
||||||
}
|
}
|
||||||
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(condition, Symbols::GetCondition(optCurrentWeather->iconId));
|
||||||
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
|
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
|
||||||
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
|
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
|
||||||
|
@ -169,7 +169,7 @@ void Weather::Refresh() {
|
||||||
minTemp = RoundTemperature(minTemp);
|
minTemp = RoundTemperature(minTemp);
|
||||||
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
|
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, 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
|
// Pad cells based on the largest number of digits on each column
|
||||||
char maxPadding[3] = " ";
|
char maxPadding[3] = " ";
|
||||||
char minPadding[3] = " ";
|
char minPadding[3] = " ";
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
#include "displayapp/screens/WeatherSymbols.h"
|
#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) {
|
switch (icon) {
|
||||||
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
|
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
|
||||||
|
if (isNight) {
|
||||||
|
return Symbols::moon;
|
||||||
|
}
|
||||||
return Symbols::sun;
|
return Symbols::sun;
|
||||||
break;
|
break;
|
||||||
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
|
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
|
||||||
|
if (isNight) {
|
||||||
|
return Symbols::cloudMoon;
|
||||||
|
}
|
||||||
return Symbols::cloudSun;
|
return Symbols::cloudSun;
|
||||||
break;
|
break;
|
||||||
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
|
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
|
||||||
|
@ -24,6 +31,9 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::
|
||||||
return Symbols::cloudShowersHeavy;
|
return Symbols::cloudShowersHeavy;
|
||||||
break;
|
break;
|
||||||
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
|
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
|
||||||
|
if (isNight) {
|
||||||
|
return Symbols::cloudMoonRain;
|
||||||
|
}
|
||||||
return Symbols::cloudSunRain;
|
return Symbols::cloudSunRain;
|
||||||
break;
|
break;
|
||||||
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
|
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
namespace Screens {
|
namespace Screens {
|
||||||
namespace Symbols {
|
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* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue