diff options
Diffstat (limited to 'src/components')
37 files changed, 3269 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..abe41099 --- /dev/null +++ b/src/components/ble/AlertNotificationClient.cpp @@ -0,0 +1,194 @@ +#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; + +namespace { + int + OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, + void *arg) { + auto client = static_cast<AlertNotificationClient *>(arg); + return client->OnDiscoveryEvent(conn_handle, error, service); + } + + int OnAlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) { + auto client = static_cast<AlertNotificationClient *>(arg); + return client->OnCharacteristicsDiscoveryEvent(conn_handle, error, chr); + } + + int OnAlertNotificationDescriptorDiscoveryEventCallback(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<AlertNotificationClient *>(arg); + return client->OnDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc); + } + + int 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 ¬ificationManager) : + 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) { + if (isDiscovered) { + NRF_LOG_INFO("ANS Discovery found, starting characteristics discovery"); + + ble_gattc_disc_all_chrs(connectionHandle, ansStartHandle, ansEndHandle, + OnAlertNotificationCharacteristicDiscoveredCallback, this); + } else { + NRF_LOG_INFO("ANS not found"); + onServiceDiscovered(connectionHandle); + } + return true; + } + + if (service != nullptr && ble_uuid_cmp(((ble_uuid_t *) &ansServiceUuid), &service->uuid.u) == 0) { + NRF_LOG_INFO("ANS discovered : 0x%x - 0x%x", service->start_handle, service->end_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"); + onServiceDiscovered(connectionHandle); + return 0; + } + + if (characteristic == nullptr && error->status == BLE_HS_EDONE) { + NRF_LOG_INFO("ANS Characteristic discovery complete"); + if (isCharacteristicDiscovered) { + ble_gattc_disc_all_dscs(connectionHandle, + newAlertHandle, ansEndHandle, + OnAlertNotificationDescriptorDiscoveryEventCallback, this); + } else + onServiceDiscovered(connectionHandle); + } 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; + isCharacteristicDiscovered = true; + } 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"); + } + onServiceDiscovered(connectionHandle); + + 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; + isDescriptorFound = true; + uint8_t value[2]; + value[0] = 1; + value[1] = 0; + ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this); + } + } + } else { + if (!isDescriptorFound) + onServiceDiscovered(connectionHandle); + } + return 0; +} + +void AlertNotificationClient::OnNotification(ble_gap_event *event) { + if (event->notify_rx.attr_handle == newAlertHandle) { + constexpr size_t stringTerminatorSize = 1; // end of string '\0' + constexpr size_t headerSize = 3; + const auto maxMessageSize{NotificationManager::MaximumMessageSize()}; + const auto maxBufferSize{maxMessageSize + headerSize}; + + const auto dbgPacketLen = OS_MBUF_PKTLEN(event->notify_rx.om); + size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize); + auto messageSize = min(maxMessageSize, (bufferSize - headerSize)); + + NotificationManager::Notification notif; + os_mbuf_copydata(event->notify_rx.om, headerSize, messageSize - 1, notif.message.data()); + notif.message[messageSize - 1] = '\0'; + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + notificationManager.Push(std::move(notif)); + + systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification); + } +} + +void AlertNotificationClient::Reset() { + ansStartHandle = 0; + ansEndHandle = 0; + supportedNewAlertCategoryHandle = 0; + supportedUnreadAlertCategoryHandle = 0; + newAlertHandle = 0; + newAlertDescriptorHandle = 0; + newAlertDefHandle = 0; + unreadAlertStatusHandle = 0; + controlPointHandle = 0; + isDiscovered = false; + isCharacteristicDiscovered = false; + isDescriptorFound = false; +} + +void AlertNotificationClient::Discover(uint16_t connectionHandle, std::function<void(uint16_t)> onServiceDiscovered) { + NRF_LOG_INFO("[ANS] Starting discovery"); + this->onServiceDiscovered = onServiceDiscovered; + ble_gattc_disc_svc_by_uuid(connectionHandle, &ansServiceUuid.u, OnDiscoveryEventCallback, this); +} diff --git a/src/components/ble/AlertNotificationClient.h b/src/components/ble/AlertNotificationClient.h new file mode 100644 index 00000000..bc0df51e --- /dev/null +++ b/src/components/ble/AlertNotificationClient.h @@ -0,0 +1,76 @@ +#pragma once + +#include <cstdint> +#include <array> +#include <host/ble_gap.h> +#include "BleClient.h" + + +namespace Pinetime { + namespace Controllers { + class AlertNotificationClient : public BleClient { + public: + explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask, + Pinetime::Controllers::NotificationManager ¬ificationManager); + + 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); + void Reset(); + void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) override; + + 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 = 0; + uint16_t ansEndHandle = 0; + uint16_t supportedNewAlertCategoryHandle = 0; + uint16_t supportedUnreadAlertCategoryHandle = 0; + uint16_t newAlertHandle = 0; + uint16_t newAlertDescriptorHandle = 0; + uint16_t newAlertDefHandle = 0; + uint16_t unreadAlertStatusHandle = 0; + uint16_t controlPointHandle = 0; + bool isDiscovered = false; + Pinetime::System::SystemTask &systemTask; + Pinetime::Controllers::NotificationManager ¬ificationManager; + std::function<void(uint16_t)> onServiceDiscovered; + bool isCharacteristicDiscovered = false; + bool isDescriptorFound = false; + }; + } +}
\ 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..32711b92 --- /dev/null +++ b/src/components/ble/AlertNotificationService.cpp @@ -0,0 +1,75 @@ + +#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 + }, + }, systemTask{systemTask}, 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) { + constexpr size_t stringTerminatorSize = 1; // end of string '\0' + constexpr size_t headerSize = 3; + const auto maxMessageSize {NotificationManager::MaximumMessageSize()}; + const auto maxBufferSize{maxMessageSize + headerSize}; + + const auto dbgPacketLen = OS_MBUF_PKTLEN(ctxt->om); + size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize); + auto messageSize = min(maxMessageSize, (bufferSize-headerSize)); + + NotificationManager::Notification notif; + os_mbuf_copydata(ctxt->om, headerSize, messageSize-1, notif.message.data()); + notif.message[messageSize-1] = '\0'; + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + notificationManager.Push(std::move(notif)); + + 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..1b8c4989 --- /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 ¬ificationManager); + 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 &systemTask; + NotificationManager ¬ificationManager; + }; + } +} diff --git a/src/components/ble/BatteryInformationService.cpp b/src/components/ble/BatteryInformationService.cpp new file mode 100644 index 00000000..f7d895c2 --- /dev/null +++ b/src/components/ble/BatteryInformationService.cpp @@ -0,0 +1,62 @@ +#include "BatteryInformationService.h" +#include "components/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/BleClient.h b/src/components/ble/BleClient.h new file mode 100644 index 00000000..559f6c8d --- /dev/null +++ b/src/components/ble/BleClient.h @@ -0,0 +1,12 @@ +#pragma once + +#include <functional> + +namespace Pinetime { + namespace Controllers{ + class BleClient { + public: + virtual void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) = 0; + }; + } +}
\ No newline at end of file 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..92f9374b --- /dev/null +++ b/src/components/ble/CurrentTimeClient.cpp @@ -0,0 +1,111 @@ +#include <hal/nrf_rtc.h> +#include "CurrentTimeClient.h" + +using namespace Pinetime::Controllers; + +constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid; +constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid; + +namespace { + int OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) { + auto client = static_cast<CurrentTimeClient *>(arg); + return client->OnDiscoveryEvent(conn_handle, error, service); + } + + int OnCurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) { + auto client = static_cast<CurrentTimeClient *>(arg); + return client->OnCharacteristicDiscoveryEvent(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<CurrentTimeClient *>(arg); + return client->OnCurrentTimeReadResult(conn_handle, error, attr); + } +} + +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) { + if (isDiscovered) { + NRF_LOG_INFO("CTS found, starting characteristics discovery"); + + ble_gattc_disc_all_chrs(connectionHandle, ctsStartHandle, ctsEndHandle, + OnCurrentTimeCharacteristicDiscoveredCallback, this); + } else { + NRF_LOG_INFO("CTS not found"); + onServiceDiscovered(connectionHandle); + } + return true; + } + + if (service != nullptr && ble_uuid_cmp(((ble_uuid_t *) &ctsServiceUuid), &service->uuid.u) == 0) { + NRF_LOG_INFO("CTS discovered : 0x%x - 0x%x", service->start_handle, service->end_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) { + if (isCharacteristicDiscovered) { + NRF_LOG_INFO("CTS Characteristic discovery complete, fetching time"); + ble_gattc_read(conn_handle, currentTimeHandle, CurrentTimeReadCallback, this); + } else { + NRF_LOG_INFO("CTS Characteristic discovery unsuccessful"); + onServiceDiscovered(conn_handle); + } + + return 0; + } + + if (characteristic != nullptr && + ble_uuid_cmp(((ble_uuid_t *) ¤tTimeCharacteristicUuid), &characteristic->uuid.u) == 0) { + NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle); + isCharacteristicDiscovered = true; + 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); + } + + onServiceDiscovered(conn_handle); + return 0; +} + +void CurrentTimeClient::Reset() { + isDiscovered = false; + isCharacteristicDiscovered = false; +} + +void CurrentTimeClient::Discover(uint16_t connectionHandle, std::function<void(uint16_t)> onServiceDiscovered) { + NRF_LOG_INFO("[CTS] Starting discovery"); + this->onServiceDiscovered = onServiceDiscovered; + ble_gattc_disc_svc_by_uuid(connectionHandle, &ctsServiceUuid.u, OnDiscoveryEventCallback, this); +} diff --git a/src/components/ble/CurrentTimeClient.h b/src/components/ble/CurrentTimeClient.h new file mode 100644 index 00000000..93139399 --- /dev/null +++ b/src/components/ble/CurrentTimeClient.h @@ -0,0 +1,59 @@ +#pragma once +#include <cstdint> +#include <array> + +#include "components/datetime/DateTimeController.h" +#include "BleClient.h" +#include <host/ble_gap.h> + +namespace Pinetime { + namespace Controllers { + + class CurrentTimeClient : public BleClient { + public: + explicit CurrentTimeClient(DateTime& dateTimeController); + void Init(); + void Reset(); + 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); + static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; } + static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; } + void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) override; + + 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 + }; + + DateTime& dateTimeController; + bool isDiscovered = false; + uint16_t ctsStartHandle; + uint16_t ctsEndHandle; + + bool isCharacteristicDiscovered = false; + uint16_t currentTimeHandle; + std::function<void(uint16_t)> onServiceDiscovered; + }; + } +}
\ 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, ¤tDateTime, 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..a6be9647 --- /dev/null +++ b/src/components/ble/CurrentTimeService.h @@ -0,0 +1,49 @@ +#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..4dec5141 --- /dev/null +++ b/src/components/ble/DfuService.cpp @@ -0,0 +1,441 @@ +#include <cstring> + +#include "components/ble/BleController.h" +#include "systemtask/SystemTask.h" +#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..e2cee308 --- /dev/null +++ b/src/components/ble/ImmediateAlertService.cpp @@ -0,0 +1,82 @@ +#include <systemtask/SystemTask.h> +#include <cstring> +#include "ImmediateAlertService.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 ¬ificationManager) : + 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::Notification notif; + std::memcpy(notif.message.data(), alertString, strlen(alertString)); + notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert; + notificationManager.Push(std::move(notif)); + + 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 ¬ificationManager); + 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..84f2972b --- /dev/null +++ b/src/components/ble/MusicService.cpp @@ -0,0 +1,225 @@ +/* Copyright (C) 2020 JF, Adam Pigg, Avamander + + This file is part of InfiniTime. + + InfiniTime is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InfiniTime is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ +#include <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]; + msPositionCharUuid.value[11] = msPositionCharId[0]; + msPositionCharUuid.value[12] = msPositionCharId[1]; + msTotalLengthCharUuid.value[11] = msTotalLengthCharId[0]; + msTotalLengthCharUuid.value[12] = msTotalLengthCharId[1]; + msTrackNumberCharUuid.value[11] = msTrackNumberCharId[0]; + msTrackNumberCharUuid.value[12] = msTrackNumberCharId[1]; + msTrackTotalCharUuid.value[11] = msTrackTotalCharId[0]; + msTrackTotalCharUuid.value[12] = msTrackTotalCharId[1]; + msPlaybackSpeedCharUuid.value[11] = msPlaybackSpeedCharId[0]; + msPlaybackSpeedCharUuid.value[12] = msPlaybackSpeedCharId[1]; + msRepeatCharUuid.value[11] = msRepeatCharId[0]; + msRepeatCharUuid.value[12] = msRepeatCharId[1]; + msShuffleCharUuid.value[11] = msShuffleCharId[0]; + msShuffleCharUuid.value[12] = msShuffleCharId[1]; + + characteristicDefinition[0] = {.uuid = (ble_uuid_t *) (&msEventCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_NOTIFY, + .val_handle = &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] = {.uuid = (ble_uuid_t *) (&msPositionCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[6] = {.uuid = (ble_uuid_t *) (&msTotalLengthCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[7] = {.uuid = (ble_uuid_t *) (&msTotalLengthCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[8] = {.uuid = (ble_uuid_t *) (&msTrackNumberCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[9] = {.uuid = (ble_uuid_t *) (&msTrackTotalCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[10] = {.uuid = (ble_uuid_t *) (&msPlaybackSpeedCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[11] = {.uuid = (ble_uuid_t *) (&msRepeatCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[12] = {.uuid = (ble_uuid_t *) (&msShuffleCharUuid), + .access_cb = MSCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ + }; + characteristicDefinition[13] = {0}; + + serviceDefinition[0] = { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = (ble_uuid_t *) &msUuid, + .characteristics = characteristicDefinition + }; + serviceDefinition[1] = {0}; + + artistName = "Waiting for"; + albumName = ""; + trackName = "track information..."; + playing = false; + repeat = false; + shuffle = false; + playbackSpeed = 1.0f; + trackProgress = 0; + trackLength = 0; +} + +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) { + artistName = s; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackCharUuid) == 0) { + trackName = s; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msAlbumCharUuid) == 0) { + albumName = s; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msStatusCharUuid) == 0) { + playing = s[0]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msRepeatCharUuid) == 0) { + repeat = s[0]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msShuffleCharUuid) == 0) { + shuffle = s[0]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msPositionCharUuid) == 0) { + trackProgress = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTotalLengthCharUuid) == 0) { + trackLength = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackNumberCharUuid) == 0) { + trackNumber = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msTrackTotalCharUuid) == 0) { + tracksTotal = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]; + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *) &msPlaybackSpeedCharUuid) == 0) { + playbackSpeed = static_cast<float>(((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3])) / 100.0f; + } + } + return 0; +} + +std::string Pinetime::Controllers::MusicService::getAlbum() { + return albumName; +} + +std::string Pinetime::Controllers::MusicService::getArtist() { + return artistName; +} + +std::string Pinetime::Controllers::MusicService::getTrack() { + return trackName; +} + +bool Pinetime::Controllers::MusicService::isPlaying() { + return playing; +} + +float Pinetime::Controllers::MusicService::getPlaybackSpeed() { + return playbackSpeed; +} + +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, eventHandle, om); +} + +int Pinetime::Controllers::MusicService::getProgress() { + return trackProgress; +} + +int Pinetime::Controllers::MusicService::getTrackLength() { + return trackLength; +} + diff --git a/src/components/ble/MusicService.h b/src/components/ble/MusicService.h new file mode 100644 index 00000000..b365909b --- /dev/null +++ b/src/components/ble/MusicService.h @@ -0,0 +1,166 @@ +/* Copyright (C) 2020 JF, Adam Pigg, Avamander + + This file is part of InfiniTime. + + InfiniTime is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + InfiniTime is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ +#pragma once + +#include <cstdint> +#include <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: + explicit MusicService(Pinetime::System::SystemTask &system); + + void Init(); + + int OnCommand(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt); + + void event(char event); + + std::string getArtist(); + + std::string getTrack(); + + std::string getAlbum(); + + int getProgress(); + + int getTrackLength(); + + float getPlaybackSpeed(); + + bool isPlaying(); + + 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; + + enum MusicStatus { + NotPlaying = 0x00, + 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}; + static constexpr uint8_t msPositionCharId[2] = {0x00, 0x07}; + static constexpr uint8_t msTotalLengthCharId[2] = {0x00, 0x08}; + static constexpr uint8_t msTrackNumberCharId[2] = {0x00, 0x09}; + static constexpr uint8_t msTrackTotalCharId[2] = {0x00, 0x0a}; + static constexpr uint8_t msPlaybackSpeedCharId[2] = {0x00, 0x0b}; + static constexpr uint8_t msRepeatCharId[2] = {0x00, 0x0c}; + static constexpr uint8_t msShuffleCharId[2] = {0x00, 0x0d}; + + 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 + }; + ble_uuid128_t msPositionCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msTotalLengthCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msTrackNumberCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msTrackTotalCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msPlaybackSpeedCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msRepeatCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + ble_uuid128_t msShuffleCharUuid{ + .u = {.type = BLE_UUID_TYPE_128}, + .value = MUSIC_SERVICE_UUID_BASE + }; + + struct ble_gatt_chr_def characteristicDefinition[14]; + struct ble_gatt_svc_def serviceDefinition[2]; + + uint16_t eventHandle; + + std::string artistName; + std::string albumName; + std::string trackName; + + bool playing; + + int trackProgress; + int trackLength; + int trackNumber; + int tracksTotal; + + float playbackSpeed; + + bool repeat; + bool shuffle; + + 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..af7f4029 --- /dev/null +++ b/src/components/ble/NimbleController.cpp @@ -0,0 +1,237 @@ +#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; + +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}, + serviceDiscovery({¤tTimeClient, &alertNotificationClient}) { +} + +int GAPEventCallback(struct ble_gap_event *event, void *arg) { + auto nimbleController = static_cast<NimbleController*>(arg); + return nimbleController->OnGAPEvent(event); +} + +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(bleController.IsConnected() || ble_gap_conn_active() || 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 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. */ + currentTimeClient.Reset(); + alertNotificationClient.Reset(); + 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; +} + +void NimbleController::StartDiscovery() { + serviceDiscovery.StartDiscovery(connectionHandle); +} + + +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..8ddec1f9 --- /dev/null +++ b/src/components/ble/NimbleController.h @@ -0,0 +1,79 @@ +#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 "ServiceDiscovery.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} + }; + + ServiceDiscovery serviceDiscovery; + }; + } +} diff --git a/src/components/ble/NotificationManager.cpp b/src/components/ble/NotificationManager.cpp new file mode 100644 index 00000000..67711723 --- /dev/null +++ b/src/components/ble/NotificationManager.cpp @@ -0,0 +1,81 @@ +#include <cstring> +#include <algorithm> +#include "NotificationManager.h" + +using namespace Pinetime::Controllers; + +constexpr uint8_t NotificationManager::MessageSize; + + +void NotificationManager::Push(NotificationManager::Notification &¬if) { + notif.id = GetNextId(); + notif.valid = true; + notifications[writeIndex] = std::move(notif); + writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0; + if(!empty) + readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0; + else empty = false; + + newNotification = true; +} + +NotificationManager::Notification NotificationManager::GetLastNotification() { + NotificationManager::Notification notification = notifications[readIndex]; + notification.index = 1; + return notification; +} + +NotificationManager::Notification::Id NotificationManager::GetNextId() { + return nextId++; +} + +NotificationManager::Notification NotificationManager::GetNext(NotificationManager::Notification::Id id) { + auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;}); + if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{}; + + auto& lastNotification = notifications[readIndex]; + + NotificationManager::Notification result; + + if(currentIterator == (notifications.end()-1)) + result = *(notifications.begin()); + else + result = *(currentIterator+1); + + if(result.id <= id) return {}; + + result.index = (lastNotification.id - result.id)+1; + return result; +} + +NotificationManager::Notification NotificationManager::GetPrevious(NotificationManager::Notification::Id id) { + auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;}); + if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{}; + + auto& lastNotification = notifications[readIndex]; + + NotificationManager::Notification result; + + if(currentIterator == notifications.begin()) + result = *(notifications.end()-1); + else + result = *(currentIterator-1); + + if(result.id >= id) return {}; + + result.index = (lastNotification.id - result.id)+1; + return result; +} + +bool NotificationManager::AreNewNotificationsAvailable() { + return newNotification; +} + +bool NotificationManager::ClearNewNotificationFlag() { + return newNotification.exchange(false); +} + +size_t NotificationManager::NbNotifications() const { + return std::count_if(notifications.begin(), notifications.end(), [](const Notification& n){ return n.valid;}); +} + diff --git a/src/components/ble/NotificationManager.h b/src/components/ble/NotificationManager.h new file mode 100644 index 00000000..49fe8306 --- /dev/null +++ b/src/components/ble/NotificationManager.h @@ -0,0 +1,43 @@ +#pragma once + +#include <array> +#include <atomic> + +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{100}; + + struct Notification { + using Id = uint8_t; + Id id; + bool valid = false; + uint8_t index; + std::array<char, MessageSize+1> message; + Categories category = Categories::Unknown; + }; + Notification::Id nextId {0}; + + void Push(Notification&& notif); + Notification GetLastNotification(); + Notification GetNext(Notification::Id id); + Notification GetPrevious(Notification::Id id); + bool ClearNewNotificationFlag(); + bool AreNewNotificationsAvailable(); + + static constexpr uint8_t MaximumMessageSize() { return MessageSize; }; + size_t NbNotifications() const; + + private: + Notification::Id GetNextId(); + static constexpr uint8_t TotalNbNotifications = 5; + std::array<Notification, TotalNbNotifications> notifications; + uint8_t readIndex = 0; + uint8_t writeIndex = 0; + bool empty = true; + std::atomic<bool> newNotification{false}; + }; + } +}
\ No newline at end of file diff --git a/src/components/ble/ServiceDiscovery.cpp b/src/components/ble/ServiceDiscovery.cpp new file mode 100644 index 00000000..4b29d89c --- /dev/null +++ b/src/components/ble/ServiceDiscovery.cpp @@ -0,0 +1,31 @@ +#include <libraries/log/nrf_log.h> +#include "ServiceDiscovery.h" +using namespace Pinetime::Controllers; + +ServiceDiscovery::ServiceDiscovery(std::array<BleClient*, 2>&& clients) : clients{clients} { + +} + +void ServiceDiscovery::StartDiscovery(uint16_t connectionHandle) { + NRF_LOG_INFO("[Discovery] Starting discovery"); + clientIterator = clients.begin(); + DiscoverNextService(connectionHandle); +} + +void ServiceDiscovery::OnServiceDiscovered(uint16_t connectionHandle) { + clientIterator++; + if(clientIterator != clients.end()) { + DiscoverNextService(connectionHandle); + } else { + NRF_LOG_INFO("End of service discovery"); + } +} + +void ServiceDiscovery::DiscoverNextService(uint16_t connectionHandle) { + NRF_LOG_INFO("[Discovery] Discover next service"); + + auto discoverNextService = [this](uint16_t connectionHandle){ + this->OnServiceDiscovered(connectionHandle); + }; + (*clientIterator)->Discover(connectionHandle, discoverNextService); +}
\ No newline at end of file diff --git a/src/components/ble/ServiceDiscovery.h b/src/components/ble/ServiceDiscovery.h new file mode 100644 index 00000000..c86fc4ec --- /dev/null +++ b/src/components/ble/ServiceDiscovery.h @@ -0,0 +1,24 @@ +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include "BleClient.h" + +namespace Pinetime { + namespace Controllers { + class ServiceDiscovery { + public: + ServiceDiscovery(std::array<BleClient*, 2>&& bleClients); + + void StartDiscovery(uint16_t connectionHandle); + + + private: + BleClient** clientIterator; + std::array<BleClient*, 2> clients; + void OnServiceDiscovered(uint16_t connectionHandle); + void DiscoverNextService(uint16_t connectionHandle); + }; + } +} 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..bab7c8d9 --- /dev/null +++ b/src/components/gfx/Gfx.cpp @@ -0,0 +1,206 @@ +#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); + WaitTransferFinished(); + +} + +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); + + WaitTransferFinished(); +} + +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); + + WaitTransferFinished(); +} + +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], ¤t_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); + WaitTransferFinished(); + + *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; + NotifyEndOfTransfer(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::NotifyEndOfTransfer(TaskHandle_t task) { + if(task != nullptr) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } +} + +void Gfx::WaitTransferFinished() 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..eba6319f --- /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 WaitTransferFinished() const; + void NotifyEndOfTransfer(TaskHandle_t task); + }; + } +} |