Compare commits

...

6 commits

Author SHA1 Message Date
Jean-François Milants d1c146e2fb Improve and fix WeatherService
Ensure that PTS will update the weather info if temperature and cloud info were received. Assume 0 precipitation if no precipitation event were received.
2023-09-17 20:19:11 +02:00
Jean-François Milants ddfe204f14 Improve and fix WeatherService
Ensure that we keep only 1 version of each weather event in the timeline.
2023-09-17 20:18:23 +02:00
Jean-François Milants 5c70b38c58 Improve and fix WeatherService
Fix Temperature handling (uint16 -> int16) + use of optional<>.
2023-09-17 20:11:27 +02:00
Jean-François Milants 96c2c9e0bc Improve and fix WeatherService
Disable the weather events that are not processed by InfiniTime right now using "if constexpr". We'll enable them when we need them.
2023-09-17 18:35:09 +02:00
Jean-François Milants 846f54e997 Improve and fix WeatherService
Refactor WeatherService :
 - Add a sane max number of elements in the timeline
 - Create events in their own factory methods (CreateXXXEvent())
 - Encapsulate QCBOR deserialization in template functions Get<>().
2023-09-17 18:26:50 +02:00
Jean-François Milants df4c8d8c4c Improve and fix WeatherService
Enable C++ 17 so we can use new functionalities like "if constexpr".
2023-09-17 18:17:57 +02:00
4 changed files with 467 additions and 264 deletions

View file

@ -5,7 +5,7 @@ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 17)
# set(CMAKE_GENERATOR "Unix Makefiles") # set(CMAKE_GENERATOR "Unix Makefiles")
set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF)

View file

@ -20,6 +20,255 @@
#include "WeatherService.h" #include "WeatherService.h"
#include "libs/QCBOR/inc/qcbor/qcbor.h" #include "libs/QCBOR/inc/qcbor/qcbor.h"
namespace {
constexpr bool EnableObscuration = false;
constexpr bool EnablePrecipitation = true;
constexpr bool EnableWind = false;
constexpr bool EnableTemperature = true;
constexpr bool EnableAirQuality = false;
constexpr bool EnableSpecial = false;
constexpr bool EnablePressure = false;
constexpr bool EnableLocation = false;
constexpr bool EnableClouds = true;
constexpr bool EnableHumidity = false;
std::unique_ptr<Pinetime::Controllers::WeatherData::AirQuality>
CreateAirQualityEvent(int64_t timestamp, uint32_t expires, const std::string& polluter, uint32_t amount) {
auto airquality = std::make_unique<Pinetime::Controllers::WeatherData::AirQuality>();
airquality->timestamp = timestamp;
airquality->eventType = Pinetime::Controllers::WeatherData::eventtype::AirQuality;
airquality->expires = expires;
airquality->polluter = polluter;
airquality->amount = amount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
return airquality;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Obscuration>
CreateObscurationEvent(int64_t timestamp, uint32_t expires, uint16_t amount) {
auto obscuration = std::make_unique<Pinetime::Controllers::WeatherData::Obscuration>();
obscuration->timestamp = timestamp;
obscuration->eventType = Pinetime::Controllers::WeatherData::eventtype::Obscuration;
obscuration->expires = expires;
obscuration->amount = amount;
return obscuration;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Precipitation>
CreatePrecipitationEvent(int64_t timestamp,
uint32_t expires,
Pinetime::Controllers::WeatherData::precipitationtype type,
uint8_t amount) {
auto precipitation = std::make_unique<Pinetime::Controllers::WeatherData::Precipitation>();
precipitation->timestamp = timestamp;
precipitation->eventType = Pinetime::Controllers::WeatherData::eventtype::Precipitation;
precipitation->expires = expires;
precipitation->type = type;
precipitation->amount = amount;
return precipitation;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Wind>
CreateWindEvent(int64_t timestamp, uint32_t expires, uint8_t speedMin, uint8_t speedMax, uint8_t directionMin, uint8_t directionMax) {
auto wind = std::make_unique<Pinetime::Controllers::WeatherData::Wind>();
wind->timestamp = timestamp;
wind->eventType = Pinetime::Controllers::WeatherData::eventtype::Wind;
wind->expires = expires;
wind->speedMin = speedMin;
wind->speedMax = speedMax;
wind->directionMin = directionMin;
wind->directionMax = directionMax;
return wind;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Temperature>
CreateTemperatureEvent(int64_t timestamp, uint32_t expires, int16_t temperatureValue, int16_t dewPoint) {
auto temperature = std::make_unique<Pinetime::Controllers::WeatherData::Temperature>();
temperature->timestamp = timestamp;
temperature->eventType = Pinetime::Controllers::WeatherData::eventtype::Temperature;
temperature->expires = expires;
temperature->temperature = temperatureValue;
temperature->dewPoint = dewPoint;
return temperature;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Special>
CreateSpecialEvent(int64_t timestamp, uint32_t expires, Pinetime::Controllers::WeatherData::specialtype type) {
auto special = std::make_unique<Pinetime::Controllers::WeatherData::Special>();
special->timestamp = timestamp;
special->eventType = Pinetime::Controllers::WeatherData::eventtype::Special;
special->expires = expires;
special->type = type;
return special;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Pressure>
CreatePressureEvent(int64_t timestamp, uint32_t expires, uint16_t pressureValue) {
auto pressure = std::make_unique<Pinetime::Controllers::WeatherData::Pressure>();
pressure->timestamp = timestamp;
pressure->eventType = Pinetime::Controllers::WeatherData::eventtype::Pressure;
pressure->expires = expires;
pressure->pressure = pressureValue;
return pressure;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Location> CreateLocationEvent(int64_t timestamp,
uint32_t expires,
const std::string& locationValue,
int16_t altitude,
int32_t latitude,
int32_t longitude) {
auto location = std::make_unique<Pinetime::Controllers::WeatherData::Location>();
location->timestamp = timestamp;
location->eventType = Pinetime::Controllers::WeatherData::eventtype::Location;
location->expires = expires;
location->location = locationValue;
location->altitude = altitude;
location->latitude = latitude;
location->longitude = longitude;
return location;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Clouds> CreateCloudsEvent(int64_t timestamp, uint32_t expires, uint8_t amount) {
auto clouds = std::make_unique<Pinetime::Controllers::WeatherData::Clouds>();
clouds->timestamp = timestamp;
clouds->eventType = Pinetime::Controllers::WeatherData::eventtype::Clouds;
clouds->expires = expires;
clouds->amount = amount;
return clouds;
}
std::unique_ptr<Pinetime::Controllers::WeatherData::Humidity>
CreateHumidityEvent(int64_t timestamp, uint32_t expires, uint8_t humidityValue) {
auto humidity = std::make_unique<Pinetime::Controllers::WeatherData::Humidity>();
humidity->timestamp = timestamp;
humidity->eventType = Pinetime::Controllers::WeatherData::eventtype::Humidity;
humidity->expires = expires;
humidity->humidity = humidityValue;
return humidity;
}
template <typename T>
std::optional<T> Get(QCBORDecodeContext* /*context*/, const char* /*field*/) = delete;
template <>
std::optional<int64_t> Get<int64_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS) {
return {};
}
return {tmp};
}
template <>
std::optional<uint32_t> Get<uint32_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 || tmp > 4294967295) {
return {};
}
return {static_cast<uint32_t>(tmp)};
}
template <>
std::optional<int32_t> Get<int32_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < -2147483648 || tmp >= 2147483647) {
return {};
}
return {static_cast<int32_t>(tmp)};
}
template <>
std::optional<uint16_t> Get<uint16_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 || tmp > 65535) {
return {};
}
return {static_cast<uint16_t>(tmp)};
}
template <>
std::optional<int16_t> Get<int16_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < -32768 || tmp >= 32767) {
return {};
}
return {static_cast<int16_t>(tmp)};
}
template <>
std::optional<uint8_t> Get<uint8_t>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 || tmp > 255) {
return {};
}
return {static_cast<uint8_t>(tmp)};
}
template <>
std::optional<Pinetime::Controllers::WeatherData::eventtype>
Get<Pinetime::Controllers::WeatherData::eventtype>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 ||
tmp >= static_cast<int64_t>(Pinetime::Controllers::WeatherData::eventtype::Length)) {
return {};
}
return {static_cast<Pinetime::Controllers::WeatherData::eventtype>(tmp)};
}
template <>
std::optional<std::string> Get<std::string>(QCBORDecodeContext* context, const char* field) {
UsefulBufC stringBuf;
QCBORDecode_GetTextStringInMapSZ(context, field, &stringBuf);
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
return {};
}
return {std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len)};
}
template <>
std::optional<Pinetime::Controllers::WeatherData::obscurationtype>
Get<Pinetime::Controllers::WeatherData::obscurationtype>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 ||
tmp >= static_cast<int64_t>(Pinetime::Controllers::WeatherData::obscurationtype::Length)) {
return {};
}
return {static_cast<Pinetime::Controllers::WeatherData::obscurationtype>(tmp)};
}
template <>
std::optional<Pinetime::Controllers::WeatherData::precipitationtype>
Get<Pinetime::Controllers::WeatherData::precipitationtype>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 ||
tmp >= static_cast<int64_t>(Pinetime::Controllers::WeatherData::precipitationtype::Length)) {
return {};
}
return {static_cast<Pinetime::Controllers::WeatherData::precipitationtype>(tmp)};
}
template <>
std::optional<Pinetime::Controllers::WeatherData::specialtype>
Get<Pinetime::Controllers::WeatherData::specialtype>(QCBORDecodeContext* context, const char* field) {
int64_t tmp = 0;
QCBORDecode_GetInt64InMapSZ(context, field, &tmp);
if (QCBORDecode_GetError(context) != QCBOR_SUCCESS || tmp < 0 ||
tmp >= static_cast<int64_t>(Pinetime::Controllers::WeatherData::specialtype::Length)) {
return {};
}
return {static_cast<Pinetime::Controllers::WeatherData::specialtype>(tmp)};
}
}
int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) { int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(ctxt); return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(ctxt);
} }
@ -54,309 +303,236 @@ namespace Pinetime {
// KINDLY provide us a fixed-length map // KINDLY provide us a fixed-length map
QCBORDecode_EnterMap(&decodeContext, nullptr); QCBORDecode_EnterMap(&decodeContext, nullptr);
// Always encodes to the smallest number of bytes based on the value // Always encodes to the smallest number of bytes based on the value
int64_t tmpTimestamp = 0; auto optTimestamp = Get<int64_t>(&decodeContext, "Timestamp");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp); if (!optTimestamp) {
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
int64_t tmpExpires = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
int64_t tmpEventType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
tmpEventType >= static_cast<int64_t>(WeatherData::eventtype::Length)) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
switch (static_cast<WeatherData::eventtype>(tmpEventType)) { auto optExpires = Get<uint32_t>(&decodeContext, "Expires");
case WeatherData::eventtype::AirQuality: { if (!optExpires) {
std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
airquality->timestamp = tmpTimestamp;
airquality->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
airquality->expires = tmpExpires;
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
int64_t tmpAmount = 0; auto optEventType = Get<Pinetime::Controllers::WeatherData::eventtype>(&decodeContext, "EventType");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); if (!optEventType) {
if (tmpAmount < 0 || tmpAmount > 4294967295) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
if (!AddEventToTimeline(std::move(airquality))) { switch (*optEventType) {
case WeatherData::eventtype::AirQuality:
if constexpr (EnableAirQuality) {
auto optPolluter = Get<std::string>(&decodeContext, "Polluter");
if (!optPolluter) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
auto optAmount = Get<uint32_t>(&decodeContext, "Amount");
if (!optAmount) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
auto airQuality = CreateAirQualityEvent(*optTimestamp, *optExpires, *optPolluter, *optAmount);
if (!AddEventToTimeline(std::move(airQuality))) {
CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
}
break; break;
} case WeatherData::eventtype::Obscuration:
case WeatherData::eventtype::Obscuration: { if constexpr (EnableObscuration) {
std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>(); auto optType = Get<WeatherData::obscurationtype>(&decodeContext, "Type");
obscuration->timestamp = tmpTimestamp; if (!optType) {
obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
obscuration->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
int64_t tmpAmount = 0; auto optAmount = Get<uint16_t>(&decodeContext, "Amount");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); if (!optAmount) {
if (tmpAmount < 0 || tmpAmount > 65535) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
auto obscuration = CreateObscurationEvent(*optTimestamp, *optExpires, *optAmount);
if (!AddEventToTimeline(std::move(obscuration))) { if (!AddEventToTimeline(std::move(obscuration))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
}
break; break;
} case WeatherData::eventtype::Precipitation:
case WeatherData::eventtype::Precipitation: { if constexpr (EnablePrecipitation) {
std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>(); auto optType = Get<WeatherData::precipitationtype>(&decodeContext, "Type");
precipitation->timestamp = tmpTimestamp; if (optType) {
precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
precipitation->expires = tmpExpires;
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
int64_t tmpAmount = 0; auto optAmount = Get<uint8_t>(&decodeContext, "Amount");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount); if (!optAmount) {
if (tmpAmount < 0 || tmpAmount > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
auto precipitation = CreatePrecipitationEvent(*optTimestamp, *optExpires, *optType, *optAmount);
if (!AddEventToTimeline(std::move(precipitation))) { if (!AddEventToTimeline(std::move(precipitation))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
}
break; break;
} case WeatherData::eventtype::Wind:
case WeatherData::eventtype::Wind: { if constexpr (EnableWind) {
std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>(); auto optMin = Get<uint8_t>(&decodeContext, "SpeedMin");
wind->timestamp = tmpTimestamp; if (!optMin) {
wind->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
wind->expires = tmpExpires;
int64_t tmpMin = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
if (tmpMin < 0 || tmpMin > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpMax = 0; auto optMax = Get<uint8_t>(&decodeContext, "SpeedMax");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax); if (!optMax) {
if (tmpMax < 0 || tmpMax > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDMin = 0; auto optDMin = Get<uint8_t>(&decodeContext, "DirectionMin");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin); if (!optDMin) {
if (tmpDMin < 0 || tmpDMin > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDMax = 0; auto optDMax = Get<uint8_t>(&decodeContext, "DirectionMax");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax); if (!optDMax) {
if (tmpDMax < 0 || tmpDMax > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
auto wind = CreateWindEvent(*optTimestamp, *optExpires, *optMin, *optMax, *optDMin, *optDMax);
if (!AddEventToTimeline(std::move(wind))) { if (!AddEventToTimeline(std::move(wind))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
}
break; break;
} case WeatherData::eventtype::Temperature:
case WeatherData::eventtype::Temperature: { if constexpr (EnableTemperature) {
std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>(); auto optTemperature = Get<int16_t>(&decodeContext, "Temperature");
temperature->timestamp = tmpTimestamp; if (!optTemperature) {
temperature->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
temperature->expires = tmpExpires;
int64_t tmpTemperature = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
if (tmpTemperature < -32768 || tmpTemperature > 32767) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
temperature->temperature =
static_cast<int16_t>(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
int64_t tmpDewPoint = 0; auto optDewPoint = Get<int16_t>(&decodeContext, "DewPoint");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint); if (!optDewPoint) {
if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
temperature->dewPoint =
static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
auto temperature =
CreateTemperatureEvent(*optTimestamp, *optExpires, *optTemperature, *optDewPoint);
if (!AddEventToTimeline(std::move(temperature))) { if (!AddEventToTimeline(std::move(temperature))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
break;
} }
case WeatherData::eventtype::Special: { break;
std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>(); case WeatherData::eventtype::Special:
special->timestamp = tmpTimestamp; if constexpr (EnableSpecial) {
special->eventType = static_cast<WeatherData::eventtype>(tmpEventType); auto optType = Get<WeatherData::specialtype>(&decodeContext, "Type");
special->expires = tmpExpires; if (!optType) {
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
special->type = static_cast<WeatherData::specialtype>(tmpType);
auto special = CreateSpecialEvent(*optTimestamp, *optExpires, *optType);
if (!AddEventToTimeline(std::move(special))) { if (!AddEventToTimeline(std::move(special))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
break;
} }
case WeatherData::eventtype::Pressure: { break;
std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>(); case WeatherData::eventtype::Pressure:
pressure->timestamp = tmpTimestamp; if constexpr (EnablePressure) {
pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType); auto optPressure = Get<uint16_t>(&decodeContext, "Pressure");
pressure->expires = tmpExpires; if (!optPressure) {
int64_t tmpPressure = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
if (tmpPressure < 0 || tmpPressure >= 65535) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
auto pressure = CreatePressureEvent(*optTimestamp, *optExpires, *optPressure);
if (!AddEventToTimeline(std::move(pressure))) { if (!AddEventToTimeline(std::move(pressure))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
}
break; break;
} case WeatherData::eventtype::Location:
case WeatherData::eventtype::Location: { if constexpr (EnableLocation) {
std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>(); auto optLocation = Get<std::string>(&decodeContext, "Location");
location->timestamp = tmpTimestamp; if (!optLocation) {
location->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
location->expires = tmpExpires;
UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
int64_t tmpAltitude = 0; auto optAltitude = Get<int16_t>(&decodeContext, "Altitude");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude); if (!optAltitude) {
if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
location->altitude = static_cast<int16_t>(tmpAltitude);
int64_t tmpLatitude = 0; auto optLatitude = Get<int32_t>(&decodeContext, "Latitude");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude); if (!optLatitude) {
if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
location->latitude = static_cast<int32_t>(tmpLatitude);
int64_t tmpLongitude = 0; auto optLongitude = Get<int32_t>(&decodeContext, "Longitude");
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude); if (!optLongitude) {
if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
location->latitude = static_cast<int32_t>(tmpLongitude);
auto location = CreateLocationEvent(*optTimestamp, *optExpires, *optLocation, *optAltitude, *optLatitude, *optLongitude);
if (!AddEventToTimeline(std::move(location))) { if (!AddEventToTimeline(std::move(location))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
break;
} }
case WeatherData::eventtype::Clouds: { break;
std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>(); case WeatherData::eventtype::Clouds:
clouds->timestamp = tmpTimestamp; if constexpr (EnableClouds) {
clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType); auto optAmount = Get<uint8_t>(&decodeContext, "Amount");
clouds->expires = tmpExpires; if (!optAmount) {
int64_t tmpAmount = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
if (tmpAmount < 0 || tmpAmount > 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
clouds->amount = static_cast<uint8_t>(tmpAmount);
auto clouds = CreateCloudsEvent(*optTimestamp, *optExpires, *optAmount);
if (!AddEventToTimeline(std::move(clouds))) { if (!AddEventToTimeline(std::move(clouds))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
break;
} }
case WeatherData::eventtype::Humidity: { break;
std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>(); case WeatherData::eventtype::Humidity:
humidity->timestamp = tmpTimestamp; if constexpr (EnableHumidity) {
humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType); auto optType = Get<uint8_t>(&decodeContext, "Humidity");
humidity->expires = tmpExpires; if (!optType) {
int64_t tmpType = 0;
QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
if (tmpType < 0 || tmpType >= 255) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
humidity->humidity = static_cast<uint8_t>(tmpType);
auto humidity = CreateHumidityEvent(*optTimestamp, *optExpires, *optType);
if (!AddEventToTimeline(std::move(humidity))) { if (!AddEventToTimeline(std::move(humidity))) {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} }
break;
} }
break;
default: { default: {
CleanUpQcbor(&decodeContext); CleanUpQcbor(&decodeContext);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
@ -505,11 +681,25 @@ namespace Pinetime {
} }
bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) { bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) {
if (timeline.size() == timeline.max_size()) { if (timeline.size() == maxNbElements) {
return false; return false;
} }
auto existingEvent = std::find_if(timeline.begin(), timeline.end(), [&event](const std::unique_ptr<WeatherData::TimelineHeader>& item) {
return event->eventType == item->eventType;
});
// Add the new event in the timeline (and remove the older one) if
// the timeline contains an event of the same type with a longer expiry.
// If an event with a shorter expiry already exists, keep it and drop the new one
if(existingEvent == timeline.end()) {
timeline.push_back(std::move(event)); timeline.push_back(std::move(event));
} else if(event->expires < (*existingEvent)->expires) {
timeline.erase(existingEvent);
timeline.push_back(std::move(event));
} else {
// keep the existing event and to not add this one
}
return true; return true;
} }

View file

@ -99,6 +99,8 @@ namespace Pinetime {
.value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}}; .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
} }
static constexpr size_t maxNbElements = 10;
ble_uuid128_t weatherUuid {BaseUuid()}; ble_uuid128_t weatherUuid {BaseUuid()};
/** /**

View file

@ -537,11 +537,22 @@ void WatchFacePineTimeStyle::Refresh() {
} }
} }
if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 && const auto& newTemperatureEvent = weatherService.GetCurrentTemperature();
weatherService.GetCurrentPrecipitation()->timestamp != 0) { const auto& newCloudsEvent = weatherService.GetCurrentClouds();
const auto& newPrecipitationEvent = weatherService.GetCurrentPrecipitation();
if(newTemperatureEvent->timestamp == 0 && newCloudsEvent->timestamp == 0 && newPrecipitationEvent == 0) {
lv_label_set_text_static(temperature, "--");
lv_label_set_text(weatherIcon, Symbols::ban);
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
// Assume 0 precipitation if no precipitation event were received
if (newTemperatureEvent->timestamp != 0 && newCloudsEvent->timestamp != 0 /*&& newPrecipitationEvent->timestamp !=0*/) {
nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100); nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
clouds = (weatherService.GetCurrentClouds()->amount); clouds = (weatherService.GetCurrentClouds()->amount);
precip = (weatherService.GetCurrentPrecipitation()->amount); precip = (newPrecipitationEvent->timestamp != 0) ? weatherService.GetCurrentPrecipitation()->amount : 0;
if (nowTemp.IsUpdated()) { if (nowTemp.IsUpdated()) {
lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get()); lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get());
if ((clouds <= 30) && (precip == 0)) { if ((clouds <= 30) && (precip == 0)) {