summaryrefslogtreecommitdiff
path: root/src/components/ble
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/ble')
-rw-r--r--src/components/ble/AlertNotificationClient.cpp4
-rw-r--r--src/components/ble/AlertNotificationClient.h2
-rw-r--r--src/components/ble/AlertNotificationService.cpp7
-rw-r--r--src/components/ble/BatteryInformationService.cpp2
-rw-r--r--src/components/ble/BleController.cpp2
-rw-r--r--src/components/ble/BleController.h11
-rw-r--r--src/components/ble/CurrentTimeClient.cpp2
-rw-r--r--src/components/ble/CurrentTimeClient.h2
-rw-r--r--src/components/ble/CurrentTimeService.cpp2
-rw-r--r--src/components/ble/DeviceInformationService.cpp2
-rw-r--r--src/components/ble/DeviceInformationService.h1
-rw-r--r--src/components/ble/DfuService.cpp2
-rw-r--r--src/components/ble/FSService.cpp330
-rw-r--r--src/components/ble/FSService.h191
-rw-r--r--src/components/ble/HeartRateService.cpp2
-rw-r--r--src/components/ble/ImmediateAlertService.cpp4
-rw-r--r--src/components/ble/MotionService.cpp8
-rw-r--r--src/components/ble/MusicService.cpp2
-rw-r--r--src/components/ble/NavigationService.cpp2
-rw-r--r--src/components/ble/NimbleController.cpp242
-rw-r--r--src/components/ble/NimbleController.h49
-rw-r--r--src/components/ble/NotificationManager.cpp2
-rw-r--r--src/components/ble/ServiceDiscovery.cpp4
-rw-r--r--src/components/ble/weather/WeatherData.h385
-rw-r--r--src/components/ble/weather/WeatherService.cpp604
-rw-r--r--src/components/ble/weather/WeatherService.h172
26 files changed, 1951 insertions, 85 deletions
diff --git a/src/components/ble/AlertNotificationClient.cpp b/src/components/ble/AlertNotificationClient.cpp
index 5e5c25cf..0f850874 100644
--- a/src/components/ble/AlertNotificationClient.cpp
+++ b/src/components/ble/AlertNotificationClient.cpp
@@ -1,6 +1,6 @@
-#include "AlertNotificationClient.h"
+#include "components/ble/AlertNotificationClient.h"
#include <algorithm>
-#include "NotificationManager.h"
+#include "components/ble/NotificationManager.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/AlertNotificationClient.h b/src/components/ble/AlertNotificationClient.h
index dfba8143..2d6a3873 100644
--- a/src/components/ble/AlertNotificationClient.h
+++ b/src/components/ble/AlertNotificationClient.h
@@ -7,7 +7,7 @@
#include <host/ble_gap.h>
#undef max
#undef min
-#include "BleClient.h"
+#include "components/ble/BleClient.h"
namespace Pinetime {
diff --git a/src/components/ble/AlertNotificationService.cpp b/src/components/ble/AlertNotificationService.cpp
index 56fc595f..04819122 100644
--- a/src/components/ble/AlertNotificationService.cpp
+++ b/src/components/ble/AlertNotificationService.cpp
@@ -1,8 +1,8 @@
-#include "AlertNotificationService.h"
+#include "components/ble/AlertNotificationService.h"
#include <hal/nrf_rtc.h>
#include <cstring>
#include <algorithm>
-#include "NotificationManager.h"
+#include "components/ble/NotificationManager.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
@@ -53,8 +53,9 @@ int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle
// Ignore notifications with empty message
const auto packetLen = OS_MBUF_PKTLEN(ctxt->om);
- if (packetLen <= headerSize)
+ if (packetLen <= headerSize) {
return 0;
+ }
size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));
diff --git a/src/components/ble/BatteryInformationService.cpp b/src/components/ble/BatteryInformationService.cpp
index 29178667..9a3f86f5 100644
--- a/src/components/ble/BatteryInformationService.cpp
+++ b/src/components/ble/BatteryInformationService.cpp
@@ -1,5 +1,5 @@
+#include "components/ble/BatteryInformationService.h"
#include <nrf_log.h>
-#include "BatteryInformationService.h"
#include "components/battery/BatteryController.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/BleController.cpp b/src/components/ble/BleController.cpp
index 7c14aeb7..a80c9719 100644
--- a/src/components/ble/BleController.cpp
+++ b/src/components/ble/BleController.cpp
@@ -1,4 +1,4 @@
-#include "BleController.h"
+#include "components/ble/BleController.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/BleController.h b/src/components/ble/BleController.h
index 2cba26a9..72b87663 100644
--- a/src/components/ble/BleController.h
+++ b/src/components/ble/BleController.h
@@ -9,7 +9,7 @@ namespace Pinetime {
public:
using BleAddress = std::array<uint8_t, 6>;
enum class FirmwareUpdateStates { Idle, Running, Validated, Error };
- enum class AddressTypes { Public, Random };
+ enum class AddressTypes { Public, Random, RPA_Public, RPA_Random };
Ble() = default;
bool IsConnected() const {
@@ -48,6 +48,12 @@ namespace Pinetime {
void AddressType(AddressTypes t) {
addressType = t;
}
+ void SetPairingKey(uint32_t k) {
+ pairingKey = k;
+ }
+ uint32_t GetPairingKey() const {
+ return pairingKey;
+ }
private:
bool isConnected = false;
@@ -57,6 +63,7 @@ namespace Pinetime {
FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle;
BleAddress address;
AddressTypes addressType;
+ uint32_t pairingKey = 0;
};
}
-} \ No newline at end of file
+}
diff --git a/src/components/ble/CurrentTimeClient.cpp b/src/components/ble/CurrentTimeClient.cpp
index 90d1f0c7..dd8171b9 100644
--- a/src/components/ble/CurrentTimeClient.cpp
+++ b/src/components/ble/CurrentTimeClient.cpp
@@ -1,4 +1,4 @@
-#include "CurrentTimeClient.h"
+#include "components/ble/CurrentTimeClient.h"
#include <hal/nrf_rtc.h>
#include <nrf_log.h>
#include "components/datetime/DateTimeController.h"
diff --git a/src/components/ble/CurrentTimeClient.h b/src/components/ble/CurrentTimeClient.h
index 6424c035..9e48be79 100644
--- a/src/components/ble/CurrentTimeClient.h
+++ b/src/components/ble/CurrentTimeClient.h
@@ -5,7 +5,7 @@
#undef max
#undef min
#include <cstdint>
-#include "BleClient.h"
+#include "components/ble/BleClient.h"
namespace Pinetime {
namespace Controllers {
diff --git a/src/components/ble/CurrentTimeService.cpp b/src/components/ble/CurrentTimeService.cpp
index eefb7ec1..e509aeaf 100644
--- a/src/components/ble/CurrentTimeService.cpp
+++ b/src/components/ble/CurrentTimeService.cpp
@@ -1,4 +1,4 @@
-#include "CurrentTimeService.h"
+#include "components/ble/CurrentTimeService.h"
#include <hal/nrf_rtc.h>
#include <nrf_log.h>
diff --git a/src/components/ble/DeviceInformationService.cpp b/src/components/ble/DeviceInformationService.cpp
index 778d6e35..0f47c90f 100644
--- a/src/components/ble/DeviceInformationService.cpp
+++ b/src/components/ble/DeviceInformationService.cpp
@@ -1,4 +1,4 @@
-#include "DeviceInformationService.h"
+#include "components/ble/DeviceInformationService.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/DeviceInformationService.h b/src/components/ble/DeviceInformationService.h
index 54b3e961..a269afb4 100644
--- a/src/components/ble/DeviceInformationService.h
+++ b/src/components/ble/DeviceInformationService.h
@@ -1,4 +1,5 @@
#pragma once
+#include <cstdint>
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
diff --git a/src/components/ble/DfuService.cpp b/src/components/ble/DfuService.cpp
index 3d6416fa..71dcc7e6 100644
--- a/src/components/ble/DfuService.cpp
+++ b/src/components/ble/DfuService.cpp
@@ -1,4 +1,4 @@
-#include "DfuService.h"
+#include "components/ble/DfuService.h"
#include <cstring>
#include "components/ble/BleController.h"
#include "drivers/SpiNorFlash.h"
diff --git a/src/components/ble/FSService.cpp b/src/components/ble/FSService.cpp
new file mode 100644
index 00000000..8dc9ed67
--- /dev/null
+++ b/src/components/ble/FSService.cpp
@@ -0,0 +1,330 @@
+#include <nrf_log.h>
+#include "FSService.h"
+#include "components/ble/BleController.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t FSService::fsServiceUuid;
+constexpr ble_uuid128_t FSService::fsVersionUuid;
+constexpr ble_uuid128_t FSService::fsTransferUuid;
+
+int FSServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto* fsService = static_cast<FSService*>(arg);
+ return fsService->OnFSServiceRequested(conn_handle, attr_handle, ctxt);
+}
+
+FSService::FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs)
+ : systemTask {systemTask},
+ fs {fs},
+ characteristicDefinition {{.uuid = &fsVersionUuid.u,
+ .access_cb = FSServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ .val_handle = &versionCharacteristicHandle},
+ {
+ .uuid = &fsTransferUuid.u,
+ .access_cb = FSServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &transferCharacteristicHandle,
+ },
+ {0}},
+ serviceDefinition {
+ {/* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = &fsServiceUuid.u,
+ .characteristics = characteristicDefinition},
+ {0},
+ } {
+}
+
+void FSService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
+ if (attributeHandle == versionCharacteristicHandle) {
+ NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle);
+ int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion));
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ if (attributeHandle == transferCharacteristicHandle) {
+ return FSCommandHandler(connectionHandle, context->om);
+ }
+ return 0;
+}
+
+int FSService::FSCommandHandler(uint16_t connectionHandle, os_mbuf* om) {
+ auto command = static_cast<commands>(om->om_data[0]);
+ NRF_LOG_INFO("[FS_S] -> FSCommandHandler Command %d", command);
+ // Just always make sure we are awake...
+ systemTask.PushMessage(Pinetime::System::Messages::StartFileTransfer);
+ vTaskDelay(10);
+ while (systemTask.IsSleeping()) {
+ vTaskDelay(100); // 50ms
+ }
+ lfs_dir_t dir = {0};
+ lfs_info info = {0};
+ lfs_file f = {0};
+ switch (command) {
+ case commands::READ: {
+ NRF_LOG_INFO("[FS_S] -> Read");
+ auto* header = (ReadHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ if (plen > maxpathlen) { //> counts for null term
+ return -1;
+ }
+ memcpy(filepath, header->pathstr, plen);
+ filepath[plen] = 0; // Copy and null teminate string
+ ReadResponse resp;
+ os_mbuf* om;
+ resp.command = commands::READ_DATA;
+ resp.status = 0x01;
+ resp.chunkoff = header->chunkoff;
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp.status = (int8_t) res;
+ resp.chunklen = 0;
+ resp.totallen = 0;
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ } else {
+ resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
+ resp.totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ uint8_t fileData[resp.chunklen] = {0};
+ resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ os_mbuf_append(om, fileData, resp.chunklen);
+ fs.FileClose(&f);
+ }
+
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::READ_PACING: {
+ NRF_LOG_INFO("[FS_S] -> Readpacing");
+ auto* header = (ReadHeader*) om->om_data;
+ ReadResponse resp;
+ resp.command = commands::READ_DATA;
+ resp.status = 0x01;
+ resp.chunkoff = header->chunkoff;
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp.status = (int8_t) res;
+ resp.chunklen = 0;
+ resp.totallen = 0;
+ } else {
+ resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
+ resp.totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ }
+ os_mbuf* om;
+ if (resp.chunklen > 0) {
+ uint8_t fileData[resp.chunklen] = {0};
+ resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ os_mbuf_append(om, fileData, resp.chunklen);
+ } else {
+ resp.chunklen = 0;
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ }
+ fs.FileClose(&f);
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::WRITE: {
+ NRF_LOG_INFO("[FS_S] -> Write");
+ auto* header = (WriteHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ if (plen > maxpathlen) { //> counts for null term
+ return -1; // TODO make this actually return a BLE notif
+ }
+ memcpy(filepath, header->pathstr, plen);
+ filepath[plen] = 0; // Copy and null teminate string
+ fileSize = header->totalSize;
+ WriteResponse resp;
+ resp.command = commands::WRITE_PACING;
+ resp.offset = header->offset;
+ resp.modTime = 0;
+
+ int res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT);
+ if (res == 0) {
+ fs.FileClose(&f);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ }
+ resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::WRITE_DATA: {
+ NRF_LOG_INFO("[FS_S] -> WriteData");
+ auto* header = (WritePacing*) om->om_data;
+ WriteResponse resp;
+ resp.command = commands::WRITE_PACING;
+ resp.offset = header->offset;
+ int res = 0;
+
+ if (!(res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT))) {
+ if ((res = fs.FileSeek(&f, header->offset)) >= 0) {
+ res = fs.FileWrite(&f, header->data, header->dataSize);
+ }
+ fs.FileClose(&f);
+ }
+ if (res < 0) {
+ resp.status = (int8_t) res;
+ }
+ resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::DELETE: {
+ NRF_LOG_INFO("[FS_S] -> Delete");
+ auto* header = (DelHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ memcpy(path, header->pathstr, plen);
+ path[plen] = 0; // Copy and null teminate string
+ DelResponse resp {};
+ resp.command = commands::DELETE_STATUS;
+ int res = fs.FileDelete(path);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(DelResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::MKDIR: {
+ NRF_LOG_INFO("[FS_S] -> MKDir");
+ auto* header = (MKDirHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ memcpy(path, header->pathstr, plen);
+ path[plen] = 0; // Copy and null teminate string
+ MKDirResponse resp {};
+ resp.command = commands::MKDIR_STATUS;
+ resp.modification_time = 0;
+ int res = fs.DirCreate(path);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MKDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::LISTDIR: {
+ NRF_LOG_INFO("[FS_S] -> ListDir");
+ ListDirHeader* header = (ListDirHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ path[plen] = 0; // Copy and null teminate string
+ memcpy(path, header->pathstr, plen);
+
+ ListDirResponse resp {};
+
+ resp.command = commands::LISTDIR_ENTRY;
+ resp.status = 0x01;
+ resp.totalentries = 0;
+ resp.entry = 0;
+ resp.modification_time = 0;
+ int res = fs.DirOpen(path, &dir);
+ if (res != 0) {
+ resp.status = (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ };
+ while (fs.DirRead(&dir, &info)) {
+ resp.totalentries++;
+ }
+ fs.DirRewind(&dir);
+ while (true) {
+ res = fs.DirRead(&dir, &info);
+ if (res <= 0) {
+ break;
+ }
+ switch (info.type) {
+ case LFS_TYPE_REG: {
+ resp.flags = 0;
+ resp.file_size = info.size;
+ break;
+ }
+ case LFS_TYPE_DIR: {
+ resp.flags = 1;
+ resp.file_size = 0;
+ break;
+ }
+ }
+
+ // strcpy(resp.path, info.name);
+ resp.path_length = strlen(info.name);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ os_mbuf_append(om, info.name, resp.path_length);
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ /*
+ * Todo Figure out how to know when the previous Notify was TX'd
+ * For now just delay 100ms to make sure that the data went out...
+ */
+ vTaskDelay(100); // Allow stuff to actually go out over the BLE conn
+ resp.entry++;
+ }
+ assert(fs.DirClose(&dir) == 0);
+ resp.file_size = 0;
+ resp.path_length = 0;
+ resp.flags = 0;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::MOVE: {
+ NRF_LOG_INFO("[FS_S] -> Move");
+ MoveHeader* header = (MoveHeader*) om->om_data;
+ uint16_t plen = header->OldPathLength;
+ // Null Terminate string
+ header->pathstr[plen] = 0;
+ char path[header->NewPathLength + 1] = {0};
+ memcpy(path, &header->pathstr[plen + 1], header->NewPathLength);
+ path[header->NewPathLength] = 0; // Copy and null teminate string
+ MoveResponse resp {};
+ resp.command = commands::MOVE_STATUS;
+ int8_t res = (int8_t) fs.Rename(header->pathstr, path);
+ resp.status = (res == 0) ? 1 : res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MoveResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ }
+ default:
+ break;
+ }
+ NRF_LOG_INFO("[FS_S] -> done ");
+ systemTask.PushMessage(Pinetime::System::Messages::StopFileTransfer);
+ return 0;
+}
+
+// Loads resp with file data given a valid filepath header and resp
+void FSService::prepareReadDataResp(ReadHeader* header, ReadResponse* resp) {
+ // uint16_t plen = header->pathlen;
+ resp->command = commands::READ_DATA;
+ resp->chunkoff = header->chunkoff;
+ resp->status = 0x01;
+ struct lfs_info info = {};
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp->status = 0x03;
+ resp->chunklen = 0;
+ resp->totallen = 0;
+ } else {
+ lfs_file f;
+ resp->chunklen = std::min(header->chunksize, info.size);
+ resp->totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ resp->chunklen = fs.FileRead(&f, resp->chunk, resp->chunklen);
+ fs.FileClose(&f);
+ }
+}
diff --git a/src/components/ble/FSService.h b/src/components/ble/FSService.h
new file mode 100644
index 00000000..828925a8
--- /dev/null
+++ b/src/components/ble/FSService.h
@@ -0,0 +1,191 @@
+#pragma once
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#undef max
+#undef min
+
+#include "components/fs/FS.h"
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class Ble;
+ class FSService {
+ public:
+ FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs);
+ void Init();
+
+ int OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
+ void NotifyFSRaw(uint16_t connectionHandle);
+
+ private:
+ Pinetime::System::SystemTask& systemTask;
+ Pinetime::Controllers::FS& fs;
+ static constexpr uint16_t FSServiceId {0xFEBB};
+ static constexpr uint16_t fsVersionId {0x0100};
+ static constexpr uint16_t fsTransferId {0x0200};
+ uint16_t fsVersion = {0x0004};
+ static constexpr uint16_t maxpathlen = 256;
+ static constexpr ble_uuid16_t fsServiceUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = {0xFEBB}}; // {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0xBB, 0xFE, 0xAF, 0xAD}};
+
+ static constexpr ble_uuid128_t fsVersionUuid {
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x01, 0xAF, 0xAD}};
+
+ static constexpr ble_uuid128_t fsTransferUuid {
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x02, 0xAF, 0xAD}};
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+ uint16_t versionCharacteristicHandle;
+ uint16_t transferCharacteristicHandle;
+
+ enum class commands : uint8_t {
+ INVALID = 0x00,
+ READ = 0x10,
+ READ_DATA = 0x11,
+ READ_PACING = 0x12,
+ WRITE = 0x20,
+ WRITE_PACING = 0x21,
+ WRITE_DATA = 0x22,
+ DELETE = 0x30,
+ DELETE_STATUS = 0x31,
+ MKDIR = 0x40,
+ MKDIR_STATUS = 0x41,
+ LISTDIR = 0x50,
+ LISTDIR_ENTRY = 0x51,
+ MOVE = 0x60,
+ MOVE_STATUS = 0x61
+ };
+ enum class FSState : uint8_t {
+ IDLE = 0x00,
+ READ = 0x01,
+ WRITE = 0x02,
+ };
+ FSState state;
+ char filepath[maxpathlen]; // TODO ..ugh fixed filepath len
+ int fileSize;
+ using ReadHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t chunkoff;
+ uint32_t chunksize;
+ char pathstr[];
+ };
+
+ using ReadResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t chunkoff;
+ uint32_t totallen;
+ uint32_t chunklen;
+ uint8_t chunk[];
+ };
+ using ReadPacing = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t chunkoff;
+ uint32_t chunksize;
+ };
+
+ using WriteHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t offset;
+ uint64_t modTime;
+ uint32_t totalSize;
+ char pathstr[];
+ };
+
+ using WriteResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t offset;
+ uint64_t modTime;
+ uint32_t freespace;
+ };
+
+ using WritePacing = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t offset;
+ uint32_t dataSize;
+ uint8_t data[];
+ };
+ using ListDirHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ char pathstr[];
+ };
+
+ using ListDirResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t path_length;
+ uint32_t entry;
+ uint32_t totalentries;
+ uint32_t flags;
+ uint64_t modification_time;
+ uint32_t file_size;
+ char path[];
+ };
+
+ using MKDirHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t padding2;
+ uint64_t time;
+ char pathstr[];
+ };
+
+ using MKDirResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint32_t padding1;
+ uint16_t padding2;
+ uint64_t modification_time;
+ };
+
+ using DelHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ char pathstr[];
+ };
+
+ using DelResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ };
+ using MoveHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t OldPathLength;
+ uint16_t NewPathLength;
+ char pathstr[];
+ };
+
+ using MoveResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ };
+
+ int FSCommandHandler(uint16_t connectionHandle, os_mbuf* om);
+ void prepareReadDataResp(ReadHeader* header, ReadResponse* resp);
+ };
+ }
+}
diff --git a/src/components/ble/HeartRateService.cpp b/src/components/ble/HeartRateService.cpp
index 75a038a2..f178af79 100644
--- a/src/components/ble/HeartRateService.cpp
+++ b/src/components/ble/HeartRateService.cpp
@@ -1,4 +1,4 @@
-#include "HeartRateService.h"
+#include "components/ble/HeartRateService.h"
#include "components/heartrate/HeartRateController.h"
#include "systemtask/SystemTask.h"
diff --git a/src/components/ble/ImmediateAlertService.cpp b/src/components/ble/ImmediateAlertService.cpp
index 17ed1a96..c80b3783 100644
--- a/src/components/ble/ImmediateAlertService.cpp
+++ b/src/components/ble/ImmediateAlertService.cpp
@@ -1,6 +1,6 @@
-#include "ImmediateAlertService.h"
+#include "components/ble/ImmediateAlertService.h"
#include <cstring>
-#include "NotificationManager.h"
+#include "components/ble/NotificationManager.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/MotionService.cpp b/src/components/ble/MotionService.cpp
index b4786ab5..6381915d 100644
--- a/src/components/ble/MotionService.cpp
+++ b/src/components/ble/MotionService.cpp
@@ -1,11 +1,11 @@
-#include "MotionService.h"
-#include "components/motion//MotionController.h"
+#include "components/ble/MotionService.h"
+#include "components/motion/MotionController.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
namespace {
- // 0002yyxx-78fc-48fe-8e23-433b3a1942d0
+ // 0003yyxx-78fc-48fe-8e23-433b3a1942d0
constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
return ble_uuid128_t{
.u = {.type = BLE_UUID_TYPE_128},
@@ -13,7 +13,7 @@ namespace {
};
}
- // 00020000-78fc-48fe-8e23-433b3a1942d0
+ // 00030000-78fc-48fe-8e23-433b3a1942d0
constexpr ble_uuid128_t BaseUuid() {
return CharUuid(0x00, 0x00);
}
diff --git a/src/components/ble/MusicService.cpp b/src/components/ble/MusicService.cpp
index 74fe9522..3457ce4c 100644
--- a/src/components/ble/MusicService.cpp
+++ b/src/components/ble/MusicService.cpp
@@ -15,7 +15,7 @@
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 "MusicService.h"
+#include "components/ble/MusicService.h"
#include "systemtask/SystemTask.h"
namespace {
diff --git a/src/components/ble/NavigationService.cpp b/src/components/ble/NavigationService.cpp
index b49148d2..5418b9e5 100644
--- a/src/components/ble/NavigationService.cpp
+++ b/src/components/ble/NavigationService.cpp
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-#include "NavigationService.h"
+#include "components/ble/NavigationService.h"
#include "systemtask/SystemTask.h"
diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp
index 1bcae1bc..d8510bd3 100644
--- a/src/components/ble/NimbleController.cpp
+++ b/src/components/ble/NimbleController.cpp
@@ -1,4 +1,6 @@
-#include "NimbleController.h"
+#include "components/ble/NimbleController.h"
+#include <cstring>
+
#include <hal/nrf_rtc.h>
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
@@ -6,13 +8,16 @@
#include <host/ble_hs.h>
#include <host/ble_hs_id.h>
#include <host/util/util.h>
-#undef max
-#undef min
+#include <controller/ble_ll.h>
+#include <controller/ble_hw.h>
#include <services/gap/ble_svc_gap.h>
#include <services/gatt/ble_svc_gatt.h>
+#undef max
+#undef min
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/datetime/DateTimeController.h"
+#include "components/fs/FS.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Controllers;
@@ -24,37 +29,44 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController,
- Controllers::MotionController& motionController)
+ Controllers::MotionController& motionController,
+ Controllers::FS& fs)
: systemTask {systemTask},
bleController {bleController},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
spiNorFlash {spiNorFlash},
+ fs {fs},
dfuService {systemTask, bleController, spiNorFlash},
+
currentTimeClient {dateTimeController},
anService {systemTask, notificationManager},
alertNotificationClient {systemTask, notificationManager},
currentTimeService {dateTimeController},
musicService {systemTask},
+ weatherService {systemTask, dateTimeController},
navService {systemTask},
batteryInformationService {batteryController},
immediateAlertService {systemTask, notificationManager},
heartRateService {systemTask, heartRateController},
- motionService{systemTask, motionController},
+ motionService {systemTask, motionController},
+ fsService {systemTask, fs},
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
}
void nimble_on_reset(int reason) {
- NRF_LOG_INFO("Resetting state; reason=%d\n", reason);
+ NRF_LOG_INFO("Nimble lost sync, resetting state; reason=%d", reason);
}
void nimble_on_sync(void) {
- int rc;
+ int rc;
- rc = ble_hs_util_ensure_addr(0);
- ASSERT(rc == 0);
+ NRF_LOG_INFO("Nimble is synced");
- nptr->StartAdvertising();
+ rc = ble_hs_util_ensure_addr(0);
+ ASSERT(rc == 0);
+
+ nptr->StartAdvertising();
}
int GAPEventCallback(struct ble_gap_event* event, void* arg) {
@@ -69,6 +81,7 @@ void NimbleController::Init() {
nptr = this;
ble_hs_cfg.reset_cb = nimble_on_reset;
ble_hs_cfg.sync_cb = nimble_on_sync;
+ ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_svc_gap_init();
ble_svc_gatt_init();
@@ -77,6 +90,7 @@ void NimbleController::Init() {
currentTimeClient.Init();
currentTimeService.Init();
musicService.Init();
+ weatherService.Init();
navService.Init();
anService.Init();
dfuService.Init();
@@ -84,6 +98,7 @@ void NimbleController::Init() {
immediateAlertService.Init();
heartRateService.Init();
motionService.Init();
+ fsService.Init();
int rc;
rc = ble_hs_util_ensure_addr(0);
@@ -97,28 +112,36 @@ void NimbleController::Init() {
Pinetime::Controllers::Ble::BleAddress address;
rc = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
ASSERT(rc == 0);
- bleController.AddressType((addrType == 0) ? Ble::AddressTypes::Public : Ble::AddressTypes::Random);
+
bleController.Address(std::move(address));
+ switch (addrType) {
+ case BLE_OWN_ADDR_PUBLIC:
+ bleController.AddressType(Ble::AddressTypes::Public);
+ break;
+ case BLE_OWN_ADDR_RANDOM:
+ bleController.AddressType(Ble::AddressTypes::Random);
+ break;
+ case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
+ bleController.AddressType(Ble::AddressTypes::RPA_Public);
+ break;
+ case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+ bleController.AddressType(Ble::AddressTypes::RPA_Random);
+ break;
+ }
rc = ble_gatts_start();
ASSERT(rc == 0);
- if (!ble_gap_adv_active() && !bleController.IsConnected())
- StartAdvertising();
+ RestoreBond();
+
+ StartAdvertising();
}
void NimbleController::StartAdvertising() {
- int rc;
-
- /* set adv parameters */
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
- /* advertising payload is split into advertising data and advertising
- response, because all data cannot fit into single packet; name of device
- is sent as response to scan request */
struct ble_hs_adv_fields rsp_fields;
- /* fill all fields and parameters with zeros */
memset(&adv_params, 0, sizeof(adv_params));
memset(&fields, 0, sizeof(fields));
memset(&rsp_fields, 0, sizeof(rsp_fields));
@@ -141,10 +164,11 @@ void NimbleController::StartAdvertising() {
fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
- rsp_fields.name = (uint8_t*) deviceName;
+ rsp_fields.name = reinterpret_cast<const uint8_t*>(deviceName);
rsp_fields.name_len = strlen(deviceName);
rsp_fields.name_is_complete = 1;
+ int rc;
rc = ble_gap_adv_set_fields(&fields);
ASSERT(rc == 0);
@@ -159,15 +183,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
switch (event->type) {
case BLE_GAP_EVENT_ADV_COMPLETE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
- NRF_LOG_INFO("reason=%d; status=%d", event->adv_complete.reason, event->connect.status);
+ NRF_LOG_INFO("reason=%d; status=%0X", event->adv_complete.reason, event->connect.status);
StartAdvertising();
break;
case BLE_GAP_EVENT_CONNECT:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
-
/* A new connection was established or a connection attempt failed. */
- NRF_LOG_INFO("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
+ NRF_LOG_INFO("Connect event : BLE_GAP_EVENT_CONNECT");
+ NRF_LOG_INFO("connection %s; status=%0X ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
@@ -186,10 +209,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break;
case BLE_GAP_EVENT_DISCONNECT:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_DISCONNECT");
+ /* Connection terminated; resume advertising. */
+ NRF_LOG_INFO("Disconnect event : BLE_GAP_EVENT_DISCONNECT");
NRF_LOG_INFO("disconnect reason=%d", event->disconnect.reason);
- /* Connection terminated; resume advertising. */
+ if (event->disconnect.conn.sec_state.bonded) {
+ PersistBond(event->disconnect.conn);
+ }
+
currentTimeClient.Reset();
alertNotificationClient.Reset();
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
@@ -199,18 +226,67 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
break;
case BLE_GAP_EVENT_CONN_UPDATE:
- NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONN_UPDATE");
/* The central has updated the connection parameters. */
- NRF_LOG_INFO("update status=%d ", event->conn_update.status);
+ NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE");
+ NRF_LOG_INFO("update status=%0X ", event->conn_update.status);
+ break;
+
+ case BLE_GAP_EVENT_CONN_UPDATE_REQ:
+ /* The central has requested updated connection parameters */
+ NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE_REQ");
+ NRF_LOG_INFO("update request : itvl_min=%d itvl_max=%d latency=%d supervision=%d",
+ event->conn_update_req.peer_params->itvl_min,
+ event->conn_update_req.peer_params->itvl_max,
+ event->conn_update_req.peer_params->latency,
+ event->conn_update_req.peer_params->supervision_timeout);
break;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
- NRF_LOG_INFO("encryption change event; status=%d ", event->enc_change.status);
+ NRF_LOG_INFO("Security event : BLE_GAP_EVENT_ENC_CHANGE");
+ NRF_LOG_INFO("encryption change event; status=%0X ", event->enc_change.status);
+
+ if (event->enc_change.status == 0) {
+ struct ble_gap_conn_desc desc;
+ ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+ if (desc.sec_state.bonded) {
+ PersistBond(desc);
+ }
+
+ NRF_LOG_INFO("new state: encrypted=%d authenticated=%d bonded=%d key_size=%d",
+ desc.sec_state.encrypted,
+ desc.sec_state.authenticated,
+ desc.sec_state.bonded,
+ desc.sec_state.key_size);
+ }
+ break;
+
+ case BLE_GAP_EVENT_PASSKEY_ACTION:
+ /* Authentication has been requested for this connection.
+ *
+ * BLE authentication is determined by the combination of I/O capabilities
+ * on the central and peripheral. When the peripheral is display only and
+ * the central has a keyboard and display then passkey auth is selected.
+ * When both the central and peripheral have displays and support yes/no
+ * buttons then numeric comparison is selected. We currently advertise
+ * display capability only so we only handle the "display" action here.
+ *
+ * Standards insist that the rand() PRNG be deterministic.
+ * Use the tinycrypt prng here since rand() is predictable.
+ */
+ NRF_LOG_INFO("Security event : BLE_GAP_EVENT_PASSKEY_ACTION");
+ if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
+ struct ble_sm_io pkey = {0};
+ pkey.action = event->passkey.params.action;
+ pkey.passkey = ble_ll_rand() % 1000000;
+ bleController.SetPairingKey(pkey.passkey);
+ systemTask.PushMessage(Pinetime::System::Messages::OnPairing);
+ ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+ }
break;
case BLE_GAP_EVENT_SUBSCRIBE:
- NRF_LOG_INFO("subscribe event; conn_handle=%d attr_handle=%d "
+ NRF_LOG_INFO("Subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=???\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
@@ -219,26 +295,24 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
event->subscribe.cur_notify,
event->subscribe.prev_indicate);
- if(event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
+ if (event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
- }
- else if(event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
+ } else if (event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
- }
- else if(event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
+ } else if (event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
}
break;
case BLE_GAP_EVENT_MTU:
- NRF_LOG_INFO("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
- event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
+ NRF_LOG_INFO("MTU Update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
break;
case BLE_GAP_EVENT_REPEAT_PAIRING: {
+ NRF_LOG_INFO("Pairing event : BLE_GAP_EVENT_REPEAT_PAIRING");
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
@@ -257,6 +331,8 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
case BLE_GAP_EVENT_NOTIFY_RX: {
/* Peer sent us a notification or indication. */
+ /* Attribute data is contained in event->notify_rx.attr_data. */
+ NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_RX");
size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
@@ -268,10 +344,17 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
alertNotificationClient.OnNotification(event);
} break;
- /* Attribute data is contained in event->notify_rx.attr_data. */
+
+ case BLE_GAP_EVENT_NOTIFY_TX:
+ NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_TX");
+ break;
+
+ case BLE_GAP_EVENT_IDENTITY_RESOLVED:
+ NRF_LOG_INFO("Identity event : BLE_GAP_EVENT_IDENTITY_RESOLVED");
+ break;
default:
- // NRF_LOG_INFO("Advertising event : %d", event->type);
+ NRF_LOG_INFO("UNHANDLED GAP event : %d", event->type);
break;
}
return 0;
@@ -292,3 +375,82 @@ void NimbleController::NotifyBatteryLevel(uint8_t level) {
batteryInformationService.NotifyBatteryLevel(connectionHandle, level);
}
}
+
+void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
+ union ble_store_key key;
+ union ble_store_value our_sec, peer_sec, peer_cccd_set[MYNEWT_VAL(BLE_STORE_MAX_CCCDS)] = {0};
+ int rc;
+
+ memset(&key, 0, sizeof key);
+ memset(&our_sec, 0, sizeof our_sec);
+ key.sec.peer_addr = desc.peer_id_addr;
+ rc = ble_store_read_our_sec(&key.sec, &our_sec.sec);
+
+ if (memcmp(&our_sec.sec, &bondId, sizeof bondId) == 0) {
+ return;
+ }
+
+ memcpy(&bondId, &our_sec.sec, sizeof bondId);
+
+ memset(&key, 0, sizeof key);
+ memset(&peer_sec, 0, sizeof peer_sec);
+ key.sec.peer_addr = desc.peer_id_addr;
+ rc += ble_store_read_peer_sec(&key.sec, &peer_sec.sec);
+
+ if (rc == 0) {
+ memset(&key, 0, sizeof key);
+ key.cccd.peer_addr = desc.peer_id_addr;
+ int peer_count = 0;
+ ble_store_util_count(BLE_STORE_OBJ_TYPE_CCCD, &peer_count);
+ for (int i = 0; i < peer_count; i++) {
+ key.cccd.idx = peer_count;
+ ble_store_read_cccd(&key.cccd, &peer_cccd_set[i].cccd);
+ }
+
+ /* Wakeup Spi and SpiNorFlash before accessing the file system
+ * This should be fixed in the FS driver
+ */
+ systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
+ systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
+ vTaskDelay(10);
+
+ lfs_file_t file_p;
+
+ rc = fs.FileOpen(&file_p, "/bond.dat", LFS_O_WRONLY | LFS_O_CREAT);
+ if (rc == 0) {
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&our_sec.sec), sizeof our_sec);
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&peer_sec.sec), sizeof peer_sec);
+ fs.FileWrite(&file_p, reinterpret_cast<const uint8_t*>(&peer_count), 1);
+ for (int i = 0; i < peer_count; i++) {
+ fs.FileWrite(&file_p, reinterpret_cast<uint8_t*>(&peer_cccd_set[i].cccd), sizeof(struct ble_store_value_cccd));
+ }
+ fs.FileClose(&file_p);
+ }
+ systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
+ }
+}
+
+void NimbleController::RestoreBond() {
+ lfs_file_t file_p;
+ union ble_store_value sec, cccd;
+ uint8_t peer_count = 0;
+
+ if (fs.FileOpen(&file_p, "/bond.dat", LFS_O_RDONLY) == 0) {
+ memset(&sec, 0, sizeof sec);
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&sec.sec), sizeof sec);
+ ble_store_write_our_sec(&sec.sec);
+
+ memset(&sec, 0, sizeof sec);
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&sec.sec), sizeof sec);
+ ble_store_write_peer_sec(&sec.sec);
+
+ fs.FileRead(&file_p, &peer_count, 1);
+ for (int i = 0; i < peer_count; i++) {
+ fs.FileRead(&file_p, reinterpret_cast<uint8_t*>(&cccd.cccd), sizeof(struct ble_store_value_cccd));
+ ble_store_write_cccd(&cccd.cccd);
+ }
+
+ fs.FileClose(&file_p);
+ fs.FileDelete("/bond.dat");
+ }
+}
diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index 76f89ba8..2b300e63 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -7,19 +7,22 @@
#include <host/ble_gap.h>
#undef max
#undef min
-#include "AlertNotificationClient.h"
-#include "AlertNotificationService.h"
-#include "BatteryInformationService.h"
-#include "CurrentTimeClient.h"
-#include "CurrentTimeService.h"
-#include "DeviceInformationService.h"
-#include "DfuService.h"
-#include "ImmediateAlertService.h"
-#include "MusicService.h"
-#include "NavigationService.h"
-#include "ServiceDiscovery.h"
-#include "HeartRateService.h"
-#include "MotionService.h"
+#include "components/ble/AlertNotificationClient.h"
+#include "components/ble/AlertNotificationService.h"
+#include "components/ble/BatteryInformationService.h"
+#include "components/ble/CurrentTimeClient.h"
+#include "components/ble/CurrentTimeService.h"
+#include "components/ble/DeviceInformationService.h"
+#include "components/ble/DfuService.h"
+#include "components/ble/HeartRateService.h"
+#include "components/ble/ImmediateAlertService.h"
+#include "components/ble/MusicService.h"
+#include "components/ble/NavigationService.h"
+#include "components/ble/ServiceDiscovery.h"
+#include "components/ble/MotionService.h"
+#include "components/ble/weather/WeatherService.h"
+#include "components/fs/FS.h"
+#include "components/ble/FSService.h"
namespace Pinetime {
namespace Drivers {
@@ -45,7 +48,8 @@ namespace Pinetime {
Controllers::Battery& batteryController,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController,
- Controllers::MotionController& motionController);
+ Controllers::MotionController& motionController,
+ Pinetime::Controllers::FS& fs);
void Init();
void StartAdvertising();
int OnGAPEvent(ble_gap_event* event);
@@ -70,6 +74,9 @@ namespace Pinetime {
Pinetime::Controllers::AlertNotificationService& alertService() {
return anService;
};
+ Pinetime::Controllers::WeatherService& weather() {
+ return weatherService;
+ };
uint16_t connHandle();
void NotifyBatteryLevel(uint8_t level);
@@ -79,12 +86,16 @@ namespace Pinetime {
}
private:
+ void PersistBond(struct ble_gap_conn_desc& desc);
+ void RestoreBond();
+
static constexpr const char* deviceName = "InfiniTime";
Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController;
DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
+ Pinetime::Controllers::FS& fs;
Pinetime::Controllers::DfuService dfuService;
DeviceInformationService deviceInformationService;
@@ -93,23 +104,25 @@ namespace Pinetime {
AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService;
MusicService musicService;
+ WeatherService weatherService;
NavigationService navService;
BatteryInformationService batteryInformationService;
ImmediateAlertService immediateAlertService;
HeartRateService heartRateService;
MotionService motionService;
+ FSService fsService;
+ ServiceDiscovery serviceDiscovery;
- uint8_t addrType; // 1 = Random, 0 = PUBLIC
+ uint8_t addrType;
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
uint8_t fastAdvCount = 0;
+ uint8_t bondId[16] = {0};
ble_uuid128_t dfuServiceUuid {
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
-
- ServiceDiscovery serviceDiscovery;
};
- static NimbleController* nptr;
+ static NimbleController* nptr;
}
}
diff --git a/src/components/ble/NotificationManager.cpp b/src/components/ble/NotificationManager.cpp
index 7ffed300..ec99c4ed 100644
--- a/src/components/ble/NotificationManager.cpp
+++ b/src/components/ble/NotificationManager.cpp
@@ -1,4 +1,4 @@
-#include "NotificationManager.h"
+#include "components/ble/NotificationManager.h"
#include <cstring>
#include <algorithm>
diff --git a/src/components/ble/ServiceDiscovery.cpp b/src/components/ble/ServiceDiscovery.cpp
index b36b241c..03bcfeb4 100644
--- a/src/components/ble/ServiceDiscovery.cpp
+++ b/src/components/ble/ServiceDiscovery.cpp
@@ -1,6 +1,6 @@
-#include "ServiceDiscovery.h"
+#include "components/ble/ServiceDiscovery.h"
#include <libraries/log/nrf_log.h>
-#include "BleClient.h"
+#include "components/ble/BleClient.h"
using namespace Pinetime::Controllers;
diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h
new file mode 100644
index 00000000..613d5acb
--- /dev/null
+++ b/src/components/ble/weather/WeatherData.h
@@ -0,0 +1,385 @@
+/* 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}
+ *
+ * How to upload events to the timeline?
+ *
+ * All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
+ *
+ * All payloads have a mandatory header part and the dynamic part that
+ * depends on the event type specified in the header. If you don't,
+ * you'll get an error returned. Data is relatively well-validated,
+ * so keep in the bounds of the data types given.
+ *
+ * Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
+ * Mind the MTU.
+ *
+ * How to debug?
+ *
+ * There's a Screen that you can compile into your firmware that shows currently valid events.
+ * You can adapt that to display something else. That part right now is very much work in progress
+ * because the exact requirements are not yet known.
+ *
+ *
+ * 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,
+ /** Tiny, 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,
+ /** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
+ Precipitation = 8,
+ Length
+ };
+
+ /**
+ * 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,
+ /** It's raining down ash, e.g. from a volcano */
+ Ash = 10,
+ Length
+ };
+
+ /**
+ * 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,
+ Length
+ };
+
+ /**
+ * 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,
+ Length
+ };
+
+ /**
+ * Events have types
+ * then they're easier to parse after sending them over the air
+ */
+ enum class eventtype : uint8_t {
+ /** @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,
+ /** @see humidity */
+ Humidity = 9,
+ Length
+ };
+
+ /**
+ * Valid event query
+ *
+ * NOTE: Not currently available, until needs are better known
+ */
+ class ValidEventQuery {
+ public:
+ static constexpr controlcodes code = controlcodes::HasValidEvent;
+ eventtype eventType;
+ };
+
+ /** The header used for further parsing */
+ class TimelineHeader {
+ public:
+ /**
+ * UNIX timestamp
+ * TODO: This is currently WITH A TIMEZONE OFFSET!
+ * Please send events with the timestamp offset by the timezone.
+ **/
+ 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 of precipitation */
+ obscurationtype type;
+ /**
+ * Visibility distance in meters
+ * 65535 is reserved for unspecified
+ */
+ uint16_t amount;
+ };
+
+ /** Specifies how precipitation is stored */
+ class Precipitation : public TimelineHeader {
+ public:
+ /** Type of precipitation */
+ precipitationtype type;
+ /**
+ * How much is it going to rain? In millimeters
+ * 255 is reserved for unspecified
+ **/
+ 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, picodegrees 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)
+ * -32768 is reserved for "no data"
+ */
+ int16_t temperature;
+ /**
+ * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
+ * -32768 is reserved for "no data"
+ */
+ 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;
+ };
+ };
+ }
+}
diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp
new file mode 100644
index 00000000..23f53b74
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.cpp
@@ -0,0 +1,604 @@
+/* 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 connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(connHandle, attrHandle, ctxt);
+}
+
+namespace Pinetime {
+ namespace Controllers {
+ WeatherService::WeatherService(System::SystemTask& system, DateTime& dateTimeController)
+ : system(system), dateTimeController(dateTimeController) {
+ nullHeader = &nullTimelineheader;
+ nullTimelineheader->timestamp = 0;
+ }
+
+ 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 connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt) {
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ if (packetLen <= 0) {
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ // Decode
+ QCBORDecodeContext decodeContext;
+ UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+
+ QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
+ // KINDLY provide us a fixed-length map
+ QCBORDecode_EnterMap(&decodeContext, nullptr);
+ // Always encodes to the smallest number of bytes based on the value
+ int64_t tmpTimestamp = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+
+ switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
+ case WeatherData::eventtype::AirQuality: {
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 4294967295) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(airquality))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Obscuration: {
+ std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
+ obscuration->timestamp = tmpTimestamp;
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 65535) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(obscuration))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Precipitation: {
+ std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
+ precipitation->timestamp = tmpTimestamp;
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(precipitation))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Wind: {
+ std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
+ wind->timestamp = tmpTimestamp;
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpMax = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
+ if (tmpMax < 0 || tmpMax > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpDMin = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
+ if (tmpDMin < 0 || tmpDMin > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ int64_t tmpDMax = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
+ if (tmpDMax < 0 || tmpDMax > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(wind))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Temperature: {
+ std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
+ temperature->timestamp = tmpTimestamp;
+ 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);
+ 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;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
+ if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ temperature->dewPoint =
+ static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(temperature))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Special: {
+ std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
+ special->timestamp = tmpTimestamp;
+ special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ special->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ special->type = static_cast<WeatherData::specialtype>(tmpType);
+
+ if (!AddEventToTimeline(std::move(special))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Pressure: {
+ std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
+ pressure->timestamp = tmpTimestamp;
+ pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ pressure->expires = tmpExpires;
+
+ int64_t tmpPressure = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
+ if (tmpPressure < 0 || tmpPressure >= 65535) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ if (!AddEventToTimeline(std::move(pressure))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Location: {
+ std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
+ location->timestamp = tmpTimestamp;
+ 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);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
+
+ int64_t tmpAltitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
+ if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->altitude = static_cast<int16_t>(tmpAltitude);
+
+ int64_t tmpLatitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
+ if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->latitude = static_cast<int32_t>(tmpLatitude);
+
+ int64_t tmpLongitude = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
+ if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ location->latitude = static_cast<int32_t>(tmpLongitude);
+
+ if (!AddEventToTimeline(std::move(location))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Clouds: {
+ std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
+ clouds->timestamp = tmpTimestamp;
+ clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ clouds->expires = tmpExpires;
+
+ int64_t tmpAmount = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ if (tmpAmount < 0 || tmpAmount > 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ clouds->amount = static_cast<uint8_t>(tmpAmount);
+
+ if (!AddEventToTimeline(std::move(clouds))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ case WeatherData::eventtype::Humidity: {
+ std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
+ humidity->timestamp = tmpTimestamp;
+ humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
+ humidity->expires = tmpExpires;
+
+ int64_t tmpType = 0;
+ QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
+ if (tmpType < 0 || tmpType >= 255) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ humidity->humidity = static_cast<uint8_t>(tmpType);
+
+ if (!AddEventToTimeline(std::move(humidity))) {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ break;
+ }
+ default: {
+ CleanUpQcbor(&decodeContext);
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ }
+ }
+
+ QCBORDecode_ExitMap(&decodeContext);
+ GetTimelineLength();
+ TidyTimeline();
+
+ if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
+ return BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
+ // Encode
+ uint8_t buffer[64];
+ QCBOREncodeContext encodeContext;
+ /* TODO: This is very much still a test endpoint
+ * it needs a characteristic UUID check
+ * and actual implementations that show
+ * what actually has to be read.
+ * WARN: Consider commands not part of the API for now!
+ */
+ 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;
+ }
+
+ std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
+ }
+
+ std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
+ }
+ }
+
+ return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
+ }
+
+ 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 && IsEventStillValid(header, currentTimestamp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void WeatherService::TidyTimeline() {
+ uint64_t timeCurrent = GetCurrentUnixTimestamp();
+ timeline.erase(std::remove_if(std::begin(timeline),
+ std::end(timeline),
+ [&](std::unique_ptr<WeatherData::TimelineHeader> const& header) {
+ return !IsEventStillValid(header, 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;
+ }
+
+ bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp) {
+ // Not getting timestamp in isEventStillValid for more speed
+ return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
+ }
+
+ uint64_t WeatherService::GetCurrentUnixTimestamp() const {
+ return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
+ }
+
+ int16_t WeatherService::GetTodayMinTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result > temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is higher than the lowest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+ int16_t WeatherService::GetTodayMaxTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result < temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is lower than the highest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+ void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
+ QCBORDecode_ExitMap(decodeContext);
+ QCBORDecode_Finish(decodeContext);
+ }
+ }
+}
diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h
new file mode 100644
index 00000000..eca70cbd
--- /dev/null
+++ b/src/components/ble/weather/WeatherService.h
@@ -0,0 +1,172 @@
+/* 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 "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "components/datetime/DateTimeController.h"
+
+int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, 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 connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt);
+
+ /*
+ * Helper functions for quick access to currently valid data
+ */
+ std::unique_ptr<WeatherData::Location>& GetCurrentLocation();
+ std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
+ std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
+ std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
+ std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
+ std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
+ std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
+ std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
+ std::unique_ptr<WeatherData::AirQuality>& GetCurrentQuality();
+
+ /**
+ * Searches for the current day's maximum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMaxTemp() const;
+ /**
+ * Searches for the current day's minimum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMinTemp() 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
+ */
+ bool HasTimelineEventOfType(WeatherData::eventtype type) const;
+
+ private:
+ // 00040000-78fc-48fe-8e23-433b3a1942d0
+ static constexpr ble_uuid128_t BaseUuid() {
+ return CharUuid(0x00, 0x00);
+ }
+
+ // 0004yyxx-78fc-48fe-8e23-433b3a1942d0
+ static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+ return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
+ .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
+ }
+
+ ble_uuid128_t weatherUuid {BaseUuid()};
+
+ /**
+ * Just write timeline data here.
+ *
+ * See {@link WeatherData.h} for more information.
+ */
+ ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
+ /**
+ * This doesn't take timeline data, provides some control over it.
+ *
+ * NOTE: Currently not supported. Companion app implementer feedback required.
+ * There's very little point in solidifying an API before we know the needs.
+ */
+ ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
+
+ const struct ble_gatt_chr_def characteristicDefinition[3] = {
+ {.uuid = &weatherDataCharUuid.u,
+ .access_cb = WeatherCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE,
+ .val_handle = &eventHandle},
+ {.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
+ {nullptr}};
+ const struct ble_gatt_svc_def serviceDefinition[2] = {
+ {.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<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
+ std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
+ std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
+
+ /**
+ * Cleans up the timeline of expired events
+ */
+ void TidyTimeline();
+
+ /**
+ * Compares two timeline events
+ */
+ static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
+ const std::unique_ptr<WeatherData::TimelineHeader>& second);
+
+ /**
+ * Returns current UNIX timestamp
+ */
+ uint64_t GetCurrentUnixTimestamp() const;
+
+ /**
+ * Checks if the event hasn't gone past and expired
+ *
+ * @param header timeline event to check
+ * @param currentTimestamp what's the time right now
+ * @return if the event is valid
+ */
+ static bool IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp);
+
+ /**
+ * This is a helper function that closes a QCBOR map and decoding context cleanly
+ */
+ void CleanUpQcbor(QCBORDecodeContext* decodeContext);
+ };
+ }
+}