summaryrefslogtreecommitdiff
path: root/src/components/ble/weather
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/ble/weather')
-rw-r--r--src/components/ble/weather/WeatherData.h338
-rw-r--r--src/components/ble/weather/WeatherService.cpp208
-rw-r--r--src/components/ble/weather/WeatherService.h139
3 files changed, 685 insertions, 0 deletions
diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h
new file mode 100644
index 00000000..c1d53f4e
--- /dev/null
+++ b/src/components/ble/weather/WeatherData.h
@@ -0,0 +1,338 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+/**
+ * Different weather events, weather data structures used by {@link WeatherService.h}
+ *
+ *
+ * Implemented based on and other material:
+ * https://en.wikipedia.org/wiki/METAR
+ * https://www.weather.gov/jetstream/obscurationtypes
+ * http://www.faraim.org/aim/aim-4-03-14-493.html
+ */
+
+namespace Pinetime {
+ namespace Controllers {
+ class WeatherData {
+ public:
+ /**
+ * Visibility obscuration types
+ */
+ enum class obscurationtype {
+ /** No obscuration */
+ 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 */
+ Haze = 2,
+ /** Small fire-created particles suspended in the air */
+ Smoke = 3,
+ /** Fine rock powder, from for example volcanoes */
+ Ash = 4,
+ /** Fine particles of earth suspended in the air by the wind */
+ Dust = 5,
+ /** Fine particles of sand suspended in the air by the wind */
+ Sand = 6,
+ /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
+ Mist = 7,
+ };
+
+ /**
+ * Types of precipitation
+ */
+ enum class precipitationtype {
+ /**
+ * No precipitation
+ *
+ * Theoretically we could just _not_ send the event, but then
+ * how do we differentiate between no precipitation and
+ * no information about precipitation
+ */
+ None = 0,
+ /** Drops larger than a drizzle; also widely separated drizzle */
+ Rain = 1,
+ /** Fairly uniform rain consisting of fine drops */
+ Drizzle = 2,
+ /** Rain that freezes upon contact with objects and ground */
+ FreezingRain = 3,
+ /** Rain + hail; ice pellets; small translucent frozen raindrops */
+ Sleet = 4,
+ /** Larger ice pellets; falling separately or in irregular clumps */
+ Hail = 5,
+ /** Hail with smaller grains of ice; mini-snowballs */
+ SmallHail = 6,
+ /** Snow... */
+ Snow = 7,
+ /** 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
+ };
+
+ /**
+ * These are special events that can "enhance" the "experience" of existing weather events
+ */
+ enum class specialtype {
+ /** Strong wind with a sudden onset that lasts at least a minute */
+ Squall = 0,
+ /** Series of waves in a water body caused by the displacement of a large volume of water */
+ Tsunami = 1,
+ /** Violent; rotating column of air */
+ Tornado = 2,
+ /** Unplanned; unwanted; uncontrolled fire in an area */
+ Fire = 3,
+ /** Thunder and/or lightning */
+ Thunder = 4,
+ };
+
+ /**
+ * These are used for weather timeline manipulation
+ * that isn't just adding to the stack of weather events
+ */
+ enum class controlcodes {
+ /** How much is stored already */
+ GetLength = 0,
+ /** This wipes the entire timeline */
+ DelTimeline = 1,
+ /** There's a currently valid timeline event with the given type */
+ HasValidEvent = 3
+ };
+
+ /**
+ * Events have types
+ * then they're easier to parse after sending them over the air
+ */
+ enum class eventtype {
+ /** @see obscuration */
+ Obscuration = 0,
+ /** @see precipitation */
+ Precipitation = 1,
+ /** @see wind */
+ Wind = 2,
+ /** @see temperature */
+ Temperature = 3,
+ /** @see airquality */
+ AirQuality = 4,
+ /** @see special */
+ Special = 5,
+ /** @see pressure */
+ Pressure = 6,
+ /** @see location */
+ Location = 7,
+ /** @see cloud */
+ Clouds = 8,
+ };
+
+ /**
+ * Valid event query
+ */
+ class valideventquery {
+ public:
+ static constexpr controlcodes code = controlcodes::HasValidEvent;
+ eventtype eventType;
+ };
+
+ /** The header used for further parsing */
+ class timelineheader {
+ public:
+ /** UNIX timestamp */
+ uint64_t timestamp;
+ /**
+ * Time in seconds until the event expires
+ *
+ * 32 bits ought to be enough for everyone
+ *
+ * If there's a newer event of the same type then it overrides this one, even if it hasn't expired
+ */
+ uint32_t expires;
+ /**
+ * What type of weather-related event
+ */
+ eventtype eventType;
+ };
+
+ /** Specifies how cloudiness is stored */
+ class clouds : public timelineheader {
+ public:
+ /** Cloud coverage in percentage, 0-100% */
+ uint8_t amount;
+ };
+
+ /** Specifies how obscuration is stored */
+ class obscuration : public timelineheader {
+ public:
+ /** Type */
+ obscurationtype type;
+ /** Visibility distance in meters */
+ uint8_t amount;
+ };
+
+ /** Specifies how precipitation is stored */
+ class precipitation : public timelineheader {
+ public:
+ /** Type */
+ precipitationtype type;
+ /** How much is it going to rain? In millimeters */
+ uint8_t amount;
+ };
+
+ /**
+ * How wind speed is stored
+ *
+ * In order to represent bursts of wind instead of constant wind,
+ * you have minimum and maximum speeds.
+ *
+ * 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 {
+ public:
+ /** Meters per second */
+ uint8_t speedMin;
+ /** Meters per second */
+ uint8_t speedMax;
+ /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
+ uint8_t directionMin;
+ /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
+ uint8_t directionMax;
+ };
+
+ /**
+ * How temperature is stored
+ *
+ * As it's annoying to figure out the dewpoint on the watch,
+ * please send it from the companion
+ *
+ * We don't do floats, microdegrees are not useful. Make sure to multiply.
+ */
+ class temperature : public timelineheader {
+ public:
+ /** Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250) */
+ int16_t temperature;
+ /** Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250) */
+ int16_t dewPoint;
+ };
+
+ /**
+ * How location info is stored
+ *
+ * This can be mostly static with long expiration,
+ * as it usually is, but it could change during a trip for ex.
+ * so we allow changing it dynamically.
+ *
+ * Location info can be for some kind of map watchface
+ * or daylight calculations, should those be required.
+ *
+ */
+ class location : public timelineheader {
+ public:
+ /** Location name */
+ std::string location;
+ /** Altitude relative to sea level in meters */
+ int16_t altitude;
+ /** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
+ int32_t latitude;
+ /** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
+ int32_t longitude;
+ };
+
+ /**
+ * How humidity is stored
+ */
+ class humidity : public timelineheader {
+ public:
+ /** Relative humidity, 0-100% */
+ uint8_t humidity;
+ };
+
+ /**
+ * How air pressure is stored
+ */
+ class pressure : public timelineheader {
+ public:
+ /** Air pressure in hectopascals (hPa) */
+ int16_t pressure;
+ };
+
+ /**
+ * How special events are stored
+ */
+ class special : public timelineheader {
+ public:
+ /** Special event's type */
+ specialtype type;
+ };
+
+ /**
+ * How air quality is stored
+ *
+ * These events are a bit more complex because the topic is not simple,
+ * the intention is to heavy-lift the annoying preprocessing from the watch
+ * this allows watchface or watchapp makers to generate accurate alerts and graphics
+ *
+ * If this needs further enforced standardization, pull requests are welcome
+ */
+ class airquality : public timelineheader {
+ public:
+ /**
+ * The name of the pollution
+ *
+ * for the sake of better compatibility with watchapps
+ * that might want to use this data for say visuals
+ * don't localize the name.
+ *
+ * Ideally watchapp itself localizes the name, if it's at all needed.
+ *
+ * E.g.
+ * For generic ones use "PM0.1", "PM5", "PM10"
+ * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
+ * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
+ */
+ std::string polluter;
+ /**
+ * Amount of the pollution in SI units,
+ * otherwise it's going to be difficult to create UI, alerts
+ * and so on and for.
+ *
+ * See more:
+ * https://ec.europa.eu/environment/air/quality/standards.htm
+ * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
+ *
+ * Example units:
+ * count/m³ for pollen
+ * µgC/m³ for micrograms of organic carbon
+ * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
+ * mg/m³ CO2, CO
+ * ng/m³ for heavy metals
+ *
+ * List is not comprehensive, should be improved.
+ * The current ones are what watchapps assume.
+ *
+ * Note: ppb and ppm to concentration should be calculated on the companion, using
+ * the correct formula (taking into account temperature and air pressure)
+ *
+ * Note2: The amount is off by times 100, for two decimal places of precision.
+ * E.g. 54.32µg/m³ is 5432
+ *
+ */
+ uint32_t amount;
+ };
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp
new file mode 100644
index 00000000..006fc6c1
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.cpp
@@ -0,0 +1,208 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <qcbor/qcbor_spiffy_decode.h>
+#include "WeatherService.h"
+#include "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "systemtask/SystemTask.h"
+
+int WeatherCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(conn_handle, attr_handle, ctxt);
+}
+
+namespace Pinetime {
+ namespace Controllers {
+ WeatherService::WeatherService(System::SystemTask& system, DateTime& dateTimeController)
+ : system(system), dateTimeController(dateTimeController) {
+ }
+
+ void WeatherService::Init() {
+ uint8_t res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0)
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+ }
+
+ int WeatherService::OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) {
+ 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);
+ 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<int64_t*>(&(timelineHeader.timestamp)));
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", reinterpret_cast<int64_t*>(&(timelineHeader.expires)));
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", reinterpret_cast<int64_t*>(&(timelineHeader.eventType)));
+ switch (timelineHeader.eventType) {
+ // TODO: Populate
+ case WeatherData::eventtype::AirQuality: {
+ break;
+ }
+ case WeatherData::eventtype::Obscuration: {
+ break;
+ }
+ case WeatherData::eventtype::Precipitation: {
+ break;
+ }
+ case WeatherData::eventtype::Wind: {
+ break;
+ }
+ case WeatherData::eventtype::Temperature: {
+ break;
+ }
+ case WeatherData::eventtype::Special: {
+ break;
+ }
+ case WeatherData::eventtype::Pressure: {
+ break;
+ }
+ case WeatherData::eventtype::Location: {
+ break;
+ }
+ case WeatherData::eventtype::Clouds: {
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ QCBORDecode_ExitMap(&decodeContext);
+
+ auto uErr = QCBORDecode_Finish(&decodeContext);
+ if (uErr != 0) {
+ 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;
+ QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
+ QCBOREncode_OpenMap(&encodeContext);
+ QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
+ QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
+ QCBOREncode_CloseMap(&encodeContext);
+
+ UsefulBufC encodedEvent;
+ auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
+ if (uErr != 0) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
+ if (res == 0) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+
+ return 0;
+ }
+ return 0;
+ }
+
+ WeatherData::location WeatherService::getCurrentLocation() const {
+ return WeatherData::location();
+ }
+ WeatherData::clouds WeatherService::getCurrentClouds() const {
+ return WeatherData::clouds();
+ }
+ WeatherData::obscuration WeatherService::getCurrentObscuration() const {
+ return WeatherData::obscuration();
+ }
+ WeatherData::precipitation WeatherService::getCurrentPrecipitation() const {
+ return WeatherData::precipitation();
+ }
+ WeatherData::wind WeatherService::getCurrentWind() const {
+ return WeatherData::wind();
+ }
+ WeatherData::temperature WeatherService::getCurrentTemperature() const {
+ return WeatherData::temperature();
+ }
+ WeatherData::humidity WeatherService::getCurrentHumidity() const {
+ return WeatherData::humidity();
+ }
+ 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();
+ }
+
+ WeatherData::airquality WeatherService::getCurrentQuality() const {
+ return WeatherData::airquality();
+ }
+
+ size_t WeatherService::getTimelineLength() const {
+ return timeline.size();
+ }
+
+ bool WeatherService::addEventToTimeline(std::unique_ptr<WeatherData::timelineheader> event) {
+ if (timeline.size() == timeline.max_size()) {
+ return false;
+ }
+
+ timeline.push_back(std::move(event));
+ return true;
+ }
+
+ bool WeatherService::hasTimelineEventOfType(const WeatherData::eventtype type) const {
+ uint64_t currentTimestamp = getCurrentUNIXTimestamp();
+ for (auto&& header : timeline) {
+ if (header->eventType == type && header->timestamp + header->expires <= currentTimestamp) {
+ // TODO: Check if its currently valid
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void WeatherService::tidyTimeline() {
+ uint64_t timeCurrent = 0;
+ timeline.erase(std::remove_if(std::begin(timeline),
+ std::end(timeline),
+ [&](std::unique_ptr<WeatherData::timelineheader> const& header) {
+ return header->timestamp + header->expires > timeCurrent;
+ }),
+ std::end(timeline));
+
+ std::sort(std::begin(timeline), std::end(timeline), compareTimelineEvents);
+ }
+
+ bool WeatherService::compareTimelineEvents(const std::unique_ptr<WeatherData::timelineheader>& first,
+ const std::unique_ptr<WeatherData::timelineheader>& second) {
+ return first->timestamp > second->timestamp;
+ }
+
+ uint64_t WeatherService::getCurrentUNIXTimestamp() const {
+ return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
+ }
+ }
+}
diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h
new file mode 100644
index 00000000..ef99db86
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.h
@@ -0,0 +1,139 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#include <host/ble_uuid.h>
+#undef max
+#undef min
+
+#include "WeatherData.h"
+#include <components/datetime/DateTimeController.h>
+
+// 00030000-78fc-48fe-8e23-433b3a1942d0
+#define WEATHER_SERVICE_UUID_BASE \
+ { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0x03, 0x00 }
+#define WEATHER_SERVICE_CHAR_UUID(y, x) \
+ { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, (x), (y), 0x03, 0x00 }
+
+int WeatherCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg);
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+
+ class WeatherService {
+ public:
+ explicit WeatherService(System::SystemTask& system, DateTime& dateTimeController);
+
+ void Init();
+
+ int OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt);
+
+ /*
+ * 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;
+
+ /*
+ * Management functions
+ */
+ /**
+ * Adds an event to the timeline
+ * @return
+ */
+ bool addEventToTimeline(std::unique_ptr<WeatherData::timelineheader> event);
+ /**
+ * Gets the current timeline length
+ */
+ size_t getTimelineLength() const;
+ /**
+ * Checks if an event of a certain type exists in the timeline
+ * @return
+ */
+ bool hasTimelineEventOfType(WeatherData::eventtype type) const;
+
+ private:
+ ble_uuid128_t msUuid {.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)};
+ /**
+ * This doesn't take timeline data
+ * but provides some control over it
+ */
+ ble_uuid128_t wControlCharUuid {.u = {.type = BLE_UUID_TYPE_128}, .value = WEATHER_SERVICE_CHAR_UUID(0x00, 0x02)};
+
+ const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = reinterpret_cast<ble_uuid_t*>(&wDataCharUuid),
+ .access_cb = WeatherCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &eventHandle},
+ {.uuid = reinterpret_cast<ble_uuid_t*>(&wControlCharUuid),
+ .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<ble_uuid_t*>(&msUuid), .characteristics = characteristicDefinition},
+ {0}};
+
+ uint16_t eventHandle {};
+
+ Pinetime::System::SystemTask& system;
+ Pinetime::Controllers::DateTime& dateTimeController;
+
+ std::vector<std::unique_ptr<WeatherData::timelineheader>> timeline;
+
+ /**
+ * Cleans up the timeline of expired events
+ * @return result code
+ */
+ void tidyTimeline();
+
+ /**
+ * Compares two timeline events
+ */
+ static bool compareTimelineEvents(const std::unique_ptr<WeatherData::timelineheader>& first,
+ const std::unique_ptr<WeatherData::timelineheader>& second);
+
+ /**
+ *
+ */
+ uint64_t getCurrentUNIXTimestamp() const;
+ };
+ }
+}