Initial Weather service skeleton
This commit is contained in:
@@ -36,6 +36,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||
alertNotificationClient {systemTask, notificationManager},
|
||||
currentTimeService {dateTimeController},
|
||||
musicService {systemTask},
|
||||
weatherService {systemTask, dateTimeController},
|
||||
navService {systemTask},
|
||||
batteryInformationService {batteryController},
|
||||
immediateAlertService {systemTask, notificationManager},
|
||||
@@ -77,6 +78,7 @@ void NimbleController::Init() {
|
||||
currentTimeClient.Init();
|
||||
currentTimeService.Init();
|
||||
musicService.Init();
|
||||
weatherService.Init();
|
||||
navService.Init();
|
||||
anService.Init();
|
||||
dfuService.Init();
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "components/ble/ServiceDiscovery.h"
|
||||
#include "components/ble/HeartRateService.h"
|
||||
#include "components/ble/MotionService.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
@@ -93,6 +94,7 @@ namespace Pinetime {
|
||||
AlertNotificationClient alertNotificationClient;
|
||||
CurrentTimeService currentTimeService;
|
||||
MusicService musicService;
|
||||
WeatherService weatherService;
|
||||
NavigationService navService;
|
||||
BatteryInformationService batteryInformationService;
|
||||
ImmediateAlertService immediateAlertService;
|
||||
|
338
src/components/ble/weather/WeatherData.h
Normal file
338
src/components/ble/weather/WeatherData.h
Normal file
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
208
src/components/ble/weather/WeatherService.cpp
Normal file
208
src/components/ble/weather/WeatherService.cpp
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
139
src/components/ble/weather/WeatherService.h
Normal file
139
src/components/ble/weather/WeatherService.h
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user