diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h index c1d53f4e..7cf68418 100644 --- a/src/components/ble/weather/WeatherData.h +++ b/src/components/ble/weather/WeatherData.h @@ -39,7 +39,7 @@ namespace Pinetime { None = 0, /** Water particles suspended in the air; low visibility; does not fall */ Fog = 1, - /** Extremely small, dry particles in the air; invisible to the eye; opalescent */ + /** Tiny, dry particles in the air; invisible to the eye; opalescent */ Haze = 2, /** Small fire-created particles suspended in the air */ Smoke = 3, @@ -51,6 +51,7 @@ namespace Pinetime { Sand = 6, /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */ Mist = 7, + Length }; /** @@ -82,7 +83,8 @@ namespace Pinetime { /** Frozen drizzle; very small snow crystals */ SnowGrains = 8, /** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */ - IceCrystals = 9 + IceCrystals = 9, + Length }; /** @@ -99,6 +101,7 @@ namespace Pinetime { Fire = 3, /** Thunder and/or lightning */ Thunder = 4, + Length }; /** @@ -111,7 +114,8 @@ namespace Pinetime { /** This wipes the entire timeline */ DelTimeline = 1, /** There's a currently valid timeline event with the given type */ - HasValidEvent = 3 + HasValidEvent = 3, + Length }; /** @@ -137,19 +141,20 @@ namespace Pinetime { Location = 7, /** @see cloud */ Clouds = 8, + Length }; /** * Valid event query */ - class valideventquery { + class ValidEventQuery { public: static constexpr controlcodes code = controlcodes::HasValidEvent; eventtype eventType; }; /** The header used for further parsing */ - class timelineheader { + class TimelineHeader { public: /** UNIX timestamp */ uint64_t timestamp; @@ -168,23 +173,23 @@ namespace Pinetime { }; /** Specifies how cloudiness is stored */ - class clouds : public timelineheader { + class Clouds : public TimelineHeader { public: /** Cloud coverage in percentage, 0-100% */ uint8_t amount; }; /** Specifies how obscuration is stored */ - class obscuration : public timelineheader { + class Obscuration : public TimelineHeader { public: /** Type */ obscurationtype type; /** Visibility distance in meters */ - uint8_t amount; + uint16_t amount; }; /** Specifies how precipitation is stored */ - class precipitation : public timelineheader { + class Precipitation : public TimelineHeader { public: /** Type */ precipitationtype type; @@ -201,7 +206,7 @@ namespace Pinetime { * As direction can fluctuate wildly and some watchfaces might wish to display it nicely, * we're following the aerospace industry weather report option of specifying a range. */ - class wind : public timelineheader { + class Wind : public TimelineHeader { public: /** Meters per second */ uint8_t speedMin; @@ -221,7 +226,7 @@ namespace Pinetime { * * We don't do floats, microdegrees are not useful. Make sure to multiply. */ - class temperature : public timelineheader { + class Temperature : public TimelineHeader { public: /** Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250) */ int16_t temperature; @@ -240,7 +245,7 @@ namespace Pinetime { * or daylight calculations, should those be required. * */ - class location : public timelineheader { + class Location : public TimelineHeader { public: /** Location name */ std::string location; @@ -255,7 +260,7 @@ namespace Pinetime { /** * How humidity is stored */ - class humidity : public timelineheader { + class Humidity : public TimelineHeader { public: /** Relative humidity, 0-100% */ uint8_t humidity; @@ -264,7 +269,7 @@ namespace Pinetime { /** * How air pressure is stored */ - class pressure : public timelineheader { + class Pressure : public TimelineHeader { public: /** Air pressure in hectopascals (hPa) */ int16_t pressure; @@ -273,7 +278,7 @@ namespace Pinetime { /** * How special events are stored */ - class special : public timelineheader { + class Special : public TimelineHeader { public: /** Special event's type */ specialtype type; @@ -288,7 +293,7 @@ namespace Pinetime { * * If this needs further enforced standardization, pull requests are welcome */ - class airquality : public timelineheader { + class AirQuality : public TimelineHeader { public: /** * The name of the pollution diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp index 006fc6c1..60e608e7 100644 --- a/src/components/ble/weather/WeatherService.cpp +++ b/src/components/ble/weather/WeatherService.cpp @@ -40,58 +40,125 @@ namespace Pinetime { } int WeatherService::OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { + // TODO: Detect control messages if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { - getCurrentPressure(); - tidyTimeline(); - getTimelineLength(); const auto packetLen = OS_MBUF_PKTLEN(ctxt->om); if (packetLen <= 0) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } // Decode QCBORDecodeContext decodeContext; - UsefulBufC EncodedCBOR; - // TODO: Check uninit fine - QCBORDecode_Init(&decodeContext, EncodedCBOR, QCBOR_DECODE_MODE_NORMAL); + UsefulBufC encodedCbor; + // TODO: Check, uninit fine? + + QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL); QCBORDecode_EnterMap(&decodeContext, nullptr); - WeatherData::timelineheader timelineHeader {}; // Always encodes to the smallest number of bytes based on the value - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", reinterpret_cast(&(timelineHeader.timestamp))); - QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", reinterpret_cast(&(timelineHeader.expires))); - QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", reinterpret_cast(&(timelineHeader.eventType))); - switch (timelineHeader.eventType) { - // TODO: Populate + int64_t tmpVersion = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "Version", &tmpVersion); + if (tmpVersion != 1) { + // TODO: Return better error? + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + int64_t tmpTimestamp = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp); + int64_t tmpExpires = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires); + if (tmpExpires > 4294967295) { + // TODO: Return better error? + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + int64_t tmpEventType = 0; + QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType); + if (tmpEventType > static_cast(WeatherData::eventtype::Length)) { + // TODO: Return better error? + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + switch (static_cast(tmpEventType)) { + // TODO: Populate case WeatherData::eventtype::AirQuality: { + std::unique_ptr airquality = std::make_unique(); + airquality->timestamp = tmpTimestamp; + airquality->eventType = static_cast(tmpEventType); + airquality->expires = tmpExpires; + + timeline.push_back(std::move(airquality)); break; } case WeatherData::eventtype::Obscuration: { + std::unique_ptr obscuration = std::make_unique(); + obscuration->timestamp = tmpTimestamp; + obscuration->eventType = static_cast(tmpEventType); + obscuration->expires = tmpExpires; + + timeline.push_back(std::move(obscuration)); break; } case WeatherData::eventtype::Precipitation: { + std::unique_ptr precipitation = std::make_unique(); + precipitation->timestamp = tmpTimestamp; + precipitation->eventType = static_cast(tmpEventType); + precipitation->expires = tmpExpires; + timeline.push_back(std::move(precipitation)); break; } case WeatherData::eventtype::Wind: { + std::unique_ptr wind = std::make_unique(); + wind->timestamp = tmpTimestamp; + wind->eventType = static_cast(tmpEventType); + wind->expires = tmpExpires; + timeline.push_back(std::move(wind)); break; } case WeatherData::eventtype::Temperature: { + std::unique_ptr temperature = std::make_unique(); + temperature->timestamp = tmpTimestamp; + temperature->eventType = static_cast(tmpEventType); + temperature->expires = tmpExpires; + timeline.push_back(std::move(temperature)); break; } case WeatherData::eventtype::Special: { + std::unique_ptr special = std::make_unique(); + special->timestamp = tmpTimestamp; + special->eventType = static_cast(tmpEventType); + special->expires = tmpExpires; + timeline.push_back(std::move(special)); break; } case WeatherData::eventtype::Pressure: { + std::unique_ptr pressure = std::make_unique(); + pressure->timestamp = tmpTimestamp; + pressure->eventType = static_cast(tmpEventType); + pressure->expires = tmpExpires; + timeline.push_back(std::move(pressure)); break; } case WeatherData::eventtype::Location: { + std::unique_ptr location = std::make_unique(); + location->timestamp = tmpTimestamp; + location->eventType = static_cast(tmpEventType); + location->expires = tmpExpires; + timeline.push_back(std::move(location)); break; } case WeatherData::eventtype::Clouds: { + std::unique_ptr clouds = std::make_unique(); + clouds->timestamp = tmpTimestamp; + clouds->eventType = static_cast(tmpEventType); + clouds->expires = tmpExpires; + timeline.push_back(std::move(clouds)); break; } default: { break; } } + + getCurrentPressure(); + tidyTimeline(); + getTimelineLength(); QCBORDecode_ExitMap(&decodeContext); auto uErr = QCBORDecode_Finish(&decodeContext); @@ -99,8 +166,6 @@ namespace Pinetime { return BLE_ATT_ERR_INSUFFICIENT_RES; } } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { - // TODO: Detect control messages - // Encode uint8_t buffer[64]; QCBOREncodeContext encodeContext; @@ -125,46 +190,46 @@ namespace Pinetime { return 0; } - WeatherData::location WeatherService::getCurrentLocation() const { - return WeatherData::location(); + WeatherData::Location WeatherService::getCurrentLocation() const { + return WeatherData::Location(); } - WeatherData::clouds WeatherService::getCurrentClouds() const { - return WeatherData::clouds(); + WeatherData::Clouds WeatherService::getCurrentClouds() const { + return WeatherData::Clouds(); } - WeatherData::obscuration WeatherService::getCurrentObscuration() const { - return WeatherData::obscuration(); + WeatherData::Obscuration WeatherService::getCurrentObscuration() const { + return WeatherData::Obscuration(); } - WeatherData::precipitation WeatherService::getCurrentPrecipitation() const { - return WeatherData::precipitation(); + WeatherData::Precipitation WeatherService::getCurrentPrecipitation() const { + return WeatherData::Precipitation(); } - WeatherData::wind WeatherService::getCurrentWind() const { - return WeatherData::wind(); + WeatherData::Wind WeatherService::getCurrentWind() const { + return WeatherData::Wind(); } - WeatherData::temperature WeatherService::getCurrentTemperature() const { - return WeatherData::temperature(); + WeatherData::Temperature WeatherService::getCurrentTemperature() const { + return WeatherData::Temperature(); } - WeatherData::humidity WeatherService::getCurrentHumidity() const { - return WeatherData::humidity(); + WeatherData::Humidity WeatherService::getCurrentHumidity() const { + return WeatherData::Humidity(); } - WeatherData::pressure WeatherService::getCurrentPressure() const { + WeatherData::Pressure WeatherService::getCurrentPressure() const { uint64_t currentTimestamp = getCurrentUNIXTimestamp(); for (auto&& header : timeline) { if (header->eventType == WeatherData::eventtype::Pressure && header->timestamp + header->expires <= currentTimestamp) { - return WeatherData::pressure(); + return WeatherData::Pressure(); } } - return WeatherData::pressure(); + return WeatherData::Pressure(); } - WeatherData::airquality WeatherService::getCurrentQuality() const { - return WeatherData::airquality(); + WeatherData::AirQuality WeatherService::getCurrentQuality() const { + return WeatherData::AirQuality(); } size_t WeatherService::getTimelineLength() const { return timeline.size(); } - bool WeatherService::addEventToTimeline(std::unique_ptr event) { + bool WeatherService::addEventToTimeline(std::unique_ptr event) { if (timeline.size() == timeline.max_size()) { return false; } @@ -188,7 +253,7 @@ namespace Pinetime { uint64_t timeCurrent = 0; timeline.erase(std::remove_if(std::begin(timeline), std::end(timeline), - [&](std::unique_ptr const& header) { + [&](std::unique_ptr const& header) { return header->timestamp + header->expires > timeCurrent; }), std::end(timeline)); @@ -196,8 +261,8 @@ namespace Pinetime { std::sort(std::begin(timeline), std::end(timeline), compareTimelineEvents); } - bool WeatherService::compareTimelineEvents(const std::unique_ptr& first, - const std::unique_ptr& second) { + bool WeatherService::compareTimelineEvents(const std::unique_ptr& first, + const std::unique_ptr& second) { return first->timestamp > second->timestamp; } diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h index ef99db86..64a8213a 100644 --- a/src/components/ble/weather/WeatherService.h +++ b/src/components/ble/weather/WeatherService.h @@ -57,15 +57,15 @@ namespace Pinetime { /* * Helper functions for quick access to currently valid data */ - WeatherData::location getCurrentLocation() const; - WeatherData::clouds getCurrentClouds() const; - WeatherData::obscuration getCurrentObscuration() const; - WeatherData::precipitation getCurrentPrecipitation() const; - WeatherData::wind getCurrentWind() const; - WeatherData::temperature getCurrentTemperature() const; - WeatherData::humidity getCurrentHumidity() const; - WeatherData::pressure getCurrentPressure() const; - WeatherData::airquality getCurrentQuality() const; + WeatherData::Location getCurrentLocation() const; + WeatherData::Clouds getCurrentClouds() const; + WeatherData::Obscuration getCurrentObscuration() const; + WeatherData::Precipitation getCurrentPrecipitation() const; + WeatherData::Wind getCurrentWind() const; + WeatherData::Temperature getCurrentTemperature() const; + WeatherData::Humidity getCurrentHumidity() const; + WeatherData::Pressure getCurrentPressure() const; + WeatherData::AirQuality getCurrentQuality() const; /* * Management functions @@ -74,7 +74,7 @@ namespace Pinetime { * Adds an event to the timeline * @return */ - bool addEventToTimeline(std::unique_ptr event); + bool addEventToTimeline(std::unique_ptr event); /** * Gets the current timeline length */ @@ -86,37 +86,36 @@ namespace Pinetime { bool hasTimelineEventOfType(WeatherData::eventtype type) const; private: - ble_uuid128_t msUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_UUID_BASE}; + ble_uuid128_t weatherUUID {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_UUID_BASE}; /** * Just write timeline data here */ - ble_uuid128_t wDataCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_CHAR_UUID(0x00, 0x01)}; + ble_uuid128_t weatherDataCharUUID {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_CHAR_UUID(0x00, 0x01)}; /** - * This doesn't take timeline data - * but provides some control over it + * This doesn't take timeline data, + * provides some control over it */ - ble_uuid128_t wControlCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_CHAR_UUID(0x00, 0x02)}; + ble_uuid128_t weatherControlCharUUID {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_CHAR_UUID(0x00, 0x02)}; - const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = reinterpret_cast(&wDataCharUuid), + const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUUID.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_NOTIFY, .val_handle = &eventHandle}, - {.uuid = reinterpret_cast(&wControlCharUuid), + {.uuid = &weatherControlCharUUID.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ}}; const struct ble_gatt_svc_def serviceDefinition[2] = { - {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = reinterpret_cast(&msUuid), .characteristics = characteristicDefinition}, - {0}}; + {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUUID.u, .characteristics = characteristicDefinition}, {0}}; uint16_t eventHandle {}; Pinetime::System::SystemTask& system; Pinetime::Controllers::DateTime& dateTimeController; - std::vector> timeline; + std::vector> timeline; /** * Cleans up the timeline of expired events @@ -127,11 +126,11 @@ namespace Pinetime { /** * Compares two timeline events */ - static bool compareTimelineEvents(const std::unique_ptr& first, - const std::unique_ptr& second); + static bool compareTimelineEvents(const std::unique_ptr& first, + const std::unique_ptr& second); /** - * + * Returns current UNIX timestamp */ uint64_t getCurrentUNIXTimestamp() const; };