summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorAvamander <avamander@gmail.com>2020-10-02 21:44:27 +0300
committerAvamander <avamander@gmail.com>2020-10-02 21:44:27 +0300
commit40a643d203d2d21834dd2b35d83419a56a3939b6 (patch)
tree522947ffe2e7c10f6e8c45a6e7d1e58d3521829b /src/components
parent455d8319e4af521de4e24cfa423a4c06c4378a8d (diff)
Renamed Components/ to components/
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Battery/BatteryController.cpp48
-rw-r--r--src/components/Battery/BatteryController.h27
-rw-r--r--src/components/Ble/AlertNotificationClient.cpp145
-rw-r--r--src/components/Ble/AlertNotificationClient.h81
-rw-r--r--src/components/Ble/AlertNotificationService.cpp80
-rw-r--r--src/components/Ble/AlertNotificationService.h39
-rw-r--r--src/components/Ble/BatteryInformationService.cpp62
-rw-r--r--src/components/Ble/BatteryInformationService.h40
-rw-r--r--src/components/Ble/BleController.cpp31
-rw-r--r--src/components/Ble/BleController.h45
-rw-r--r--src/components/Ble/CurrentTimeClient.cpp77
-rw-r--r--src/components/Ble/CurrentTimeClient.h55
-rw-r--r--src/components/Ble/CurrentTimeService.cpp86
-rw-r--r--src/components/Ble/CurrentTimeService.h48
-rw-r--r--src/components/Ble/DeviceInformationService.cpp116
-rw-r--r--src/components/Ble/DeviceInformationService.h76
-rw-r--r--src/components/Ble/DfuService.cpp440
-rw-r--r--src/components/Ble/DfuService.h161
-rw-r--r--src/components/Ble/ImmediateAlertService.cpp76
-rw-r--r--src/components/Ble/ImmediateAlertService.h46
-rw-r--r--src/components/Ble/MusicService.cpp129
-rw-r--r--src/components/Ble/MusicService.h92
-rw-r--r--src/components/Ble/NimbleController.cpp337
-rw-r--r--src/components/Ble/NimbleController.h75
-rw-r--r--src/components/Ble/NotificationManager.cpp30
-rw-r--r--src/components/Ble/NotificationManager.h29
-rw-r--r--src/components/Brightness/BrightnessController.cpp70
-rw-r--r--src/components/Brightness/BrightnessController.h28
-rw-r--r--src/components/DateTime/DateTimeController.cpp66
-rw-r--r--src/components/DateTime/DateTimeController.h39
-rw-r--r--src/components/FirmwareValidator/FirmwareValidator.cpp20
-rw-r--r--src/components/FirmwareValidator/FirmwareValidator.h18
-rw-r--r--src/components/Gfx/Gfx.cpp207
-rw-r--r--src/components/Gfx/Gfx.h60
34 files changed, 2979 insertions, 0 deletions
diff --git a/src/components/Battery/BatteryController.cpp b/src/components/Battery/BatteryController.cpp
new file mode 100644
index 00000000..571efae6
--- /dev/null
+++ b/src/components/Battery/BatteryController.cpp
@@ -0,0 +1,48 @@
+#include <drivers/include/nrfx_saadc.h>
+#include <hal/nrf_gpio.h>
+#include <libraries/log/nrf_log.h>
+#include <algorithm>
+#include "BatteryController.h"
+
+using namespace Pinetime::Controllers;
+
+void Battery::Init() {
+ nrf_gpio_cfg_input(chargingPin, (nrf_gpio_pin_pull_t)GPIO_PIN_CNF_PULL_Pullup);
+ nrf_gpio_cfg_input(powerPresentPin, (nrf_gpio_pin_pull_t)GPIO_PIN_CNF_PULL_Pullup);
+
+ nrfx_saadc_config_t adcConfig = NRFX_SAADC_DEFAULT_CONFIG;
+ nrfx_saadc_init(&adcConfig, SaadcEventHandler);
+ nrf_saadc_channel_config_t adcChannelConfig = {
+ .resistor_p = NRF_SAADC_RESISTOR_DISABLED,
+ .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
+ .gain = NRF_SAADC_GAIN1_5,
+ .reference = NRF_SAADC_REFERENCE_INTERNAL,
+ .acq_time = NRF_SAADC_ACQTIME_3US,
+ .mode = NRF_SAADC_MODE_SINGLE_ENDED,
+ .burst = NRF_SAADC_BURST_DISABLED,
+ .pin_p = batteryVoltageAdcInput,
+ .pin_n = NRF_SAADC_INPUT_DISABLED
+ };
+ nrfx_saadc_channel_init(0, &adcChannelConfig);
+}
+
+void Battery::Update() {
+ isCharging = !nrf_gpio_pin_read(chargingPin);
+ isPowerPresent = !nrf_gpio_pin_read(powerPresentPin);
+
+ nrf_saadc_value_t value = 0;
+ nrfx_saadc_sample_convert(0, &value);
+
+ // see https://forum.pine64.org/showthread.php?tid=8147
+ voltage = (value * 2.0f) / (1024/3.0f);
+ percentRemaining = ((voltage - 3.55f)*100.0f)*3.9f;
+ percentRemaining = std::max(percentRemaining, 0.0f);
+ percentRemaining = std::min(percentRemaining, 100.0f);
+
+// NRF_LOG_INFO("BATTERY " NRF_LOG_FLOAT_MARKER " %% - " NRF_LOG_FLOAT_MARKER " v", NRF_LOG_FLOAT(percentRemaining), NRF_LOG_FLOAT(voltage));
+// NRF_LOG_INFO("POWER Charging : %d - Power : %d", isCharging, isPowerPresent);
+}
+
+void Battery::SaadcEventHandler(nrfx_saadc_evt_t const * event) {
+
+} \ No newline at end of file
diff --git a/src/components/Battery/BatteryController.h b/src/components/Battery/BatteryController.h
new file mode 100644
index 00000000..f07648a9
--- /dev/null
+++ b/src/components/Battery/BatteryController.h
@@ -0,0 +1,27 @@
+#pragma once
+#include <drivers/include/nrfx_saadc.h>
+
+
+namespace Pinetime {
+ namespace Controllers {
+ class Battery {
+ public:
+ void Init();
+ void Update();
+ float PercentRemaining() const { return percentRemaining; }
+ float Voltage() const { return voltage; }
+ bool IsCharging() const { return isCharging; }
+ bool IsPowerPresent() const { return isPowerPresent; }
+
+ private:
+ static constexpr uint32_t chargingPin = 12;
+ static constexpr uint32_t powerPresentPin = 19;
+ static constexpr nrf_saadc_input_t batteryVoltageAdcInput = NRF_SAADC_INPUT_AIN7;
+ static void SaadcEventHandler(nrfx_saadc_evt_t const * p_event);
+ float percentRemaining = 0.0f;
+ float voltage = 0.0f;
+ bool isCharging = false;
+ bool isPowerPresent = false;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/AlertNotificationClient.cpp b/src/components/Ble/AlertNotificationClient.cpp
new file mode 100644
index 00000000..3e4b495f
--- /dev/null
+++ b/src/components/Ble/AlertNotificationClient.cpp
@@ -0,0 +1,145 @@
+#include <SystemTask/SystemTask.h>
+#include "NotificationManager.h"
+
+#include "AlertNotificationClient.h"
+
+
+using namespace Pinetime::Controllers;
+constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
+
+constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
+constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid ;
+constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
+constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
+constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
+
+int Pinetime::Controllers::NewAlertSubcribeCallback(uint16_t conn_handle,
+ const struct ble_gatt_error *error,
+ struct ble_gatt_attr *attr,
+ void *arg) {
+ auto client = static_cast<AlertNotificationClient*>(arg);
+ return client->OnNewAlertSubcribe(conn_handle, error, attr);
+}
+
+AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager) :
+ systemTask{systemTask}, notificationManager{notificationManager}{
+
+}
+
+bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
+ if(service == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS Discovery complete");
+ return true;
+ }
+
+ if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ansServiceUuid), &service->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS discovered : 0x%x", service->start_handle);
+ ansStartHandle = service->start_handle;
+ ansEndHandle = service->end_handle;
+ isDiscovered = true;
+ }
+ return false;
+}
+
+int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic) {
+ if(error->status != 0 && error->status != BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS Characteristic discovery ERROR");
+ return 0;
+ }
+
+ if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS Characteristic discovery complete");
+ } else {
+ if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedNewAlertCategoryUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : supportedNewAlertCategoryUuid");
+ supportedNewAlertCategoryHandle = characteristic->val_handle;
+ } else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedUnreadAlertCategoryUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : supportedUnreadAlertCategoryUuid");
+ supportedUnreadAlertCategoryHandle = characteristic->val_handle;
+ } else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
+ newAlertHandle = characteristic->val_handle;
+ newAlertDefHandle = characteristic->def_handle;
+ } else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&unreadAlertStatusUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : unreadAlertStatusUuid");
+ unreadAlertStatusHandle = characteristic->val_handle;
+ } else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&controlPointUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
+ controlPointHandle = characteristic->val_handle;
+ }else
+ NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
+ }
+ return 0;
+}
+
+int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error,
+ ble_gatt_attr *attribute) {
+ if(error->status == 0) {
+ NRF_LOG_INFO("ANS New alert subscribe OK");
+ } else {
+ NRF_LOG_INFO("ANS New alert subscribe ERROR");
+ }
+
+ return 0;
+}
+
+int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
+ uint16_t characteristicValueHandle,
+ const ble_gatt_dsc *descriptor) {
+ if(error->status == 0) {
+ if(characteristicValueHandle == newAlertHandle && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &descriptor->uuid.u)) {
+ if(newAlertDescriptorHandle == 0) {
+ NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
+ newAlertDescriptorHandle = descriptor->handle;
+ uint8_t value[2];
+ value[0] = 1;
+ value[1] = 0;
+ ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
+ }
+ }
+ }
+ return 0;
+}
+
+void AlertNotificationClient::OnNotification(ble_gap_event *event) {
+ if(event->notify_rx.attr_handle == newAlertHandle) {
+ // TODO implement this with more memory safety (and constexpr)
+ static const size_t maxBufferSize{21};
+ static const size_t maxMessageSize{18};
+ size_t bufferSize = min(OS_MBUF_PKTLEN(event->notify_rx.om), maxBufferSize);
+
+ uint8_t data[bufferSize];
+ os_mbuf_copydata(event->notify_rx.om, 0, bufferSize, data);
+
+ char *s = (char *) &data[3];
+ auto messageSize = min(maxMessageSize, (bufferSize-3));
+
+ for (uint i = 0; i < messageSize-1; i++) {
+ if (s[i] == 0x00) {
+ s[i] = 0x0A;
+ }
+ }
+ s[messageSize-1] = '\0';
+
+ notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
+ }
+}
+
+bool AlertNotificationClient::IsDiscovered() const {
+ return isDiscovered;
+}
+
+uint16_t AlertNotificationClient::StartHandle() const {
+ return ansStartHandle;
+}
+
+uint16_t AlertNotificationClient::EndHandle() const {
+ return ansEndHandle;
+}
+
+uint16_t AlertNotificationClient::NewAlerthandle() const {
+ return newAlertHandle;
+}
diff --git a/src/components/Ble/AlertNotificationClient.h b/src/components/Ble/AlertNotificationClient.h
new file mode 100644
index 00000000..ca4f4e94
--- /dev/null
+++ b/src/components/Ble/AlertNotificationClient.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <cstdint>
+#include <array>
+#include <host/ble_gap.h>
+
+
+namespace Pinetime {
+ namespace Controllers {
+ int NewAlertSubcribeCallback(uint16_t conn_handle,
+ const struct ble_gatt_error *error,
+ struct ble_gatt_attr *attr,
+ void *arg);
+
+ class AlertNotificationClient {
+ public:
+ explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager &notificationManager);
+
+ bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
+ int OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic);
+ int OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
+ int OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
+ uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
+ void OnNotification(ble_gap_event *event);
+ bool IsDiscovered() const;
+ uint16_t StartHandle() const;
+ uint16_t EndHandle() const;
+
+ static constexpr const ble_uuid16_t &Uuid() { return ansServiceUuid; }
+
+ uint16_t NewAlerthandle() const;
+ private:
+ static constexpr uint16_t ansServiceId{0x1811};
+ static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
+ static constexpr uint16_t supportedUnreadAlertCategoryId = 0x2a48;
+ static constexpr uint16_t newAlertId = 0x2a46;
+ static constexpr uint16_t unreadAlertStatusId = 0x2a45;
+ static constexpr uint16_t controlPointId = 0x2a44;
+
+ static constexpr ble_uuid16_t ansServiceUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = ansServiceId
+ };
+ static constexpr ble_uuid16_t supportedNewAlertCategoryUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = supportedNewAlertCategoryId
+ };
+ static constexpr ble_uuid16_t supportedUnreadAlertCategoryUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = supportedUnreadAlertCategoryId
+ };
+ static constexpr ble_uuid16_t newAlertUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = newAlertId
+ };
+ static constexpr ble_uuid16_t unreadAlertStatusUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = unreadAlertStatusId
+ };
+ static constexpr ble_uuid16_t controlPointUuid{
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = controlPointId
+ };
+
+ uint16_t ansStartHandle;
+ uint16_t ansEndHandle;
+ uint16_t supportedNewAlertCategoryHandle;
+ uint16_t supportedUnreadAlertCategoryHandle;
+ uint16_t newAlertHandle;
+ uint16_t newAlertDescriptorHandle = 0;
+ uint16_t newAlertDefHandle;
+ uint16_t unreadAlertStatusHandle;
+ uint16_t controlPointHandle;
+ bool isDiscovered = false;
+ Pinetime::System::SystemTask &systemTask;
+ Pinetime::Controllers::NotificationManager &notificationManager;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/AlertNotificationService.cpp b/src/components/Ble/AlertNotificationService.cpp
new file mode 100644
index 00000000..ce2f7dd7
--- /dev/null
+++ b/src/components/Ble/AlertNotificationService.cpp
@@ -0,0 +1,80 @@
+
+#include <hal/nrf_rtc.h>
+#include "NotificationManager.h"
+#include <SystemTask/SystemTask.h>
+
+#include "AlertNotificationService.h"
+#include <cstring>
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t AlertNotificationService::ansUuid;
+constexpr ble_uuid16_t AlertNotificationService::ansCharUuid;
+
+
+int AlertNotificationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto anService = static_cast<AlertNotificationService*>(arg);
+ return anService->OnAlert(conn_handle, attr_handle, ctxt);
+}
+
+void AlertNotificationService::Init() {
+ int res;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+AlertNotificationService::AlertNotificationService ( System::SystemTask& systemTask, NotificationManager& notificationManager )
+ : characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &ansCharUuid,
+ .access_cb = AlertNotificationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE
+ },
+ {
+ 0
+ }
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &ansUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ }, m_systemTask{systemTask}, m_notificationManager{notificationManager} {
+}
+
+int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt) {
+
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ // TODO implement this with more memory safety (and constexpr)
+ static const size_t maxBufferSize{21};
+ static const size_t maxMessageSize{18};
+ size_t bufferSize = min(OS_MBUF_PKTLEN(ctxt->om), maxBufferSize);
+
+ uint8_t data[bufferSize];
+ os_mbuf_copydata(ctxt->om, 0, bufferSize, data);
+
+ char *s = (char *) &data[3];
+ auto messageSize = min(maxMessageSize, (bufferSize-3));
+
+ for (uint i = 0; i < messageSize-1; i++) {
+ if (s[i] == 0x00) {
+ s[i] = 0x0A;
+ }
+ }
+ s[messageSize-1] = '\0';
+
+ m_notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
+ m_systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
+ }
+ return 0;
+}
diff --git a/src/components/Ble/AlertNotificationService.h b/src/components/Ble/AlertNotificationService.h
new file mode 100644
index 00000000..53cb44cc
--- /dev/null
+++ b/src/components/Ble/AlertNotificationService.h
@@ -0,0 +1,39 @@
+#pragma once
+#include <cstdint>
+#include <array>
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace Controllers {
+ class AlertNotificationService {
+ public:
+ AlertNotificationService(Pinetime::System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager &notificationManager);
+ void Init();
+
+ int OnAlert(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt);
+
+
+ private:
+ static constexpr uint16_t ansId {0x1811};
+ static constexpr uint16_t ansCharId {0x2a46};
+
+ static constexpr ble_uuid16_t ansUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = ansId
+ };
+
+ static constexpr ble_uuid16_t ansCharUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = ansCharId
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[2];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ Pinetime::System::SystemTask &m_systemTask;
+ NotificationManager &m_notificationManager;
+ };
+ }
+}
diff --git a/src/components/Ble/BatteryInformationService.cpp b/src/components/Ble/BatteryInformationService.cpp
new file mode 100644
index 00000000..c86830b8
--- /dev/null
+++ b/src/components/Ble/BatteryInformationService.cpp
@@ -0,0 +1,62 @@
+#include "BatteryInformationService.h"
+#include "../Battery/BatteryController.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t BatteryInformationService::batteryInformationServiceUuid;
+constexpr ble_uuid16_t BatteryInformationService::batteryLevelUuid;
+
+
+
+int BatteryInformationServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto* batteryInformationService = static_cast<BatteryInformationService*>(arg);
+ return batteryInformationService->OnBatteryServiceRequested(conn_handle, attr_handle, ctxt);
+}
+
+BatteryInformationService::BatteryInformationService(Controllers::Battery& batteryController) :
+ batteryController{batteryController},
+ characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &batteryLevelUuid,
+ .access_cb = BatteryInformationServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ .val_handle = &batteryLevelHandle
+ },
+ {
+ 0
+ }
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &batteryInformationServiceUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ }{
+
+}
+
+void BatteryInformationService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int BatteryInformationService::OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle,
+ ble_gatt_access_ctxt *context) {
+ if(attributeHandle == batteryLevelHandle) {
+ NRF_LOG_INFO("BATTERY : handle = %d", batteryLevelHandle);
+ static uint8_t batteryValue = batteryController.PercentRemaining();
+ int res = os_mbuf_append(context->om, &batteryValue, 1);
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/src/components/Ble/BatteryInformationService.h b/src/components/Ble/BatteryInformationService.h
new file mode 100644
index 00000000..74b2222c
--- /dev/null
+++ b/src/components/Ble/BatteryInformationService.h
@@ -0,0 +1,40 @@
+#pragma once
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class Battery;
+ class BatteryInformationService {
+ public:
+ BatteryInformationService(Controllers::Battery& batteryController);
+ void Init();
+
+ int
+ OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
+
+ private:
+ Controllers::Battery& batteryController;
+ static constexpr uint16_t batteryInformationServiceId {0x180F};
+ static constexpr uint16_t batteryLevelId {0x2A19};
+
+ static constexpr ble_uuid16_t batteryInformationServiceUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = batteryInformationServiceId
+ };
+
+ static constexpr ble_uuid16_t batteryLevelUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = batteryLevelId
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t batteryLevelHandle;
+
+ };
+ }
+}
diff --git a/src/components/Ble/BleController.cpp b/src/components/Ble/BleController.cpp
new file mode 100644
index 00000000..2b396e12
--- /dev/null
+++ b/src/components/Ble/BleController.cpp
@@ -0,0 +1,31 @@
+#include <cstring>
+#include <cstdlib>
+#include "BleController.h"
+
+using namespace Pinetime::Controllers;
+
+void Ble::Connect() {
+ isConnected = true;
+}
+
+void Ble::Disconnect() {
+ isConnected = false;
+}
+
+void Ble::StartFirmwareUpdate() {
+ isFirmwareUpdating = true;
+}
+
+void Ble::StopFirmwareUpdate() {
+ isFirmwareUpdating = false;
+}
+
+void Ble::FirmwareUpdateTotalBytes(uint32_t totalBytes) {
+ firmwareUpdateTotalBytes = totalBytes;
+}
+
+void Ble::FirmwareUpdateCurrentBytes(uint32_t currentBytes) {
+ firmwareUpdateCurrentBytes = currentBytes;
+}
+
+
diff --git a/src/components/Ble/BleController.h b/src/components/Ble/BleController.h
new file mode 100644
index 00000000..3f52ea25
--- /dev/null
+++ b/src/components/Ble/BleController.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <FreeRTOS.h>
+#include <queue.h>
+#include <array>
+
+namespace Pinetime {
+ namespace Controllers {
+ class Ble {
+ public:
+ using BleAddress = std::array<uint8_t, 6>;
+ enum class FirmwareUpdateStates {Idle, Running, Validated, Error};
+ enum class AddressTypes { Public, Random };
+
+ Ble() = default;
+ bool IsConnected() const {return isConnected;}
+ void Connect();
+ void Disconnect();
+
+ void StartFirmwareUpdate();
+ void StopFirmwareUpdate();
+ void FirmwareUpdateTotalBytes(uint32_t totalBytes);
+ void FirmwareUpdateCurrentBytes(uint32_t currentBytes);
+ void State(FirmwareUpdateStates state) { firmwareUpdateState = state; }
+
+ bool IsFirmwareUpdating() const { return isFirmwareUpdating; }
+ uint32_t FirmwareUpdateTotalBytes() const { return firmwareUpdateTotalBytes; }
+ uint32_t FirmwareUpdateCurrentBytes() const { return firmwareUpdateCurrentBytes; }
+ FirmwareUpdateStates State() const { return firmwareUpdateState; }
+
+ void Address(BleAddress&& addr) { address = addr; }
+ const BleAddress& Address() const { return address; }
+ void AddressType(AddressTypes t) { addressType = t;}
+ private:
+ bool isConnected = false;
+ bool isFirmwareUpdating = false;
+ uint32_t firmwareUpdateTotalBytes = 0;
+ uint32_t firmwareUpdateCurrentBytes = 0;
+ FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle;
+ BleAddress address;
+ AddressTypes addressType;
+
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/CurrentTimeClient.cpp b/src/components/Ble/CurrentTimeClient.cpp
new file mode 100644
index 00000000..7a225f4b
--- /dev/null
+++ b/src/components/Ble/CurrentTimeClient.cpp
@@ -0,0 +1,77 @@
+#include <hal/nrf_rtc.h>
+#include "CurrentTimeClient.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid;
+constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid;
+
+CurrentTimeClient::CurrentTimeClient(DateTime& dateTimeController) : dateTimeController{dateTimeController} {
+
+}
+
+void CurrentTimeClient::Init() {
+
+}
+
+bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
+ if(service == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("CTS Discovery complete");
+ return true;
+ }
+
+ if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ctsServiceUuid), &service->uuid.u) == 0) {
+ NRF_LOG_INFO("CTS discovered : 0x%x", service->start_handle);
+ isDiscovered = true;
+ ctsStartHandle = service->start_handle;
+ ctsEndHandle = service->end_handle;
+ return false;
+ }
+ return false;
+}
+
+int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic) {
+ if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("CTS Characteristic discovery complete");
+ return 0;
+ }
+
+ if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&currentTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
+ currentTimeHandle = characteristic->val_handle;
+ }
+ return 0;
+}
+
+int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute) {
+ if(error->status == 0) {
+ // TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent
+ CtsData result;
+ os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result);
+ NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
+ result.month, result.dayofmonth,
+ result.hour, result.minute, result.second);
+ dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
+ 0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
+ } else {
+ NRF_LOG_INFO("Error retrieving current time: %d", error->status);
+ }
+ return 0;
+}
+
+bool CurrentTimeClient::IsDiscovered() const {
+ return isDiscovered;
+}
+
+uint16_t CurrentTimeClient::StartHandle() const {
+ return ctsStartHandle;
+}
+
+uint16_t CurrentTimeClient::EndHandle() const {
+ return ctsEndHandle;
+}
+
+uint16_t CurrentTimeClient::CurrentTimeHandle() const {
+ return currentTimeHandle;
+} \ No newline at end of file
diff --git a/src/components/Ble/CurrentTimeClient.h b/src/components/Ble/CurrentTimeClient.h
new file mode 100644
index 00000000..fabcdaca
--- /dev/null
+++ b/src/components/Ble/CurrentTimeClient.h
@@ -0,0 +1,55 @@
+#pragma once
+#include <cstdint>
+#include <array>
+#include <Components/DateTime/DateTimeController.h>
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace Controllers {
+
+ class CurrentTimeClient {
+ public:
+ explicit CurrentTimeClient(DateTime& dateTimeController);
+ void Init();
+ bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
+ int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic);
+ int OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute);
+ bool IsDiscovered() const;
+ uint16_t StartHandle() const;
+ uint16_t EndHandle() const;
+ uint16_t CurrentTimeHandle() const;
+ static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; }
+ static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; }
+ private:
+ typedef struct __attribute__((packed)) {
+ uint16_t year;
+ uint8_t month;
+ uint8_t dayofmonth;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ uint8_t millis;
+ uint8_t reason;
+ } CtsData;
+
+ static constexpr uint16_t ctsServiceId {0x1805};
+ static constexpr uint16_t currentTimeCharacteristicId {0x2a2b};
+
+ static constexpr ble_uuid16_t ctsServiceUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = ctsServiceId
+ };
+ static constexpr ble_uuid16_t currentTimeCharacteristicUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = currentTimeCharacteristicId
+ };
+
+ uint16_t currentTimeHandle;
+ DateTime& dateTimeController;
+ bool isDiscovered = false;
+ uint16_t ctsStartHandle;
+ uint16_t ctsEndHandle;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/CurrentTimeService.cpp b/src/components/Ble/CurrentTimeService.cpp
new file mode 100644
index 00000000..3a6264e2
--- /dev/null
+++ b/src/components/Ble/CurrentTimeService.cpp
@@ -0,0 +1,86 @@
+#include "CurrentTimeService.h"
+#include <hal/nrf_rtc.h>
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t CurrentTimeService::ctsUuid;
+constexpr ble_uuid16_t CurrentTimeService::ctChrUuid;
+
+
+int CTSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto cts = static_cast<CurrentTimeService*>(arg);
+ return cts->OnTimeAccessed(conn_handle, attr_handle, ctxt);
+}
+
+void CurrentTimeService::Init() {
+ int res;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+
+int CurrentTimeService::OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt) {
+
+ NRF_LOG_INFO("Setting time...");
+
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ CtsData result;
+ os_mbuf_copydata(ctxt->om, 0, sizeof(CtsData), &result);
+
+ NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
+ result.month, result.dayofmonth,
+ result.hour, result.minute, result.second);
+
+ m_dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
+ 0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
+
+ } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
+ CtsData currentDateTime;
+ currentDateTime.year = m_dateTimeController.Year();
+ currentDateTime.month = static_cast<u_int8_t>(m_dateTimeController.Month());
+ currentDateTime.dayofmonth = m_dateTimeController.Day();
+ currentDateTime.hour = m_dateTimeController.Hours();
+ currentDateTime.minute = m_dateTimeController.Minutes();
+ currentDateTime.second = m_dateTimeController.Seconds();
+ currentDateTime.millis = 0;
+
+
+ int res = os_mbuf_append(ctxt->om, &currentDateTime, sizeof(CtsData));
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+
+ }
+
+ return 0;
+}
+
+CurrentTimeService::CurrentTimeService(DateTime &dateTimeController) :
+ characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &ctChrUuid,
+ .access_cb = CTSCallback,
+
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
+ },
+ {
+ 0
+ }
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &ctsUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ }, m_dateTimeController{dateTimeController} {
+
+}
+
diff --git a/src/components/Ble/CurrentTimeService.h b/src/components/Ble/CurrentTimeService.h
new file mode 100644
index 00000000..58bc5ba6
--- /dev/null
+++ b/src/components/Ble/CurrentTimeService.h
@@ -0,0 +1,48 @@
+#pragma once
+#include <cstdint>
+#include <array>
+#include <Components/DateTime/DateTimeController.h>
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace Controllers {
+ class CurrentTimeService {
+ public:
+ CurrentTimeService(DateTime &dateTimeController);
+ void Init();
+
+ int OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt);
+
+ private:
+ static constexpr uint16_t ctsId {0x1805};
+ static constexpr uint16_t ctsCharId {0x2a2b};
+
+ static constexpr ble_uuid16_t ctsUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = ctsId
+ };
+
+ static constexpr ble_uuid16_t ctChrUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = ctsCharId
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[2];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ typedef struct __attribute__((packed)) {
+ uint16_t year;
+ uint8_t month;
+ uint8_t dayofmonth;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ uint8_t millis;
+ uint8_t reason;
+ } CtsData;
+
+ DateTime &m_dateTimeController;
+ };
+ }
+}
diff --git a/src/components/Ble/DeviceInformationService.cpp b/src/components/Ble/DeviceInformationService.cpp
new file mode 100644
index 00000000..406db1cf
--- /dev/null
+++ b/src/components/Ble/DeviceInformationService.cpp
@@ -0,0 +1,116 @@
+#include "DeviceInformationService.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t DeviceInformationService::manufacturerNameUuid;
+constexpr ble_uuid16_t DeviceInformationService::modelNumberUuid;
+constexpr ble_uuid16_t DeviceInformationService::serialNumberUuid;
+constexpr ble_uuid16_t DeviceInformationService::fwRevisionUuid;
+constexpr ble_uuid16_t DeviceInformationService::deviceInfoUuid;
+constexpr ble_uuid16_t DeviceInformationService::hwRevisionUuid;
+constexpr ble_uuid16_t DeviceInformationService::swRevisionUuid;
+
+
+int DeviceInformationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto deviceInformationService = static_cast<DeviceInformationService*>(arg);
+ return deviceInformationService->OnDeviceInfoRequested(conn_handle, attr_handle, ctxt);
+}
+
+void DeviceInformationService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+
+int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt) {
+ const char *str;
+
+ switch (ble_uuid_u16(ctxt->chr->uuid)) {
+ case manufacturerNameId:
+ str = manufacturerName;
+ break;
+ case modelNumberId:
+ str = modelNumber;
+ break;
+ case serialNumberId:
+ str = serialNumber;
+ break;
+ case fwRevisionId:
+ str = fwRevision;
+ break;
+ case hwRevisionId:
+ str = hwRevision;
+ break;
+ case swRevisionId:
+ str = swRevision;
+ break;
+ default:
+ return BLE_ATT_ERR_UNLIKELY;
+ }
+
+ int res = os_mbuf_append(ctxt->om, str, strlen(str));
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+}
+
+DeviceInformationService::DeviceInformationService() :
+ characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &manufacturerNameUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ .uuid = (ble_uuid_t *) &modelNumberUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ .uuid = (ble_uuid_t *) &serialNumberUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ .uuid = (ble_uuid_t *) &fwRevisionUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ .uuid = (ble_uuid_t *) &hwRevisionUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ .uuid = (ble_uuid_t *) &swRevisionUuid,
+ .access_cb = DeviceInformationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ },
+ {
+ 0
+ }
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &deviceInfoUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ }
+ {
+
+}
+
diff --git a/src/components/Ble/DeviceInformationService.h b/src/components/Ble/DeviceInformationService.h
new file mode 100644
index 00000000..25ab8402
--- /dev/null
+++ b/src/components/Ble/DeviceInformationService.h
@@ -0,0 +1,76 @@
+#pragma once
+#include <cstdint>
+#include <array>
+
+#include <host/ble_gap.h>
+#include <Version.h>
+
+namespace Pinetime {
+ namespace Controllers {
+ class DeviceInformationService {
+ public:
+ DeviceInformationService();
+ void Init();
+
+ int OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt);
+
+ private:
+ static constexpr uint16_t deviceInfoId {0x180a};
+ static constexpr uint16_t manufacturerNameId {0x2a29};
+ static constexpr uint16_t modelNumberId {0x2a24};
+ static constexpr uint16_t serialNumberId {0x2a25};
+ static constexpr uint16_t fwRevisionId {0x2a26};
+ static constexpr uint16_t hwRevisionId {0x2a27};
+ static constexpr uint16_t swRevisionId {0x2a28};
+
+ static constexpr const char* manufacturerName = "PINE64";
+ static constexpr const char* modelNumber = "PineTime";
+ static constexpr const char* hwRevision = "1.0.0";
+ static constexpr const char* serialNumber = "0";
+ static constexpr const char* fwRevision = Version::VersionString();
+ static constexpr const char* swRevision = "InfiniTime";
+
+
+ static constexpr ble_uuid16_t deviceInfoUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = deviceInfoId
+ };
+
+ static constexpr ble_uuid16_t manufacturerNameUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = manufacturerNameId
+ };
+
+ static constexpr ble_uuid16_t modelNumberUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = modelNumberId
+ };
+
+ static constexpr ble_uuid16_t serialNumberUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = serialNumberId
+ };
+
+ static constexpr ble_uuid16_t fwRevisionUuid {
+ .u { .type = BLE_UUID_TYPE_16 },
+ .value = fwRevisionId
+ };
+
+ static constexpr ble_uuid16_t hwRevisionUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = hwRevisionId
+ };
+
+ static constexpr ble_uuid16_t swRevisionUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = swRevisionId
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[7];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/DfuService.cpp b/src/components/Ble/DfuService.cpp
new file mode 100644
index 00000000..fcbefdd0
--- /dev/null
+++ b/src/components/Ble/DfuService.cpp
@@ -0,0 +1,440 @@
+#include <Components/Ble/BleController.h>
+#include <SystemTask/SystemTask.h>
+#include <cstring>
+#include "DfuService.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid128_t DfuService::serviceUuid;
+constexpr ble_uuid128_t DfuService::controlPointCharacteristicUuid;
+constexpr ble_uuid128_t DfuService::revisionCharacteristicUuid;
+constexpr ble_uuid128_t DfuService::packetCharacteristicUuid;
+
+int DfuServiceCallback(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto dfuService = static_cast<DfuService *>(arg);
+ return dfuService->OnServiceData(conn_handle, attr_handle, ctxt);
+}
+
+void NotificationTimerCallback(TimerHandle_t xTimer) {
+ auto notificationManager = static_cast<DfuService::NotificationManager *>(pvTimerGetTimerID(xTimer));
+ notificationManager->OnNotificationTimer();
+}
+
+void TimeoutTimerCallback(TimerHandle_t xTimer) {
+ auto dfuService = static_cast<DfuService *>(pvTimerGetTimerID(xTimer));
+ dfuService->OnTimeout();
+}
+
+DfuService::DfuService(Pinetime::System::SystemTask &systemTask, Pinetime::Controllers::Ble &bleController,
+ Pinetime::Drivers::SpiNorFlash &spiNorFlash) :
+ systemTask{systemTask},
+ bleController{bleController},
+ dfuImage{spiNorFlash},
+ characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &packetCharacteristicUuid,
+ .access_cb = DfuServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
+ .val_handle = nullptr,
+ },
+ {
+ .uuid = (ble_uuid_t *) &controlPointCharacteristicUuid,
+ .access_cb = DfuServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = nullptr,
+ },
+ {
+ .uuid = (ble_uuid_t *) &revisionCharacteristicUuid,
+ .access_cb = DfuServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ .val_handle = &revision,
+
+ },
+ {
+ 0
+ }
+
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &serviceUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ } {
+ timeoutTimer = xTimerCreate ("notificationTimer", 10000, pdFALSE, this, TimeoutTimerCallback);
+}
+
+void DfuService::Init() {
+ int res;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
+ if(bleController.IsFirmwareUpdating()){
+ xTimerStart(timeoutTimer, 0);
+ }
+
+
+ ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &packetCharacteristicUuid, nullptr,
+ &packetCharacteristicHandle);
+ ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &controlPointCharacteristicUuid, nullptr,
+ &controlPointCharacteristicHandle);
+ ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &revisionCharacteristicUuid, nullptr,
+ &revisionCharacteristicHandle);
+
+ if (attributeHandle == packetCharacteristicHandle) {
+ if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
+ return WritePacketHandler(connectionHandle, context->om);
+ else return 0;
+ } else if (attributeHandle == controlPointCharacteristicHandle) {
+ if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
+ return ControlPointHandler(connectionHandle, context->om);
+ else return 0;
+ } else if (attributeHandle == revisionCharacteristicHandle) {
+ if (context->op == BLE_GATT_ACCESS_OP_READ_CHR)
+ return SendDfuRevision(context->om);
+ else return 0;
+ } else {
+ NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle);
+ return 0;
+ }
+}
+
+int DfuService::SendDfuRevision(os_mbuf *om) const {
+ int res = os_mbuf_append(om, &revision, sizeof(revision));
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+}
+
+int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf *om) {
+ switch (state) {
+ case States::Start: {
+ softdeviceSize = om->om_data[0] + (om->om_data[1] << 8) + (om->om_data[2] << 16) + (om->om_data[3] << 24);
+ bootloaderSize = om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
+ applicationSize = om->om_data[8] + (om->om_data[9] << 8) + (om->om_data[10] << 16) + (om->om_data[11] << 24);
+ bleController.FirmwareUpdateTotalBytes(applicationSize);
+ NRF_LOG_INFO("[DFU] -> Start data received : SD size : %d, BT size : %d, app size : %d", softdeviceSize,
+ bootloaderSize, applicationSize);
+
+ dfuImage.Erase();
+
+ uint8_t data[]{16, 1, 1};
+ notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
+ state = States::Init;
+ }
+ return 0;
+ case States::Init: {
+ uint16_t deviceType = om->om_data[0] + (om->om_data[1] << 8);
+ uint16_t deviceRevision = om->om_data[2] + (om->om_data[3] << 8);
+ uint32_t applicationVersion =
+ om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
+ uint16_t softdeviceArrayLength = om->om_data[8] + (om->om_data[9] << 8);
+ uint16_t sd[softdeviceArrayLength];
+ for (int i = 0; i < softdeviceArrayLength; i++) {
+ sd[i] = om->om_data[10 + (i * 2)] + (om->om_data[10 + (i * 2) + 1] << 8);
+ }
+ expectedCrc =
+ om->om_data[10 + (softdeviceArrayLength * 2)] + (om->om_data[10 + (softdeviceArrayLength * 2) + 1] << 8);
+
+ NRF_LOG_INFO(
+ "[DFU] -> Init data received : deviceType = %d, deviceRevision = %d, applicationVersion = %d, nb SD = %d, First SD = %d, CRC = %u",
+ deviceType, deviceRevision, applicationVersion, softdeviceArrayLength, sd[0], expectedCrc);
+
+ return 0;
+ }
+
+ case States::Data: {
+ nbPacketReceived++;
+ dfuImage.Append(om->om_data, om->om_len);
+ bytesReceived += om->om_len;
+ bleController.FirmwareUpdateCurrentBytes(bytesReceived);
+
+ if ((nbPacketReceived % nbPacketsToNotify) == 0 && bytesReceived != applicationSize) {
+ uint8_t data[5]{static_cast<uint8_t>(Opcodes::PacketReceiptNotification),
+ (uint8_t) (bytesReceived & 0x000000FFu), (uint8_t) (bytesReceived >> 8u),
+ (uint8_t) (bytesReceived >> 16u), (uint8_t) (bytesReceived >> 24u)};
+ NRF_LOG_INFO("[DFU] -> Send packet notification: %d bytes received", bytesReceived);
+ notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 5);
+ }
+ if (dfuImage.IsComplete()) {
+ uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
+ static_cast<uint8_t>(Opcodes::ReceiveFirmwareImage),
+ static_cast<uint8_t>(ErrorCodes::NoError)};
+ NRF_LOG_INFO("[DFU] -> Send packet notification : all bytes received!");
+ notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
+ state = States::Validate;
+ }
+ }
+ return 0;
+ default:
+ // Invalid state
+ return 0;
+ }
+ return 0;
+}
+
+int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf *om) {
+ auto opcode = static_cast<Opcodes>(om->om_data[0]);
+ NRF_LOG_INFO("[DFU] -> ControlPointHandler");
+
+ switch (opcode) {
+ case Opcodes::StartDFU: {
+ if (state != States::Idle && state != States::Start) {
+ NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are not in Idle state");
+ return 0;
+ }
+ if (state == States::Start) {
+ NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are already in Start state");
+ return 0;
+ }
+ auto imageType = static_cast<ImageTypes>(om->om_data[1]);
+ if (imageType == ImageTypes::Application) {
+ NRF_LOG_INFO("[DFU] -> Start DFU, mode = Application");
+ state = States::Start;
+ bleController.StartFirmwareUpdate();
+ bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Running);
+ bleController.FirmwareUpdateTotalBytes(0xffffffffu);
+ bleController.FirmwareUpdateCurrentBytes(0);
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateStarted);
+ return 0;
+ } else {
+ NRF_LOG_INFO("[DFU] -> Start DFU, mode %d not supported!", imageType);
+ return 0;
+ }
+ }
+ break;
+ case Opcodes::InitDFUParameters: {
+ if (state != States::Init) {
+ NRF_LOG_INFO("[DFU] -> Init DFU requested, but we are not in Init state");
+ return 0;
+ }
+ bool isInitComplete = (om->om_data[1] != 0);
+ NRF_LOG_INFO("[DFU] -> Init DFU parameters %s", isInitComplete ? " complete" : " not complete");
+
+ if (isInitComplete) {
+ uint8_t data[3] {
+ static_cast<uint8_t>(Opcodes::Response),
+ static_cast<uint8_t>(Opcodes::InitDFUParameters),
+ (isInitComplete ? uint8_t{1} : uint8_t{0})
+ };
+ notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
+ return 0;
+ }
+ }
+ return 0;
+ case Opcodes::PacketReceiptNotificationRequest:
+ nbPacketsToNotify = om->om_data[1];
+ NRF_LOG_INFO("[DFU] -> Receive Packet Notification Request, nb packet = %d", nbPacketsToNotify);
+ return 0;
+ case Opcodes::ReceiveFirmwareImage:
+ if (state != States::Init) {
+ NRF_LOG_INFO("[DFU] -> Receive firmware image requested, but we are not in Start Init");
+ return 0;
+ }
+ // TODO the chunk size is dependant of the implementation of the host application...
+ dfuImage.Init(20, applicationSize, expectedCrc);
+ NRF_LOG_INFO("[DFU] -> Starting receive firmware");
+ state = States::Data;
+ return 0;
+ case Opcodes::ValidateFirmware: {
+ if (state != States::Validate) {
+ NRF_LOG_INFO("[DFU] -> Validate firmware image requested, but we are not in Data state %d", state);
+ return 0;
+ }
+
+ NRF_LOG_INFO("[DFU] -> Validate firmware image requested -- %d", connectionHandle);
+
+ if(dfuImage.Validate()){
+ state = States::Validated;
+ bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
+ NRF_LOG_INFO("Image OK");
+
+ uint8_t data[3] {
+ static_cast<uint8_t>(Opcodes::Response),
+ static_cast<uint8_t>(Opcodes::ValidateFirmware),
+ static_cast<uint8_t>(ErrorCodes::NoError)
+ };
+ notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
+ } else {
+ bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
+ NRF_LOG_INFO("Image Error : bad CRC");
+
+ uint8_t data[3] {
+ static_cast<uint8_t>(Opcodes::Response),
+ static_cast<uint8_t>(Opcodes::ValidateFirmware),
+ static_cast<uint8_t>(ErrorCodes::CrcError)
+ };
+ notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
+ }
+
+ return 0;
+ }
+ case Opcodes::ActivateImageAndReset:
+ if (state != States::Validated) {
+ NRF_LOG_INFO("[DFU] -> Activate image and reset requested, but we are not in Validated state");
+ return 0;
+ }
+ NRF_LOG_INFO("[DFU] -> Activate image and reset!");
+ bleController.StopFirmwareUpdate();
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
+ Reset();
+ bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+void DfuService::OnTimeout() {
+ Reset();
+}
+
+void DfuService::Reset() {
+ state = States::Idle;
+ nbPacketsToNotify = 0;
+ nbPacketReceived = 0;
+ bytesReceived = 0;
+ softdeviceSize = 0;
+ bootloaderSize = 0;
+ applicationSize = 0;
+ expectedCrc = 0;
+ notificationManager.Reset();
+ bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
+ bleController.StopFirmwareUpdate();
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
+}
+
+DfuService::NotificationManager::NotificationManager() {
+ timer = xTimerCreate ("notificationTimer", 1000, pdFALSE, this, NotificationTimerCallback);
+}
+
+bool DfuService::NotificationManager::AsyncSend(uint16_t connection, uint16_t charactHandle, uint8_t *data, size_t s) {
+ if(size != 0 || s > 10)
+ return false;
+
+ connectionHandle = connection;
+ characteristicHandle = charactHandle;
+ size = s;
+ std::memcpy(buffer, data, size);
+ xTimerStart(timer, 0);
+ return true;
+}
+
+void DfuService::NotificationManager::OnNotificationTimer() {
+ if(size > 0) {
+ Send(connectionHandle, characteristicHandle, buffer, size);
+ size = 0;
+ }
+}
+
+void DfuService::NotificationManager::Send(uint16_t connection, uint16_t charactHandle, const uint8_t *data, const size_t s) {
+ auto *om = ble_hs_mbuf_from_flat(data, s);
+ auto ret = ble_gattc_notify_custom(connection, charactHandle, om);
+ ASSERT(ret == 0);
+}
+
+void DfuService::NotificationManager::Reset() {
+ connectionHandle = 0;
+ characteristicHandle = 0;
+ size = 0;
+ xTimerStop(timer, 0);
+}
+
+void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t expectedCrc) {
+ if(chunkSize != 20) return;
+ this->chunkSize = chunkSize;
+ this->totalSize = totalSize;
+ this->expectedCrc = expectedCrc;
+ this->ready = true;
+}
+
+void DfuService::DfuImage::Append(uint8_t *data, size_t size) {
+ if(!ready) return;
+ ASSERT(size <= 20);
+
+ std::memcpy(tempBuffer + bufferWriteIndex, data, size);
+ bufferWriteIndex += size;
+
+ if(bufferWriteIndex == bufferSize) {
+ spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
+ totalWriteIndex += bufferWriteIndex;
+ bufferWriteIndex = 0;
+ }
+
+ if(bufferWriteIndex > 0 && totalWriteIndex + bufferWriteIndex == totalSize) {
+ spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
+ totalWriteIndex += bufferWriteIndex;
+ if (totalSize < maxSize)
+ WriteMagicNumber();
+ }
+}
+
+void DfuService::DfuImage::WriteMagicNumber() {
+ uint32_t magic[4] = { // TODO When this variable is a static constexpr, the values written to the memory are not correct. Why?
+ 0xf395c277,
+ 0x7fefd260,
+ 0x0f505235,
+ 0x8079b62c,
+ };
+
+ uint32_t offset = writeOffset + (maxSize - (4 * sizeof(uint32_t)));
+ spiNorFlash.Write(offset, reinterpret_cast<const uint8_t *>(magic), 4 * sizeof(uint32_t));
+}
+
+void DfuService::DfuImage::Erase() {
+ for (size_t erased = 0; erased < maxSize; erased += 0x1000) {
+ spiNorFlash.SectorErase(writeOffset + erased);
+ }
+}
+
+bool DfuService::DfuImage::Validate() {
+ uint32_t chunkSize = 200;
+ size_t currentOffset = 0;
+ uint16_t crc = 0;
+
+ bool first = true;
+ while (currentOffset < totalSize) {
+ uint32_t readSize = (totalSize - currentOffset) > chunkSize ? chunkSize : (totalSize - currentOffset);
+
+ spiNorFlash.Read(writeOffset + currentOffset, tempBuffer, readSize);
+ if (first) {
+ crc = ComputeCrc(tempBuffer, readSize, NULL);
+ first = false;
+ } else
+ crc = ComputeCrc(tempBuffer, readSize, &crc);
+ currentOffset += readSize;
+ }
+
+ return (crc == expectedCrc);
+}
+
+uint16_t DfuService::DfuImage::ComputeCrc(uint8_t const *p_data, uint32_t size, uint16_t const *p_crc) {
+ uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
+
+ for (uint32_t i = 0; i < size; i++) {
+ crc = (uint8_t) (crc >> 8) | (crc << 8);
+ crc ^= p_data[i];
+ crc ^= (uint8_t) (crc & 0xFF) >> 4;
+ crc ^= (crc << 8) << 4;
+ crc ^= ((crc & 0xFF) << 4) << 1;
+ }
+
+ return crc;
+}
+
+bool DfuService::DfuImage::IsComplete() {
+ if(!ready) return false;
+ return totalWriteIndex == totalSize;
+}
diff --git a/src/components/Ble/DfuService.h b/src/components/Ble/DfuService.h
new file mode 100644
index 00000000..d7ba460c
--- /dev/null
+++ b/src/components/Ble/DfuService.h
@@ -0,0 +1,161 @@
+#pragma once
+
+#include <cstdint>
+#include <array>
+
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Drivers {
+ class SpiNorFlash;
+ }
+ namespace Controllers {
+ class Ble;
+
+ class DfuService {
+ public:
+ DfuService(Pinetime::System::SystemTask &systemTask, Pinetime::Controllers::Ble &bleController,
+ Pinetime::Drivers::SpiNorFlash &spiNorFlash);
+ void Init();
+ int OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
+ void OnTimeout();
+ void Reset();
+
+ class NotificationManager {
+ public:
+ NotificationManager();
+ bool AsyncSend(uint16_t connection, uint16_t charactHandle, uint8_t *data, size_t size);
+ void Send(uint16_t connection, uint16_t characteristicHandle, const uint8_t *data, const size_t s);
+ private:
+ TimerHandle_t timer;
+ uint16_t connectionHandle = 0;
+ uint16_t characteristicHandle = 0;
+ size_t size = 0;
+ uint8_t buffer[10];
+ public:
+ void OnNotificationTimer();
+ void Reset();
+ };
+ class DfuImage {
+ public:
+ DfuImage(Pinetime::Drivers::SpiNorFlash& spiNorFlash) : spiNorFlash{spiNorFlash} {}
+ void Init(size_t chunkSize, size_t totalSize, uint16_t expectedCrc);
+ void Erase();
+ void Append(uint8_t* data, size_t size);
+ bool Validate();
+ bool IsComplete();
+
+ private:
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash;
+ static constexpr size_t bufferSize = 200;
+ bool ready = false;
+ size_t chunkSize = 0;
+ size_t totalSize = 0;
+ size_t maxSize = 475136;
+ size_t bufferWriteIndex = 0;
+ size_t totalWriteIndex = 0;
+ static constexpr size_t writeOffset = 0x40000;
+ uint8_t tempBuffer[bufferSize];
+ uint16_t expectedCrc = 0;
+
+ void WriteMagicNumber();
+ uint16_t ComputeCrc(uint8_t const *p_data, uint32_t size, uint16_t const *p_crc);
+
+ };
+
+ private:
+ Pinetime::System::SystemTask &systemTask;
+ Pinetime::Controllers::Ble &bleController;
+ DfuImage dfuImage;
+ NotificationManager notificationManager;
+
+ static constexpr uint16_t dfuServiceId{0x1530};
+ static constexpr uint16_t packetCharacteristicId{0x1532};
+ static constexpr uint16_t controlPointCharacteristicId{0x1531};
+ static constexpr uint16_t revisionCharacteristicId{0x1534};
+
+ uint16_t revision{0x0008};
+
+ static constexpr ble_uuid128_t serviceUuid{
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
+ 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
+ };
+
+ static constexpr ble_uuid128_t packetCharacteristicUuid{
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
+ 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}
+ };
+
+ static constexpr ble_uuid128_t controlPointCharacteristicUuid{
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
+ 0xDE, 0xEF, 0x12, 0x12, 0x31, 0x15, 0x00, 0x00}
+ };
+
+ static constexpr ble_uuid128_t revisionCharacteristicUuid{
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
+ 0xDE, 0xEF, 0x12, 0x12, 0x34, 0x15, 0x00, 0x00}
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[4];
+ struct ble_gatt_svc_def serviceDefinition[2];
+ uint16_t packetCharacteristicHandle;
+ uint16_t controlPointCharacteristicHandle;
+ uint16_t revisionCharacteristicHandle;
+
+ enum class States : uint8_t {
+ Idle, Init, Start, Data, Validate, Validated
+ };
+ States state = States::Idle;
+
+ enum class ImageTypes : uint8_t {
+ NoImage = 0x00,
+ SoftDevice = 0x01,
+ Bootloader = 0x02,
+ SoftDeviceAndBootloader = 0x03,
+ Application = 0x04
+ };
+
+ enum class Opcodes : uint8_t {
+ StartDFU = 0x01,
+ InitDFUParameters = 0x02,
+ ReceiveFirmwareImage = 0x03,
+ ValidateFirmware = 0x04,
+ ActivateImageAndReset = 0x05,
+ PacketReceiptNotificationRequest = 0x08,
+ Response = 0x10,
+ PacketReceiptNotification = 0x11
+ };
+
+ enum class ErrorCodes {
+ NoError = 0x01,
+ InvalidState = 0x02,
+ NotSupported = 0x03,
+ DataSizeExceedsLimits = 0x04,
+ CrcError = 0x05,
+ OperationFailed = 0x06
+ };
+
+ uint8_t nbPacketsToNotify = 0;
+ uint32_t nbPacketReceived = 0;
+ uint32_t bytesReceived = 0;
+
+ uint32_t softdeviceSize = 0;
+ uint32_t bootloaderSize = 0;
+ uint32_t applicationSize = 0;
+ uint16_t expectedCrc = 0;
+
+ int SendDfuRevision(os_mbuf *om) const;
+ int WritePacketHandler(uint16_t connectionHandle, os_mbuf *om);
+ int ControlPointHandler(uint16_t connectionHandle, os_mbuf *om);
+
+ TimerHandle_t timeoutTimer;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Ble/ImmediateAlertService.cpp b/src/components/Ble/ImmediateAlertService.cpp
new file mode 100644
index 00000000..d2c4cffb
--- /dev/null
+++ b/src/components/Ble/ImmediateAlertService.cpp
@@ -0,0 +1,76 @@
+#include "ImmediateAlertService.h"
+#include <SystemTask/SystemTask.h>
+#include "AlertNotificationService.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t ImmediateAlertService::immediateAlertServiceUuid;
+constexpr ble_uuid16_t ImmediateAlertService::alertLevelUuid;
+
+namespace {
+ int AlertLevelCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto *immediateAlertService = static_cast<ImmediateAlertService *>(arg);
+ return immediateAlertService->OnAlertLevelChanged(conn_handle, attr_handle, ctxt);
+ }
+
+ const char* ToString(ImmediateAlertService::Levels level) {
+ switch (level) {
+ case ImmediateAlertService::Levels::NoAlert: return "Alert : None";
+ case ImmediateAlertService::Levels::HighAlert: return "Alert : High";
+ case ImmediateAlertService::Levels::MildAlert: return "Alert : Mild";
+ default: return "";
+ }
+ }
+}
+
+ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager &notificationManager) :
+ systemTask{systemTask},
+ notificationManager{notificationManager},
+ characteristicDefinition{
+ {
+ .uuid = (ble_uuid_t *) &alertLevelUuid,
+ .access_cb = AlertLevelCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
+ .val_handle = &alertLevelHandle
+ },
+ {
+ 0
+ }
+ },
+ serviceDefinition{
+ {
+ /* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &immediateAlertServiceUuid,
+ .characteristics = characteristicDefinition
+ },
+ {
+ 0
+ },
+ }{
+
+}
+
+void ImmediateAlertService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int ImmediateAlertService::OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
+ if(attributeHandle == alertLevelHandle) {
+ if(context->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ auto alertLevel = static_cast<Levels>(context->om->om_data[0]);
+ auto* alertString = ToString(alertLevel);
+ notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, alertString, strlen(alertString));
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
+ }
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/components/Ble/ImmediateAlertService.h b/src/components/Ble/ImmediateAlertService.h
new file mode 100644
index 00000000..c42846c4
--- /dev/null
+++ b/src/components/Ble/ImmediateAlertService.h
@@ -0,0 +1,46 @@
+#pragma once
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class NotificationManager;
+ class ImmediateAlertService {
+ public:
+ enum class Levels : uint8_t {
+ NoAlert = 0,
+ MildAlert = 1,
+ HighAlert = 2
+ };
+
+ ImmediateAlertService(Pinetime::System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager &notificationManager);
+ void Init();
+ int OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
+
+ private:
+ Pinetime::System::SystemTask& systemTask;
+ NotificationManager& notificationManager;
+
+ static constexpr uint16_t immediateAlertServiceId {0x1802};
+ static constexpr uint16_t alertLevelId {0x2A06};
+
+ static constexpr ble_uuid16_t immediateAlertServiceUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = immediateAlertServiceId
+ };
+
+ static constexpr ble_uuid16_t alertLevelUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = alertLevelId
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t alertLevelHandle;
+ };
+ }
+}
diff --git a/src/components/Ble/MusicService.cpp b/src/components/Ble/MusicService.cpp
new file mode 100644
index 00000000..b5fa5356
--- /dev/null
+++ b/src/components/Ble/MusicService.cpp
@@ -0,0 +1,129 @@
+#include <SystemTask/SystemTask.h>
+#include "MusicService.h"
+
+int MSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
+ auto musicService = static_cast<Pinetime::Controllers::MusicService*>(arg);
+ return musicService->OnCommand(conn_handle, attr_handle, ctxt);
+}
+
+Pinetime::Controllers::MusicService::MusicService(Pinetime::System::SystemTask &system) : m_system(system)
+{
+ msUuid.value[11] = msId[0];
+ msUuid.value[12] = msId[1];
+ msEventCharUuid.value[11] = msEventCharId[0];
+ msEventCharUuid.value[12] = msEventCharId[1];
+ msStatusCharUuid.value[11] = msStatusCharId[0];
+ msStatusCharUuid.value[12] = msStatusCharId[1];
+ msTrackCharUuid.value[11] = msTrackCharId[0];
+ msTrackCharUuid.value[12] = msTrackCharId[1];
+ msArtistCharUuid.value[11] = msArtistCharId[0];
+ msArtistCharUuid.value[12] = msArtistCharId[1];
+ msAlbumCharUuid.value[11] = msAlbumCharId[0];
+ msAlbumCharUuid.value[12] = msAlbumCharId[1];
+
+ characteristicDefinition[0] = { .uuid = (ble_uuid_t*)(&msEventCharUuid),
+ .access_cb = MSCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &m_eventHandle
+ };
+ characteristicDefinition[1] = { .uuid = (ble_uuid_t*)(&msStatusCharUuid),
+ .access_cb = MSCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
+ };
+ characteristicDefinition[2] = { .uuid = (ble_uuid_t*)(&msTrackCharUuid),
+ .access_cb = MSCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
+ };
+ characteristicDefinition[3] = { .uuid = (ble_uuid_t*)(&msArtistCharUuid),
+ .access_cb = MSCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
+ };
+ characteristicDefinition[4] = { .uuid = (ble_uuid_t*)(&msAlbumCharUuid),
+ .access_cb = MSCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
+ };
+ characteristicDefinition[5] = {0};
+
+ serviceDefinition[0] = {
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = (ble_uuid_t *) &msUuid,
+ .characteristics = characteristicDefinition
+ };
+ serviceDefinition[1] = {0};
+
+ m_artist = "Waiting for";
+ m_album = "";
+ m_track = "track information...";
+}
+
+void Pinetime::Controllers::MusicService::Init()
+{
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int Pinetime::Controllers::MusicService::OnCommand(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt) {
+
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
+ uint8_t data[notifSize + 1];
+ data[notifSize] = '\0';
+ os_mbuf_copydata(ctxt->om, 0, notifSize, data);
+ char *s = (char *) &data[0];
+ NRF_LOG_INFO("DATA : %s", s);
+ if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msArtistCharUuid) == 0) {
+ m_artist = s;
+ } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msTrackCharUuid) == 0) {
+ m_track = s;
+ } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msAlbumCharUuid) == 0) {
+ m_album = s;
+ } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msStatusCharUuid) == 0) {
+ m_status = s[0];
+ }
+ }
+ return 0;
+}
+
+std::string Pinetime::Controllers::MusicService::album()
+{
+ return m_album;
+}
+
+std::string Pinetime::Controllers::MusicService::artist()
+{
+ return m_artist;
+}
+
+std::string Pinetime::Controllers::MusicService::track()
+{
+ return m_track;
+}
+
+unsigned char Pinetime::Controllers::MusicService::status()
+{
+ return m_status;
+}
+
+void Pinetime::Controllers::MusicService::event(char event)
+{
+ auto *om = ble_hs_mbuf_from_flat(&event, 1);
+
+ uint16_t connectionHandle = m_system.nimble().connHandle();
+
+ if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return;
+ }
+
+ ble_gattc_notify_custom(connectionHandle, m_eventHandle, om);
+}
+
diff --git a/src/components/Ble/MusicService.h b/src/components/Ble/MusicService.h
new file mode 100644
index 00000000..ab6db572
--- /dev/null
+++ b/src/components/Ble/MusicService.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <cstdint>
+#include <array>
+#include <host/ble_gap.h>
+#include <host/ble_uuid.h>
+#include <string>
+
+//c7e50000-78fc-48fe-8e23-43b37a1942d0
+#define MUSIC_SERVICE_UUID_BASE {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0xe5, 0xc7}
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+
+ class MusicService {
+ public:
+ MusicService(Pinetime::System::SystemTask &system);
+ void Init();
+ int OnCommand(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt);
+
+ std::string artist();
+ std::string track();
+ std::string album();
+ unsigned char status();
+
+ void event(char event);
+
+ static const char EVENT_MUSIC_OPEN = 0xe0;
+ static const char EVENT_MUSIC_PLAY = 0x00;
+ static const char EVENT_MUSIC_PAUSE = 0x01;
+ static const char EVENT_MUSIC_NEXT = 0x03;
+ static const char EVENT_MUSIC_PREV = 0x04;
+ static const char EVENT_MUSIC_VOLUP = 0x05;
+ static const char EVENT_MUSIC_VOLDOWN = 0x06;
+ static const char STATUS_MUSIC_PAUSED = 0x00;
+ static const char STATUS_MUSIC_PLAYING = 0x01;
+
+ private:
+ static constexpr uint8_t msId[2] = {0x00, 0x01};
+ static constexpr uint8_t msEventCharId[2] = {0x00, 0x02};
+ static constexpr uint8_t msStatusCharId[2] = {0x00, 0x03};
+ static constexpr uint8_t msArtistCharId[2] = {0x00, 0x04};
+ static constexpr uint8_t msTrackCharId[2] = {0x00, 0x05};
+ static constexpr uint8_t msAlbumCharId[2] = {0x00, 0x06};
+
+ ble_uuid128_t msUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+
+ ble_uuid128_t msEventCharUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+ ble_uuid128_t msStatusCharUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+ ble_uuid128_t msArtistCharUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+ ble_uuid128_t msTrackCharUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+ ble_uuid128_t msAlbumCharUuid {
+ .u = { .type = BLE_UUID_TYPE_128 },
+ .value = MUSIC_SERVICE_UUID_BASE
+ };
+
+ struct ble_gatt_chr_def characteristicDefinition[6];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t m_eventHandle;
+
+ std::string m_artist;
+ std::string m_album;
+ std::string m_track;
+
+ unsigned char m_status;
+
+ Pinetime::System::SystemTask& m_system;
+
+ };
+ }
+}
+
diff --git a/src/components/Ble/NimbleController.cpp b/src/components/Ble/NimbleController.cpp
new file mode 100644
index 00000000..b13f9ce3
--- /dev/null
+++ b/src/components/Ble/NimbleController.cpp
@@ -0,0 +1,337 @@
+
+#include <Components/DateTime/DateTimeController.h>
+
+#include <SystemTask/SystemTask.h>
+#include <Components/Ble/NotificationManager.h>
+#include <hal/nrf_rtc.h>
+
+#include "NimbleController.h"
+#include "MusicService.h"
+#include <services/gatt/ble_svc_gatt.h>
+#include <services/gap/ble_svc_gap.h>
+#include <host/util/util.h>
+#include <host/ble_hs_id.h>
+#include <host/ble_hs.h>
+#include <host/ble_gap.h>
+
+
+
+using namespace Pinetime::Controllers;
+
+// TODO I'm not satisfied by how this code looks like (AlertNotificationClient and CurrentTimeClient must
+// expose too much data, too many callbacks -> NimbleController -> CTS/ANS client.
+// Let's try to improve this code (and keep it working!)
+
+NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::Ble& bleController,
+ DateTime& dateTimeController,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Controllers::Battery& batteryController,
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
+ systemTask{systemTask},
+ bleController{bleController},
+ dateTimeController{dateTimeController},
+ notificationManager{notificationManager},
+ spiNorFlash{spiNorFlash},
+ dfuService{systemTask, bleController, spiNorFlash},
+ currentTimeClient{dateTimeController},
+ anService{systemTask, notificationManager},
+ alertNotificationClient{systemTask, notificationManager},
+ currentTimeService{dateTimeController},
+ musicService{systemTask},
+ batteryInformationService{batteryController},
+ immediateAlertService{systemTask, notificationManager} {
+
+}
+
+int GAPEventCallback(struct ble_gap_event *event, void *arg) {
+ auto nimbleController = static_cast<NimbleController*>(arg);
+ return nimbleController->OnGAPEvent(event);
+}
+
+int CurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
+ const struct ble_gatt_chr *chr, void *arg) {
+ auto client = static_cast<NimbleController*>(arg);
+ return client->OnCTSCharacteristicDiscoveryEvent(conn_handle, error, chr);
+}
+
+int AlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
+ const struct ble_gatt_chr *chr, void *arg) {
+ auto client = static_cast<NimbleController*>(arg);
+ return client->OnANSCharacteristicDiscoveryEvent(conn_handle, error, chr);
+}
+
+int CurrentTimeReadCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
+ struct ble_gatt_attr *attr, void *arg) {
+ auto client = static_cast<NimbleController*>(arg);
+ return client->OnCurrentTimeReadResult(conn_handle, error, attr);
+}
+
+int AlertNotificationDescriptorDiscoveryEventCallback(uint16_t conn_handle,
+ const struct ble_gatt_error *error,
+ uint16_t chr_val_handle,
+ const struct ble_gatt_dsc *dsc,
+ void *arg) {
+ auto client = static_cast<NimbleController*>(arg);
+ return client->OnANSDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
+}
+
+void NimbleController::Init() {
+ while (!ble_hs_synced()) {}
+
+ ble_svc_gap_init();
+ ble_svc_gatt_init();
+
+ deviceInformationService.Init();
+ currentTimeClient.Init();
+ currentTimeService.Init();
+ musicService.Init();
+ anService.Init();
+ dfuService.Init();
+ batteryInformationService.Init();
+ immediateAlertService.Init();
+ int res;
+ res = ble_hs_util_ensure_addr(0);
+ ASSERT(res == 0);
+ res = ble_hs_id_infer_auto(0, &addrType);
+ ASSERT(res == 0);
+ res = ble_svc_gap_device_name_set(deviceName);
+ ASSERT(res == 0);
+ Pinetime::Controllers::Ble::BleAddress address;
+ res = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
+ ASSERT(res == 0);
+ bleController.AddressType((addrType == 0) ? Ble::AddressTypes::Public : Ble::AddressTypes::Random);
+ bleController.Address(std::move(address));
+
+ res = ble_gatts_start();
+ ASSERT(res == 0);
+}
+
+void NimbleController::StartAdvertising() {
+ if(ble_gap_adv_active()) return;
+
+ ble_svc_gap_device_name_set(deviceName);
+
+ /* 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));
+
+ adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
+ adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+
+ fields.flags = BLE_HS_ADV_F_DISC_GEN |
+ BLE_HS_ADV_F_BREDR_UNSUP;
+// fields.uuids128 = BLE_UUID128(BLE_UUID128_DECLARE(
+// 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+// 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff));
+ fields.uuids128 = &dfuServiceUuid;
+ fields.num_uuids128 = 1;
+ 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_len = strlen(deviceName);
+ rsp_fields.name_is_complete = 1;
+
+ ble_gap_adv_set_fields(&fields);
+// ASSERT(res == 0); // TODO this one sometimes fails with error 22 (notsync)
+
+ ble_gap_adv_rsp_set_fields(&rsp_fields);
+// ASSERT(res == 0);
+
+ ble_gap_adv_start(addrType, NULL, 180000,
+ &adv_params, GAPEventCallback, this);
+// ASSERT(res == 0);// TODO I've disabled these ASSERT as they sometime asserts and reset the mcu.
+ // For now, the advertising is restarted as soon as it ends. There may be a race condition
+ // that prevent the advertising from restarting reliably.
+ // I remove the assert to prevent this uncesseray crash, but in the long term, the management of
+ // the advertising should be improve (better error handling, and advertise for 3 minutes after
+ // the application has been woken up, for example.
+}
+
+int OnAllSvrDisco(uint16_t conn_handle,
+ const struct ble_gatt_error *error,
+ const struct ble_gatt_svc *service,
+ void *arg) {
+ auto nimbleController = static_cast<NimbleController*>(arg);
+ return nimbleController->OnDiscoveryEvent(conn_handle, error, service);
+ return 0;
+}
+
+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("advertise complete; reason=%dn status=%d", event->adv_complete.reason, event->connect.status);
+ 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);
+
+ if (event->connect.status != 0) {
+ /* Connection failed; resume advertising. */
+ StartAdvertising();
+ bleController.Disconnect();
+ } else {
+ bleController.Connect();
+ systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleConnected);
+ connectionHandle = event->connect.conn_handle;
+ // Service discovery is deffered via systemtask
+ }
+ }
+ break;
+ case BLE_GAP_EVENT_DISCONNECT:
+ NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_DISCONNECT");
+ NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
+
+ /* Connection terminated; resume advertising. */
+ connectionHandle = BLE_HS_CONN_HANDLE_NONE;
+ bleController.Disconnect();
+ StartAdvertising();
+ 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("connection updated; status=%d ", event->conn_update.status);
+ 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);
+ return 0;
+ case BLE_GAP_EVENT_SUBSCRIBE:
+ 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,
+ event->subscribe.reason,
+ event->subscribe.prev_notify,
+ event->subscribe.cur_notify,
+ event->subscribe.prev_indicate);
+ return 0;
+ 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);
+ return 0;
+
+ case 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.
+ */
+
+ /* Delete the old bond. */
+ struct ble_gap_conn_desc desc;
+ ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+ ble_store_util_delete_peer(&desc.peer_id_addr);
+
+ /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+ * continue with the pairing operation.
+ */
+ }
+ return BLE_GAP_REPEAT_PAIRING_RETRY;
+
+ case BLE_GAP_EVENT_NOTIFY_RX: {
+ /* Peer sent us a notification or indication. */
+ size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
+
+ NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
+ "attr_len=%d",
+ event->notify_rx.indication ?
+ "indication" :
+ "notification",
+ event->notify_rx.conn_handle,
+ event->notify_rx.attr_handle,
+ notifSize);
+
+ alertNotificationClient.OnNotification(event);
+ return 0;
+ }
+ /* Attribute data is contained in event->notify_rx.attr_data. */
+
+ default:
+// NRF_LOG_INFO("Advertising event : %d", event->type);
+ break;
+ }
+ return 0;
+}
+
+int NimbleController::OnDiscoveryEvent(uint16_t i, const ble_gatt_error *error, const ble_gatt_svc *service) {
+ if(service == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("Service Discovery complete");
+ if(currentTimeClient.IsDiscovered()) {
+ ble_gattc_disc_all_chrs(connectionHandle, currentTimeClient.StartHandle(), currentTimeClient.EndHandle(),
+ CurrentTimeCharacteristicDiscoveredCallback, this);
+
+ } else if(alertNotificationClient.IsDiscovered()) {
+ ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(),
+ AlertNotificationCharacteristicDiscoveredCallback, this);
+ }
+ }
+
+ alertNotificationClient.OnDiscoveryEvent(i, error, service);
+ currentTimeClient.OnDiscoveryEvent(i, error, service);
+ return 0;
+}
+
+int NimbleController::OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic) {
+ if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("CTS characteristic Discovery complete");
+ ble_gattc_read(connectionHandle, currentTimeClient.CurrentTimeHandle(), CurrentTimeReadCallback, this);
+ return 0;
+ }
+ return currentTimeClient.OnCharacteristicDiscoveryEvent(connectionHandle, error, characteristic);
+}
+
+int NimbleController::OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic) {
+ if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS characteristic Discovery complete");
+ ble_gattc_disc_all_dscs(connectionHandle,
+ alertNotificationClient.NewAlerthandle(), alertNotificationClient.EndHandle(),
+ AlertNotificationDescriptorDiscoveryEventCallback, this);
+ return 0;
+ }
+ return alertNotificationClient.OnCharacteristicsDiscoveryEvent(connectionHandle, error, characteristic);
+}
+
+int NimbleController::OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute) {
+ currentTimeClient.OnCurrentTimeReadResult(connectionHandle, error, attribute);
+
+ if (alertNotificationClient.IsDiscovered()) {
+ ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(),
+ alertNotificationClient.EndHandle(),
+ AlertNotificationCharacteristicDiscoveredCallback, this);
+ }
+ return 0;
+}
+
+int NimbleController::OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
+ uint16_t characteristicValueHandle,
+ const ble_gatt_dsc *descriptor) {
+ return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor);
+}
+
+void NimbleController::StartDiscovery() {
+ ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this);
+}
+
+
+uint16_t NimbleController::connHandle() {
+ return connectionHandle;
+}
+
diff --git a/src/components/Ble/NimbleController.h b/src/components/Ble/NimbleController.h
new file mode 100644
index 00000000..89fa4250
--- /dev/null
+++ b/src/components/Ble/NimbleController.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <cstdint>
+#include "AlertNotificationService.h"
+#include "AlertNotificationClient.h"
+#include "DeviceInformationService.h"
+#include "CurrentTimeClient.h"
+#include "DfuService.h"
+#include "CurrentTimeService.h"
+#include "MusicService.h"
+#include "BatteryInformationService.h"
+#include "ImmediateAlertService.h"
+#include <host/ble_gap.h>
+
+namespace Pinetime {
+ namespace Drivers {
+ class SpiNorFlash;
+ }
+ namespace Controllers {
+ class DateTime;
+
+ class NimbleController {
+
+ public:
+ NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
+ DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
+ Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash);
+ void Init();
+ void StartAdvertising();
+ int OnGAPEvent(ble_gap_event *event);
+
+ int OnDiscoveryEvent(uint16_t i, const ble_gatt_error *pError, const ble_gatt_svc *pSvc);
+ int OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic);
+ int OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
+ const ble_gatt_chr *characteristic);
+ int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
+ int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
+ uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
+
+ void StartDiscovery();
+
+ Pinetime::Controllers::MusicService& music() {return musicService;};
+
+ uint16_t connHandle();
+
+ private:
+ 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::DfuService dfuService;
+
+ DeviceInformationService deviceInformationService;
+ CurrentTimeClient currentTimeClient;
+ AlertNotificationService anService;
+ AlertNotificationClient alertNotificationClient;
+ CurrentTimeService currentTimeService;
+ MusicService musicService;
+ BatteryInformationService batteryInformationService;
+ ImmediateAlertService immediateAlertService;
+
+ uint8_t addrType; // 1 = Random, 0 = PUBLIC
+ uint16_t connectionHandle = 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}
+ };
+ };
+ }
+}
diff --git a/src/components/Ble/NotificationManager.cpp b/src/components/Ble/NotificationManager.cpp
new file mode 100644
index 00000000..0aea0697
--- /dev/null
+++ b/src/components/Ble/NotificationManager.cpp
@@ -0,0 +1,30 @@
+#include <cstring>
+#include "NotificationManager.h"
+
+using namespace Pinetime::Controllers;
+
+void NotificationManager::Push(Pinetime::Controllers::NotificationManager::Categories category,
+ const char *message, uint8_t currentMessageSize) {
+ // TODO handle edge cases on read/write index
+ auto checkedSize = std::min(currentMessageSize, uint8_t{18});
+ auto& notif = notifications[writeIndex];
+ std::memcpy(notif.message.data(), message, checkedSize);
+ notif.message[checkedSize] = '\0';
+ notif.category = category;
+
+ writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0;
+ if(!empty && writeIndex == readIndex)
+ readIndex = writeIndex + 1;
+}
+
+NotificationManager::Notification Pinetime::Controllers::NotificationManager::Pop() {
+// TODO handle edge cases on read/write index
+ NotificationManager::Notification notification = notifications[readIndex];
+
+ if(readIndex != writeIndex) {
+ readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
+ }
+
+ // TODO Check move optimization on return
+ return notification;
+}
diff --git a/src/components/Ble/NotificationManager.h b/src/components/Ble/NotificationManager.h
new file mode 100644
index 00000000..daa1571b
--- /dev/null
+++ b/src/components/Ble/NotificationManager.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <array>
+
+namespace Pinetime {
+ namespace Controllers {
+ class NotificationManager {
+ public:
+ enum class Categories {Unknown, SimpleAlert, Email, News, IncomingCall, MissedCall, Sms, VoiceMail, Schedule, HighProriotyAlert, InstantMessage };
+ static constexpr uint8_t MessageSize{18};
+
+ struct Notification {
+ std::array<char, MessageSize+1> message;
+ Categories category = Categories::Unknown;
+ };
+
+ void Push(Categories category, const char* message, uint8_t messageSize);
+ Notification Pop();
+
+
+ private:
+ static constexpr uint8_t TotalNbNotifications = 5;
+ std::array<Notification, TotalNbNotifications> notifications;
+ uint8_t readIndex = 0;
+ uint8_t writeIndex = 0;
+ bool empty = true;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/Brightness/BrightnessController.cpp b/src/components/Brightness/BrightnessController.cpp
new file mode 100644
index 00000000..c8825d68
--- /dev/null
+++ b/src/components/Brightness/BrightnessController.cpp
@@ -0,0 +1,70 @@
+#include <hal/nrf_gpio.h>
+#include "BrightnessController.h"
+
+using namespace Pinetime::Controllers;
+
+
+void BrightnessController::Init() {
+ nrf_gpio_cfg_output(pinLcdBacklight1);
+ nrf_gpio_cfg_output(pinLcdBacklight2);
+ nrf_gpio_cfg_output(pinLcdBacklight3);
+ Set(level);
+}
+
+void BrightnessController::Set(BrightnessController::Levels level) {
+ this->level = level;
+ switch(level) {
+ default:
+ case Levels::High:
+ nrf_gpio_pin_clear(pinLcdBacklight1);
+ nrf_gpio_pin_clear(pinLcdBacklight2);
+ nrf_gpio_pin_clear(pinLcdBacklight3);
+ break;
+ case Levels::Medium:
+ nrf_gpio_pin_clear(pinLcdBacklight1);
+ nrf_gpio_pin_clear(pinLcdBacklight2);
+ nrf_gpio_pin_set(pinLcdBacklight3);
+ break;
+ case Levels::Low:
+ nrf_gpio_pin_clear(pinLcdBacklight1);
+ nrf_gpio_pin_set(pinLcdBacklight2);
+ nrf_gpio_pin_set(pinLcdBacklight3);
+ break;
+ case Levels::Off:
+ nrf_gpio_pin_set(pinLcdBacklight1);
+ nrf_gpio_pin_set(pinLcdBacklight2);
+ nrf_gpio_pin_set(pinLcdBacklight3);
+ break;
+ }
+}
+
+void BrightnessController::Lower() {
+ switch(level) {
+ case Levels::High: Set(Levels::Medium); break;
+ case Levels::Medium: Set(Levels::Low); break;
+ case Levels::Low: Set(Levels::Off); break;
+ default: break;
+ }
+}
+
+void BrightnessController::Higher() {
+ switch(level) {
+ case Levels::Off: Set(Levels::Low); break;
+ case Levels::Low: Set(Levels::Medium); break;
+ case Levels::Medium: Set(Levels::High); break;
+ default: break;
+ }
+}
+
+BrightnessController::Levels BrightnessController::Level() const {
+ return level;
+}
+
+void BrightnessController::Backup() {
+ backupLevel = level;
+}
+
+void BrightnessController::Restore() {
+ Set(backupLevel);
+}
+
diff --git a/src/components/Brightness/BrightnessController.h b/src/components/Brightness/BrightnessController.h
new file mode 100644
index 00000000..b8354ec0
--- /dev/null
+++ b/src/components/Brightness/BrightnessController.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <cstdint>
+
+namespace Pinetime {
+ namespace Controllers {
+ class BrightnessController {
+ public:
+ enum class Levels {Off, Low, Medium, High};
+ void Init();
+
+ void Set(Levels level);
+ Levels Level() const;
+ void Lower();
+ void Higher();
+
+ void Backup();
+ void Restore();
+
+ private:
+ static constexpr uint8_t pinLcdBacklight1 = 14;
+ static constexpr uint8_t pinLcdBacklight2 = 22;
+ static constexpr uint8_t pinLcdBacklight3 = 23;
+ Levels level = Levels::High;
+ Levels backupLevel = Levels::High;
+ };
+ }
+}
diff --git a/src/components/DateTime/DateTimeController.cpp b/src/components/DateTime/DateTimeController.cpp
new file mode 100644
index 00000000..30d9c13f
--- /dev/null
+++ b/src/components/DateTime/DateTimeController.cpp
@@ -0,0 +1,66 @@
+#include "DateTimeController.h"
+#include <date/date.h>
+#include <libraries/log/nrf_log.h>
+
+using namespace Pinetime::Controllers;
+
+
+void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t dayOfWeek, uint8_t hour, uint8_t minute,
+ uint8_t second, uint32_t systickCounter) {
+ std::tm tm = { /* .tm_sec = */ second,
+ /* .tm_min = */ minute,
+ /* .tm_hour = */ hour,
+ /* .tm_mday = */ day,
+ /* .tm_mon = */ month - 1,
+ /* .tm_year = */ year - 1900,
+ };
+ tm.tm_isdst = -1; // Use DST value from local time zone
+ currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
+
+ NRF_LOG_INFO("%d %d %d ", day, month, year);
+ NRF_LOG_INFO("%d %d %d ", hour, minute, second);
+ previousSystickCounter = systickCounter;
+
+ UpdateTime(systickCounter);
+ NRF_LOG_INFO("* %d %d %d ", this->hour, this->minute, this->second);
+ NRF_LOG_INFO("* %d %d %d ", this->day, this->month, this->year);
+}
+
+void DateTime::UpdateTime(uint32_t systickCounter) {
+ // Handle systick counter overflow
+ uint32_t systickDelta = 0;
+ if(systickCounter < previousSystickCounter) {
+ systickDelta = 0xffffff - previousSystickCounter;
+ systickDelta += systickCounter + 1;
+ } else {
+ systickDelta = systickCounter - previousSystickCounter;
+ }
+
+ /*
+ * 1000 ms = 1024 ticks
+ */
+ auto correctedDelta = systickDelta / 1024;
+ auto rest = (systickDelta - (correctedDelta*1024));
+ if(systickCounter >= rest) {
+ previousSystickCounter = systickCounter - rest;
+ } else {
+ previousSystickCounter = 0xffffff - (rest - systickCounter);
+ }
+
+ currentDateTime += std::chrono::seconds(correctedDelta);
+ uptime += std::chrono::seconds(correctedDelta);
+
+ auto dp = date::floor<date::days>(currentDateTime);
+ auto time = date::make_time(currentDateTime-dp);
+ auto yearMonthDay = date::year_month_day(dp);
+
+ year = (int)yearMonthDay.year();
+ month = static_cast<Months>((unsigned)yearMonthDay.month());
+ day = (unsigned)yearMonthDay.day();
+ dayOfWeek = static_cast<Days>(date::weekday(yearMonthDay).iso_encoding());
+
+ hour = time.hours().count();
+ minute = time.minutes().count();
+ second = time.seconds().count();
+}
+
diff --git a/src/components/DateTime/DateTimeController.h b/src/components/DateTime/DateTimeController.h
new file mode 100644
index 00000000..d6020745
--- /dev/null
+++ b/src/components/DateTime/DateTimeController.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+
+namespace Pinetime {
+ namespace Controllers {
+ class DateTime {
+ public:
+ enum class Days : uint8_t {Unknown, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
+ enum class Months : uint8_t {Unknown, January, February, March, April, May, June, July, August, September, October, November, December};
+
+ void SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t dayOfWeek, uint8_t hour, uint8_t minute, uint8_t second, uint32_t systickCounter);
+ void UpdateTime(uint32_t systickCounter);
+ uint16_t Year() const { return year; }
+ Months Month() const { return month; }
+ uint8_t Day() const { return day; }
+ Days DayOfWeek() const { return dayOfWeek; }
+ uint8_t Hours() const { return hour; }
+ uint8_t Minutes() const { return minute; }
+ uint8_t Seconds() const { return second; }
+
+ std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const { return currentDateTime; }
+ std::chrono::seconds Uptime() const { return uptime; }
+ private:
+ uint16_t year = 0;
+ Months month = Months::Unknown;
+ uint8_t day = 0;
+ Days dayOfWeek = Days::Unknown;
+ uint8_t hour = 0;
+ uint8_t minute = 0;
+ uint8_t second = 0;
+
+ uint32_t previousSystickCounter = 0;
+ std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> currentDateTime;
+ std::chrono::seconds uptime {0};
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/FirmwareValidator/FirmwareValidator.cpp b/src/components/FirmwareValidator/FirmwareValidator.cpp
new file mode 100644
index 00000000..244d5c06
--- /dev/null
+++ b/src/components/FirmwareValidator/FirmwareValidator.cpp
@@ -0,0 +1,20 @@
+#include <drivers/InternalFlash.h>
+#include <hal/nrf_rtc.h>
+
+#include "FirmwareValidator.h"
+
+using namespace Pinetime::Controllers;
+
+bool FirmwareValidator::IsValidated() const {
+ auto* imageOkPtr = reinterpret_cast<uint32_t *>(validBitAdress);
+ return (*imageOkPtr) == validBitValue;
+}
+
+void FirmwareValidator::Validate() {
+ if(!IsValidated())
+ Pinetime::Drivers::InternalFlash::WriteWord(validBitAdress, validBitValue);
+}
+
+void FirmwareValidator::Reset() {
+ NVIC_SystemReset();
+}
diff --git a/src/components/FirmwareValidator/FirmwareValidator.h b/src/components/FirmwareValidator/FirmwareValidator.h
new file mode 100644
index 00000000..aa576d88
--- /dev/null
+++ b/src/components/FirmwareValidator/FirmwareValidator.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <cstdint>
+
+namespace Pinetime {
+ namespace Controllers {
+ class FirmwareValidator {
+ public:
+ void Validate();
+ bool IsValidated() const;
+
+ void Reset();
+ private:
+ static constexpr uint32_t validBitAdress {0x7BFE8};
+ static constexpr uint32_t validBitValue {1};
+ };
+ }
+}
diff --git a/src/components/Gfx/Gfx.cpp b/src/components/Gfx/Gfx.cpp
new file mode 100644
index 00000000..3c5dbfb7
--- /dev/null
+++ b/src/components/Gfx/Gfx.cpp
@@ -0,0 +1,207 @@
+#include <libraries/svc/nrf_svci.h>
+#include <FreeRTOS.h>
+#include <task.h>
+#include "Gfx.h"
+#include "../../drivers/St7789.h"
+using namespace Pinetime::Components;
+
+Gfx::Gfx(Pinetime::Drivers::St7789 &lcd) : lcd{lcd} {
+}
+
+void Gfx::Init() {
+
+}
+
+void Gfx::ClearScreen() {
+ SetBackgroundColor(0x0000);
+
+ state.remainingIterations = 240 + 1;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(0, 0, width, height);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
+ WaitTransfertFinished();
+
+}
+
+void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
+ SetBackgroundColor(color);
+
+ state.remainingIterations = h;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.color = color;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(x, y, w, h);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
+
+ WaitTransfertFinished();
+}
+
+void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
+ state.remainingIterations = h;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.color = 0x00;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(x, y, w, h);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(b), width * 2);
+
+ WaitTransfertFinished();
+}
+
+void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) {
+ if (y > (height - p_font->height)) {
+ // Not enough space to write even single char.
+ return;
+ }
+
+ uint8_t current_x = x;
+ uint8_t current_y = y;
+
+ for (size_t i = 0; text[i] != '\0'; i++) {
+ if (text[i] == '\n') {
+ current_x = x;
+ current_y += p_font->height + p_font->height / 10;
+ } else {
+ DrawChar(p_font, (uint8_t) text[i], &current_x, current_y, color);
+ }
+
+ uint8_t char_idx = text[i] - p_font->startChar;
+ uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
+
+ if (current_x > (width - char_width)) {
+ if (wrap) {
+ current_x = x;
+ current_y += p_font->height + p_font->height / 10;
+ } else {
+ break;
+ }
+
+ if (y > (height - p_font->height)) {
+ break;
+ }
+ }
+ }
+}
+
+void Gfx::DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color) {
+ uint8_t char_idx = c - font->startChar;
+ uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
+ uint16_t bg = 0x0000;
+
+ if (c == ' ') {
+ *x += font->height / 2;
+ return;
+ }
+
+ // Build first line
+ for (uint16_t j = 0; j < bytes_in_line; j++) {
+ for (uint8_t k = 0; k < 8; k++) {
+ if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
+ buffer[(j*8)+k] = color;
+ }
+ else {
+ buffer[(j*8)+k] = bg;
+ }
+ }
+ }
+
+ state.remainingIterations = font->height + 0;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::DrawChar;
+ state.font = const_cast<FONT_INFO *>(font);
+ state.character = c;
+ state.color = color;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(*x, y, bytes_in_line*8, font->height);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(&buffer), bytes_in_line*8*2);
+ WaitTransfertFinished();
+
+ *x += font->charInfo[char_idx].widthBits + font->spacePixels;
+}
+
+void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
+ lcd.DrawPixel(x, y, color);
+}
+
+void Gfx::Sleep() {
+ lcd.Sleep();
+}
+
+void Gfx::Wakeup() {
+ lcd.Wakeup();
+}
+
+void Gfx::SetBackgroundColor(uint16_t color) {
+ for(int i = 0; i < width; i++) {
+ buffer[i] = color;
+ }
+}
+
+bool Gfx::GetNextBuffer(uint8_t **data, size_t &size) {
+ if(!state.busy) return false;
+ state.remainingIterations--;
+ if (state.remainingIterations == 0) {
+ state.busy = false;
+ NotifyEndOfTransfert(state.taskToNotify);
+ return false;
+ }
+
+ if(state.action == Action::FillRectangle) {
+ *data = reinterpret_cast<uint8_t *>(buffer);
+ size = width * 2;
+ } else if(state.action == Action::DrawChar) {
+ uint16_t bg = 0x0000;
+ uint8_t char_idx = state.character - state.font->startChar;
+ uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
+
+ for (uint16_t j = 0; j < bytes_in_line; j++) {
+ for (uint8_t k = 0; k < 8; k++) {
+ if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration+1) * bytes_in_line) + j]) {
+ buffer[(j*8)+k] = state.color;
+ }
+ else {
+ buffer[(j*8)+k] = bg;
+ }
+ }
+ }
+
+ *data = reinterpret_cast<uint8_t *>(buffer);
+ size = bytes_in_line*8*2;
+ }
+
+ state.currentIteration++;
+
+ return true;
+}
+
+void Gfx::NotifyEndOfTransfert(TaskHandle_t task) {
+ if(task != nullptr) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ }
+}
+
+void Gfx::WaitTransfertFinished() const {
+ ulTaskNotifyTake(pdTRUE, 500);
+}
+
+void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
+ lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
+}
+
+void Gfx::SetScrollStartLine(uint16_t line) {
+ lcd.VerticalScrollStartAddress(line);
+}
+
diff --git a/src/components/Gfx/Gfx.h b/src/components/Gfx/Gfx.h
new file mode 100644
index 00000000..091f06f5
--- /dev/null
+++ b/src/components/Gfx/Gfx.h
@@ -0,0 +1,60 @@
+#pragma once
+#include <cstdint>
+#include <nrf_font.h>
+#include <drivers/BufferProvider.h>
+#include <FreeRTOS.h>
+#include <task.h>
+
+
+namespace Pinetime {
+ namespace Drivers {
+ class St7789;
+ }
+ namespace Components {
+ class Gfx : public Pinetime::Drivers::BufferProvider {
+ public:
+ explicit Gfx(Drivers::St7789& lcd);
+ void Init();
+ void ClearScreen();
+ void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO *p_font, bool wrap);
+ void DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color);
+ void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
+ void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
+ void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
+ void SetScrollStartLine(uint16_t line);
+
+
+ void Sleep();
+ void Wakeup();
+ bool GetNextBuffer(uint8_t **buffer, size_t &size) override;
+ void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
+
+
+ private:
+ static constexpr uint8_t width = 240;
+ static constexpr uint8_t height = 240;
+
+ enum class Action { None, FillRectangle, DrawChar};
+ struct State {
+ State() : busy{false}, action{Action::None}, remainingIterations{0}, currentIteration{0} {}
+ volatile bool busy;
+ volatile Action action;
+ volatile uint16_t remainingIterations;
+ volatile uint16_t currentIteration;
+ volatile FONT_INFO *font;
+ volatile uint16_t color;
+ volatile uint8_t character;
+ volatile TaskHandle_t taskToNotify = nullptr;
+ };
+
+ volatile State state;
+
+ uint16_t buffer[width]; // 1 line buffer
+ Drivers::St7789& lcd;
+
+ void SetBackgroundColor(uint16_t color);
+ void WaitTransfertFinished() const;
+ void NotifyEndOfTransfert(TaskHandle_t task);
+ };
+ }
+}