Renamed Components/ to components/
This commit is contained in:
48
src/components/Battery/BatteryController.cpp
Normal file
48
src/components/Battery/BatteryController.cpp
Normal file
@@ -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) {
|
||||
|
||||
}
|
27
src/components/Battery/BatteryController.h
Normal file
27
src/components/Battery/BatteryController.h
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
145
src/components/Ble/AlertNotificationClient.cpp
Normal file
145
src/components/Ble/AlertNotificationClient.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include "NotificationManager.h"
|
||||
|
||||
#include "AlertNotificationClient.h"
|
||||
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
|
||||
|
||||
constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
|
||||
constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid ;
|
||||
constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
|
||||
constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
|
||||
constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
|
||||
|
||||
int Pinetime::Controllers::NewAlertSubcribeCallback(uint16_t conn_handle,
|
||||
const struct ble_gatt_error *error,
|
||||
struct ble_gatt_attr *attr,
|
||||
void *arg) {
|
||||
auto client = static_cast<AlertNotificationClient*>(arg);
|
||||
return client->OnNewAlertSubcribe(conn_handle, error, attr);
|
||||
}
|
||||
|
||||
AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
|
||||
Pinetime::Controllers::NotificationManager& notificationManager) :
|
||||
systemTask{systemTask}, notificationManager{notificationManager}{
|
||||
|
||||
}
|
||||
|
||||
bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("ANS Discovery complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ansServiceUuid), &service->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS discovered : 0x%x", service->start_handle);
|
||||
ansStartHandle = service->start_handle;
|
||||
ansEndHandle = service->end_handle;
|
||||
isDiscovered = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic) {
|
||||
if(error->status != 0 && error->status != BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovery ERROR");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovery complete");
|
||||
} else {
|
||||
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedNewAlertCategoryUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : supportedNewAlertCategoryUuid");
|
||||
supportedNewAlertCategoryHandle = characteristic->val_handle;
|
||||
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&supportedUnreadAlertCategoryUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : supportedUnreadAlertCategoryUuid");
|
||||
supportedUnreadAlertCategoryHandle = characteristic->val_handle;
|
||||
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
|
||||
newAlertHandle = characteristic->val_handle;
|
||||
newAlertDefHandle = characteristic->def_handle;
|
||||
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&unreadAlertStatusUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : unreadAlertStatusUuid");
|
||||
unreadAlertStatusHandle = characteristic->val_handle;
|
||||
} else if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)&controlPointUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
|
||||
controlPointHandle = characteristic->val_handle;
|
||||
}else
|
||||
NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
ble_gatt_attr *attribute) {
|
||||
if(error->status == 0) {
|
||||
NRF_LOG_INFO("ANS New alert subscribe OK");
|
||||
} else {
|
||||
NRF_LOG_INFO("ANS New alert subscribe ERROR");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
uint16_t characteristicValueHandle,
|
||||
const ble_gatt_dsc *descriptor) {
|
||||
if(error->status == 0) {
|
||||
if(characteristicValueHandle == newAlertHandle && ble_uuid_cmp(((ble_uuid_t*)&newAlertUuid), &descriptor->uuid.u)) {
|
||||
if(newAlertDescriptorHandle == 0) {
|
||||
NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
|
||||
newAlertDescriptorHandle = descriptor->handle;
|
||||
uint8_t value[2];
|
||||
value[0] = 1;
|
||||
value[1] = 0;
|
||||
ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlertNotificationClient::OnNotification(ble_gap_event *event) {
|
||||
if(event->notify_rx.attr_handle == newAlertHandle) {
|
||||
// TODO implement this with more memory safety (and constexpr)
|
||||
static const size_t maxBufferSize{21};
|
||||
static const size_t maxMessageSize{18};
|
||||
size_t bufferSize = min(OS_MBUF_PKTLEN(event->notify_rx.om), maxBufferSize);
|
||||
|
||||
uint8_t data[bufferSize];
|
||||
os_mbuf_copydata(event->notify_rx.om, 0, bufferSize, data);
|
||||
|
||||
char *s = (char *) &data[3];
|
||||
auto messageSize = min(maxMessageSize, (bufferSize-3));
|
||||
|
||||
for (uint i = 0; i < messageSize-1; i++) {
|
||||
if (s[i] == 0x00) {
|
||||
s[i] = 0x0A;
|
||||
}
|
||||
}
|
||||
s[messageSize-1] = '\0';
|
||||
|
||||
notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||
}
|
||||
}
|
||||
|
||||
bool AlertNotificationClient::IsDiscovered() const {
|
||||
return isDiscovered;
|
||||
}
|
||||
|
||||
uint16_t AlertNotificationClient::StartHandle() const {
|
||||
return ansStartHandle;
|
||||
}
|
||||
|
||||
uint16_t AlertNotificationClient::EndHandle() const {
|
||||
return ansEndHandle;
|
||||
}
|
||||
|
||||
uint16_t AlertNotificationClient::NewAlerthandle() const {
|
||||
return newAlertHandle;
|
||||
}
|
81
src/components/Ble/AlertNotificationClient.h
Normal file
81
src/components/Ble/AlertNotificationClient.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <host/ble_gap.h>
|
||||
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
int NewAlertSubcribeCallback(uint16_t conn_handle,
|
||||
const struct ble_gatt_error *error,
|
||||
struct ble_gatt_attr *attr,
|
||||
void *arg);
|
||||
|
||||
class AlertNotificationClient {
|
||||
public:
|
||||
explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
|
||||
Pinetime::Controllers::NotificationManager ¬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);
|
||||
bool IsDiscovered() const;
|
||||
uint16_t StartHandle() const;
|
||||
uint16_t EndHandle() const;
|
||||
|
||||
static constexpr const ble_uuid16_t &Uuid() { return ansServiceUuid; }
|
||||
|
||||
uint16_t NewAlerthandle() const;
|
||||
private:
|
||||
static constexpr uint16_t ansServiceId{0x1811};
|
||||
static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
|
||||
static constexpr uint16_t supportedUnreadAlertCategoryId = 0x2a48;
|
||||
static constexpr uint16_t newAlertId = 0x2a46;
|
||||
static constexpr uint16_t unreadAlertStatusId = 0x2a45;
|
||||
static constexpr uint16_t controlPointId = 0x2a44;
|
||||
|
||||
static constexpr ble_uuid16_t ansServiceUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = ansServiceId
|
||||
};
|
||||
static constexpr ble_uuid16_t supportedNewAlertCategoryUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = supportedNewAlertCategoryId
|
||||
};
|
||||
static constexpr ble_uuid16_t supportedUnreadAlertCategoryUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = supportedUnreadAlertCategoryId
|
||||
};
|
||||
static constexpr ble_uuid16_t newAlertUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = newAlertId
|
||||
};
|
||||
static constexpr ble_uuid16_t unreadAlertStatusUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = unreadAlertStatusId
|
||||
};
|
||||
static constexpr ble_uuid16_t controlPointUuid{
|
||||
.u {.type = BLE_UUID_TYPE_16},
|
||||
.value = controlPointId
|
||||
};
|
||||
|
||||
uint16_t ansStartHandle;
|
||||
uint16_t ansEndHandle;
|
||||
uint16_t supportedNewAlertCategoryHandle;
|
||||
uint16_t supportedUnreadAlertCategoryHandle;
|
||||
uint16_t newAlertHandle;
|
||||
uint16_t newAlertDescriptorHandle = 0;
|
||||
uint16_t newAlertDefHandle;
|
||||
uint16_t unreadAlertStatusHandle;
|
||||
uint16_t controlPointHandle;
|
||||
bool isDiscovered = false;
|
||||
Pinetime::System::SystemTask &systemTask;
|
||||
Pinetime::Controllers::NotificationManager ¬ificationManager;
|
||||
};
|
||||
}
|
||||
}
|
80
src/components/Ble/AlertNotificationService.cpp
Normal file
80
src/components/Ble/AlertNotificationService.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include "NotificationManager.h"
|
||||
#include <SystemTask/SystemTask.h>
|
||||
|
||||
#include "AlertNotificationService.h"
|
||||
#include <cstring>
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid16_t AlertNotificationService::ansUuid;
|
||||
constexpr ble_uuid16_t AlertNotificationService::ansCharUuid;
|
||||
|
||||
|
||||
int AlertNotificationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto anService = static_cast<AlertNotificationService*>(arg);
|
||||
return anService->OnAlert(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
void AlertNotificationService::Init() {
|
||||
int res;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
AlertNotificationService::AlertNotificationService ( System::SystemTask& systemTask, NotificationManager& notificationManager )
|
||||
: characteristicDefinition{
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &ansCharUuid,
|
||||
.access_cb = AlertNotificationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE
|
||||
},
|
||||
{
|
||||
0
|
||||
}
|
||||
},
|
||||
serviceDefinition{
|
||||
{
|
||||
/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *) &ansUuid,
|
||||
.characteristics = characteristicDefinition
|
||||
},
|
||||
{
|
||||
0
|
||||
},
|
||||
}, m_systemTask{systemTask}, m_notificationManager{notificationManager} {
|
||||
}
|
||||
|
||||
int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt) {
|
||||
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
// TODO implement this with more memory safety (and constexpr)
|
||||
static const size_t maxBufferSize{21};
|
||||
static const size_t maxMessageSize{18};
|
||||
size_t bufferSize = min(OS_MBUF_PKTLEN(ctxt->om), maxBufferSize);
|
||||
|
||||
uint8_t data[bufferSize];
|
||||
os_mbuf_copydata(ctxt->om, 0, bufferSize, data);
|
||||
|
||||
char *s = (char *) &data[3];
|
||||
auto messageSize = min(maxMessageSize, (bufferSize-3));
|
||||
|
||||
for (uint i = 0; i < messageSize-1; i++) {
|
||||
if (s[i] == 0x00) {
|
||||
s[i] = 0x0A;
|
||||
}
|
||||
}
|
||||
s[messageSize-1] = '\0';
|
||||
|
||||
m_notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
|
||||
m_systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||
}
|
||||
return 0;
|
||||
}
|
39
src/components/Ble/AlertNotificationService.h
Normal file
39
src/components/Ble/AlertNotificationService.h
Normal file
@@ -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 &m_systemTask;
|
||||
NotificationManager &m_notificationManager;
|
||||
};
|
||||
}
|
||||
}
|
62
src/components/Ble/BatteryInformationService.cpp
Normal file
62
src/components/Ble/BatteryInformationService.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "BatteryInformationService.h"
|
||||
#include "../Battery/BatteryController.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid16_t BatteryInformationService::batteryInformationServiceUuid;
|
||||
constexpr ble_uuid16_t BatteryInformationService::batteryLevelUuid;
|
||||
|
||||
|
||||
|
||||
int BatteryInformationServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto* batteryInformationService = static_cast<BatteryInformationService*>(arg);
|
||||
return batteryInformationService->OnBatteryServiceRequested(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
BatteryInformationService::BatteryInformationService(Controllers::Battery& batteryController) :
|
||||
batteryController{batteryController},
|
||||
characteristicDefinition{
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &batteryLevelUuid,
|
||||
.access_cb = BatteryInformationServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
.val_handle = &batteryLevelHandle
|
||||
},
|
||||
{
|
||||
0
|
||||
}
|
||||
},
|
||||
serviceDefinition{
|
||||
{
|
||||
/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *) &batteryInformationServiceUuid,
|
||||
.characteristics = characteristicDefinition
|
||||
},
|
||||
{
|
||||
0
|
||||
},
|
||||
}{
|
||||
|
||||
}
|
||||
|
||||
void BatteryInformationService::Init() {
|
||||
int res = 0;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
int BatteryInformationService::OnBatteryServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle,
|
||||
ble_gatt_access_ctxt *context) {
|
||||
if(attributeHandle == batteryLevelHandle) {
|
||||
NRF_LOG_INFO("BATTERY : handle = %d", batteryLevelHandle);
|
||||
static uint8_t batteryValue = batteryController.PercentRemaining();
|
||||
int res = os_mbuf_append(context->om, &batteryValue, 1);
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return 0;
|
||||
}
|
40
src/components/Ble/BatteryInformationService.h
Normal file
40
src/components/Ble/BatteryInformationService.h
Normal file
@@ -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;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
31
src/components/Ble/BleController.cpp
Normal file
31
src/components/Ble/BleController.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
45
src/components/Ble/BleController.h
Normal file
45
src/components/Ble/BleController.h
Normal file
@@ -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;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
77
src/components/Ble/CurrentTimeClient.cpp
Normal file
77
src/components/Ble/CurrentTimeClient.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include "CurrentTimeClient.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid16_t CurrentTimeClient::ctsServiceUuid;
|
||||
constexpr ble_uuid16_t CurrentTimeClient::currentTimeCharacteristicUuid;
|
||||
|
||||
CurrentTimeClient::CurrentTimeClient(DateTime& dateTimeController) : dateTimeController{dateTimeController} {
|
||||
|
||||
}
|
||||
|
||||
void CurrentTimeClient::Init() {
|
||||
|
||||
}
|
||||
|
||||
bool CurrentTimeClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("CTS Discovery complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(service != nullptr && ble_uuid_cmp(((ble_uuid_t*)&ctsServiceUuid), &service->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("CTS discovered : 0x%x", service->start_handle);
|
||||
isDiscovered = true;
|
||||
ctsStartHandle = service->start_handle;
|
||||
ctsEndHandle = service->end_handle;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int CurrentTimeClient::OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic) {
|
||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("CTS Characteristic discovery complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(characteristic != nullptr && ble_uuid_cmp(((ble_uuid_t*)¤tTimeCharacteristicUuid), &characteristic->uuid.u) == 0) {
|
||||
NRF_LOG_INFO("CTS Characteristic discovered : 0x%x", characteristic->val_handle);
|
||||
currentTimeHandle = characteristic->val_handle;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CurrentTimeClient::OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute) {
|
||||
if(error->status == 0) {
|
||||
// TODO check that attribute->handle equals the handle discovered in OnCharacteristicDiscoveryEvent
|
||||
CtsData result;
|
||||
os_mbuf_copydata(attribute->om, 0, sizeof(CtsData), &result);
|
||||
NRF_LOG_INFO("Received data: %d-%d-%d %d:%d:%d", result.year,
|
||||
result.month, result.dayofmonth,
|
||||
result.hour, result.minute, result.second);
|
||||
dateTimeController.SetTime(result.year, result.month, result.dayofmonth,
|
||||
0, result.hour, result.minute, result.second, nrf_rtc_counter_get(portNRF_RTC_REG));
|
||||
} else {
|
||||
NRF_LOG_INFO("Error retrieving current time: %d", error->status);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CurrentTimeClient::IsDiscovered() const {
|
||||
return isDiscovered;
|
||||
}
|
||||
|
||||
uint16_t CurrentTimeClient::StartHandle() const {
|
||||
return ctsStartHandle;
|
||||
}
|
||||
|
||||
uint16_t CurrentTimeClient::EndHandle() const {
|
||||
return ctsEndHandle;
|
||||
}
|
||||
|
||||
uint16_t CurrentTimeClient::CurrentTimeHandle() const {
|
||||
return currentTimeHandle;
|
||||
}
|
55
src/components/Ble/CurrentTimeClient.h
Normal file
55
src/components/Ble/CurrentTimeClient.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <Components/DateTime/DateTimeController.h>
|
||||
#include <host/ble_gap.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
||||
class CurrentTimeClient {
|
||||
public:
|
||||
explicit CurrentTimeClient(DateTime& dateTimeController);
|
||||
void Init();
|
||||
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
|
||||
int OnCharacteristicDiscoveryEvent(uint16_t conn_handle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic);
|
||||
int OnCurrentTimeReadResult(uint16_t conn_handle, const ble_gatt_error *error, const ble_gatt_attr *attribute);
|
||||
bool IsDiscovered() const;
|
||||
uint16_t StartHandle() const;
|
||||
uint16_t EndHandle() const;
|
||||
uint16_t CurrentTimeHandle() const;
|
||||
static constexpr const ble_uuid16_t* Uuid() { return &CurrentTimeClient::ctsServiceUuid; }
|
||||
static constexpr const ble_uuid16_t* CurrentTimeCharacteristicUuid() { return &CurrentTimeClient::currentTimeCharacteristicUuid; }
|
||||
private:
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayofmonth;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t millis;
|
||||
uint8_t reason;
|
||||
} CtsData;
|
||||
|
||||
static constexpr uint16_t ctsServiceId {0x1805};
|
||||
static constexpr uint16_t currentTimeCharacteristicId {0x2a2b};
|
||||
|
||||
static constexpr ble_uuid16_t ctsServiceUuid {
|
||||
.u { .type = BLE_UUID_TYPE_16 },
|
||||
.value = ctsServiceId
|
||||
};
|
||||
static constexpr ble_uuid16_t currentTimeCharacteristicUuid {
|
||||
.u { .type = BLE_UUID_TYPE_16 },
|
||||
.value = currentTimeCharacteristicId
|
||||
};
|
||||
|
||||
uint16_t currentTimeHandle;
|
||||
DateTime& dateTimeController;
|
||||
bool isDiscovered = false;
|
||||
uint16_t ctsStartHandle;
|
||||
uint16_t ctsEndHandle;
|
||||
};
|
||||
}
|
||||
}
|
86
src/components/Ble/CurrentTimeService.cpp
Normal file
86
src/components/Ble/CurrentTimeService.cpp
Normal file
@@ -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} {
|
||||
|
||||
}
|
||||
|
48
src/components/Ble/CurrentTimeService.h
Normal file
48
src/components/Ble/CurrentTimeService.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <Components/DateTime/DateTimeController.h>
|
||||
#include <host/ble_gap.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class CurrentTimeService {
|
||||
public:
|
||||
CurrentTimeService(DateTime &dateTimeController);
|
||||
void Init();
|
||||
|
||||
int OnTimeAccessed(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t ctsId {0x1805};
|
||||
static constexpr uint16_t ctsCharId {0x2a2b};
|
||||
|
||||
static constexpr ble_uuid16_t ctsUuid {
|
||||
.u { .type = BLE_UUID_TYPE_16 },
|
||||
.value = ctsId
|
||||
};
|
||||
|
||||
static constexpr ble_uuid16_t ctChrUuid {
|
||||
.u { .type = BLE_UUID_TYPE_16 },
|
||||
.value = ctsCharId
|
||||
};
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[2];
|
||||
struct ble_gatt_svc_def serviceDefinition[2];
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayofmonth;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t millis;
|
||||
uint8_t reason;
|
||||
} CtsData;
|
||||
|
||||
DateTime &m_dateTimeController;
|
||||
};
|
||||
}
|
||||
}
|
116
src/components/Ble/DeviceInformationService.cpp
Normal file
116
src/components/Ble/DeviceInformationService.cpp
Normal file
@@ -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
|
||||
},
|
||||
}
|
||||
{
|
||||
|
||||
}
|
||||
|
76
src/components/Ble/DeviceInformationService.h
Normal file
76
src/components/Ble/DeviceInformationService.h
Normal file
@@ -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];
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
}
|
440
src/components/Ble/DfuService.cpp
Normal file
440
src/components/Ble/DfuService.cpp
Normal file
@@ -0,0 +1,440 @@
|
||||
#include <Components/Ble/BleController.h>
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include <cstring>
|
||||
#include "DfuService.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid128_t DfuService::serviceUuid;
|
||||
constexpr ble_uuid128_t DfuService::controlPointCharacteristicUuid;
|
||||
constexpr ble_uuid128_t DfuService::revisionCharacteristicUuid;
|
||||
constexpr ble_uuid128_t DfuService::packetCharacteristicUuid;
|
||||
|
||||
int DfuServiceCallback(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto dfuService = static_cast<DfuService *>(arg);
|
||||
return dfuService->OnServiceData(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
void NotificationTimerCallback(TimerHandle_t xTimer) {
|
||||
auto notificationManager = static_cast<DfuService::NotificationManager *>(pvTimerGetTimerID(xTimer));
|
||||
notificationManager->OnNotificationTimer();
|
||||
}
|
||||
|
||||
void TimeoutTimerCallback(TimerHandle_t xTimer) {
|
||||
auto dfuService = static_cast<DfuService *>(pvTimerGetTimerID(xTimer));
|
||||
dfuService->OnTimeout();
|
||||
}
|
||||
|
||||
DfuService::DfuService(Pinetime::System::SystemTask &systemTask, Pinetime::Controllers::Ble &bleController,
|
||||
Pinetime::Drivers::SpiNorFlash &spiNorFlash) :
|
||||
systemTask{systemTask},
|
||||
bleController{bleController},
|
||||
dfuImage{spiNorFlash},
|
||||
characteristicDefinition{
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &packetCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &controlPointCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &revisionCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
.val_handle = &revision,
|
||||
|
||||
},
|
||||
{
|
||||
0
|
||||
}
|
||||
|
||||
},
|
||||
serviceDefinition{
|
||||
{
|
||||
/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *) &serviceUuid,
|
||||
.characteristics = characteristicDefinition
|
||||
},
|
||||
{
|
||||
0
|
||||
},
|
||||
} {
|
||||
timeoutTimer = xTimerCreate ("notificationTimer", 10000, pdFALSE, this, TimeoutTimerCallback);
|
||||
}
|
||||
|
||||
void DfuService::Init() {
|
||||
int res;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
|
||||
if(bleController.IsFirmwareUpdating()){
|
||||
xTimerStart(timeoutTimer, 0);
|
||||
}
|
||||
|
||||
|
||||
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &packetCharacteristicUuid, nullptr,
|
||||
&packetCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &controlPointCharacteristicUuid, nullptr,
|
||||
&controlPointCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &revisionCharacteristicUuid, nullptr,
|
||||
&revisionCharacteristicHandle);
|
||||
|
||||
if (attributeHandle == packetCharacteristicHandle) {
|
||||
if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
|
||||
return WritePacketHandler(connectionHandle, context->om);
|
||||
else return 0;
|
||||
} else if (attributeHandle == controlPointCharacteristicHandle) {
|
||||
if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
|
||||
return ControlPointHandler(connectionHandle, context->om);
|
||||
else return 0;
|
||||
} else if (attributeHandle == revisionCharacteristicHandle) {
|
||||
if (context->op == BLE_GATT_ACCESS_OP_READ_CHR)
|
||||
return SendDfuRevision(context->om);
|
||||
else return 0;
|
||||
} else {
|
||||
NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int DfuService::SendDfuRevision(os_mbuf *om) const {
|
||||
int res = os_mbuf_append(om, &revision, sizeof(revision));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf *om) {
|
||||
switch (state) {
|
||||
case States::Start: {
|
||||
softdeviceSize = om->om_data[0] + (om->om_data[1] << 8) + (om->om_data[2] << 16) + (om->om_data[3] << 24);
|
||||
bootloaderSize = om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
|
||||
applicationSize = om->om_data[8] + (om->om_data[9] << 8) + (om->om_data[10] << 16) + (om->om_data[11] << 24);
|
||||
bleController.FirmwareUpdateTotalBytes(applicationSize);
|
||||
NRF_LOG_INFO("[DFU] -> Start data received : SD size : %d, BT size : %d, app size : %d", softdeviceSize,
|
||||
bootloaderSize, applicationSize);
|
||||
|
||||
dfuImage.Erase();
|
||||
|
||||
uint8_t data[]{16, 1, 1};
|
||||
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
|
||||
state = States::Init;
|
||||
}
|
||||
return 0;
|
||||
case States::Init: {
|
||||
uint16_t deviceType = om->om_data[0] + (om->om_data[1] << 8);
|
||||
uint16_t deviceRevision = om->om_data[2] + (om->om_data[3] << 8);
|
||||
uint32_t applicationVersion =
|
||||
om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
|
||||
uint16_t softdeviceArrayLength = om->om_data[8] + (om->om_data[9] << 8);
|
||||
uint16_t sd[softdeviceArrayLength];
|
||||
for (int i = 0; i < softdeviceArrayLength; i++) {
|
||||
sd[i] = om->om_data[10 + (i * 2)] + (om->om_data[10 + (i * 2) + 1] << 8);
|
||||
}
|
||||
expectedCrc =
|
||||
om->om_data[10 + (softdeviceArrayLength * 2)] + (om->om_data[10 + (softdeviceArrayLength * 2) + 1] << 8);
|
||||
|
||||
NRF_LOG_INFO(
|
||||
"[DFU] -> Init data received : deviceType = %d, deviceRevision = %d, applicationVersion = %d, nb SD = %d, First SD = %d, CRC = %u",
|
||||
deviceType, deviceRevision, applicationVersion, softdeviceArrayLength, sd[0], expectedCrc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case States::Data: {
|
||||
nbPacketReceived++;
|
||||
dfuImage.Append(om->om_data, om->om_len);
|
||||
bytesReceived += om->om_len;
|
||||
bleController.FirmwareUpdateCurrentBytes(bytesReceived);
|
||||
|
||||
if ((nbPacketReceived % nbPacketsToNotify) == 0 && bytesReceived != applicationSize) {
|
||||
uint8_t data[5]{static_cast<uint8_t>(Opcodes::PacketReceiptNotification),
|
||||
(uint8_t) (bytesReceived & 0x000000FFu), (uint8_t) (bytesReceived >> 8u),
|
||||
(uint8_t) (bytesReceived >> 16u), (uint8_t) (bytesReceived >> 24u)};
|
||||
NRF_LOG_INFO("[DFU] -> Send packet notification: %d bytes received", bytesReceived);
|
||||
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 5);
|
||||
}
|
||||
if (dfuImage.IsComplete()) {
|
||||
uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::ReceiveFirmwareImage),
|
||||
static_cast<uint8_t>(ErrorCodes::NoError)};
|
||||
NRF_LOG_INFO("[DFU] -> Send packet notification : all bytes received!");
|
||||
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
|
||||
state = States::Validate;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
// Invalid state
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf *om) {
|
||||
auto opcode = static_cast<Opcodes>(om->om_data[0]);
|
||||
NRF_LOG_INFO("[DFU] -> ControlPointHandler");
|
||||
|
||||
switch (opcode) {
|
||||
case Opcodes::StartDFU: {
|
||||
if (state != States::Idle && state != States::Start) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are not in Idle state");
|
||||
return 0;
|
||||
}
|
||||
if (state == States::Start) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are already in Start state");
|
||||
return 0;
|
||||
}
|
||||
auto imageType = static_cast<ImageTypes>(om->om_data[1]);
|
||||
if (imageType == ImageTypes::Application) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU, mode = Application");
|
||||
state = States::Start;
|
||||
bleController.StartFirmwareUpdate();
|
||||
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Running);
|
||||
bleController.FirmwareUpdateTotalBytes(0xffffffffu);
|
||||
bleController.FirmwareUpdateCurrentBytes(0);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateStarted);
|
||||
return 0;
|
||||
} else {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU, mode %d not supported!", imageType);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcodes::InitDFUParameters: {
|
||||
if (state != States::Init) {
|
||||
NRF_LOG_INFO("[DFU] -> Init DFU requested, but we are not in Init state");
|
||||
return 0;
|
||||
}
|
||||
bool isInitComplete = (om->om_data[1] != 0);
|
||||
NRF_LOG_INFO("[DFU] -> Init DFU parameters %s", isInitComplete ? " complete" : " not complete");
|
||||
|
||||
if (isInitComplete) {
|
||||
uint8_t data[3] {
|
||||
static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::InitDFUParameters),
|
||||
(isInitComplete ? uint8_t{1} : uint8_t{0})
|
||||
};
|
||||
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
case Opcodes::PacketReceiptNotificationRequest:
|
||||
nbPacketsToNotify = om->om_data[1];
|
||||
NRF_LOG_INFO("[DFU] -> Receive Packet Notification Request, nb packet = %d", nbPacketsToNotify);
|
||||
return 0;
|
||||
case Opcodes::ReceiveFirmwareImage:
|
||||
if (state != States::Init) {
|
||||
NRF_LOG_INFO("[DFU] -> Receive firmware image requested, but we are not in Start Init");
|
||||
return 0;
|
||||
}
|
||||
// TODO the chunk size is dependant of the implementation of the host application...
|
||||
dfuImage.Init(20, applicationSize, expectedCrc);
|
||||
NRF_LOG_INFO("[DFU] -> Starting receive firmware");
|
||||
state = States::Data;
|
||||
return 0;
|
||||
case Opcodes::ValidateFirmware: {
|
||||
if (state != States::Validate) {
|
||||
NRF_LOG_INFO("[DFU] -> Validate firmware image requested, but we are not in Data state %d", state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NRF_LOG_INFO("[DFU] -> Validate firmware image requested -- %d", connectionHandle);
|
||||
|
||||
if(dfuImage.Validate()){
|
||||
state = States::Validated;
|
||||
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
|
||||
NRF_LOG_INFO("Image OK");
|
||||
|
||||
uint8_t data[3] {
|
||||
static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::ValidateFirmware),
|
||||
static_cast<uint8_t>(ErrorCodes::NoError)
|
||||
};
|
||||
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
|
||||
} else {
|
||||
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
|
||||
NRF_LOG_INFO("Image Error : bad CRC");
|
||||
|
||||
uint8_t data[3] {
|
||||
static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::ValidateFirmware),
|
||||
static_cast<uint8_t>(ErrorCodes::CrcError)
|
||||
};
|
||||
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
case Opcodes::ActivateImageAndReset:
|
||||
if (state != States::Validated) {
|
||||
NRF_LOG_INFO("[DFU] -> Activate image and reset requested, but we are not in Validated state");
|
||||
return 0;
|
||||
}
|
||||
NRF_LOG_INFO("[DFU] -> Activate image and reset!");
|
||||
bleController.StopFirmwareUpdate();
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
|
||||
Reset();
|
||||
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DfuService::OnTimeout() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void DfuService::Reset() {
|
||||
state = States::Idle;
|
||||
nbPacketsToNotify = 0;
|
||||
nbPacketReceived = 0;
|
||||
bytesReceived = 0;
|
||||
softdeviceSize = 0;
|
||||
bootloaderSize = 0;
|
||||
applicationSize = 0;
|
||||
expectedCrc = 0;
|
||||
notificationManager.Reset();
|
||||
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
|
||||
bleController.StopFirmwareUpdate();
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
|
||||
}
|
||||
|
||||
DfuService::NotificationManager::NotificationManager() {
|
||||
timer = xTimerCreate ("notificationTimer", 1000, pdFALSE, this, NotificationTimerCallback);
|
||||
}
|
||||
|
||||
bool DfuService::NotificationManager::AsyncSend(uint16_t connection, uint16_t charactHandle, uint8_t *data, size_t s) {
|
||||
if(size != 0 || s > 10)
|
||||
return false;
|
||||
|
||||
connectionHandle = connection;
|
||||
characteristicHandle = charactHandle;
|
||||
size = s;
|
||||
std::memcpy(buffer, data, size);
|
||||
xTimerStart(timer, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DfuService::NotificationManager::OnNotificationTimer() {
|
||||
if(size > 0) {
|
||||
Send(connectionHandle, characteristicHandle, buffer, size);
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DfuService::NotificationManager::Send(uint16_t connection, uint16_t charactHandle, const uint8_t *data, const size_t s) {
|
||||
auto *om = ble_hs_mbuf_from_flat(data, s);
|
||||
auto ret = ble_gattc_notify_custom(connection, charactHandle, om);
|
||||
ASSERT(ret == 0);
|
||||
}
|
||||
|
||||
void DfuService::NotificationManager::Reset() {
|
||||
connectionHandle = 0;
|
||||
characteristicHandle = 0;
|
||||
size = 0;
|
||||
xTimerStop(timer, 0);
|
||||
}
|
||||
|
||||
void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t expectedCrc) {
|
||||
if(chunkSize != 20) return;
|
||||
this->chunkSize = chunkSize;
|
||||
this->totalSize = totalSize;
|
||||
this->expectedCrc = expectedCrc;
|
||||
this->ready = true;
|
||||
}
|
||||
|
||||
void DfuService::DfuImage::Append(uint8_t *data, size_t size) {
|
||||
if(!ready) return;
|
||||
ASSERT(size <= 20);
|
||||
|
||||
std::memcpy(tempBuffer + bufferWriteIndex, data, size);
|
||||
bufferWriteIndex += size;
|
||||
|
||||
if(bufferWriteIndex == bufferSize) {
|
||||
spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
|
||||
totalWriteIndex += bufferWriteIndex;
|
||||
bufferWriteIndex = 0;
|
||||
}
|
||||
|
||||
if(bufferWriteIndex > 0 && totalWriteIndex + bufferWriteIndex == totalSize) {
|
||||
spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
|
||||
totalWriteIndex += bufferWriteIndex;
|
||||
if (totalSize < maxSize)
|
||||
WriteMagicNumber();
|
||||
}
|
||||
}
|
||||
|
||||
void DfuService::DfuImage::WriteMagicNumber() {
|
||||
uint32_t magic[4] = { // TODO When this variable is a static constexpr, the values written to the memory are not correct. Why?
|
||||
0xf395c277,
|
||||
0x7fefd260,
|
||||
0x0f505235,
|
||||
0x8079b62c,
|
||||
};
|
||||
|
||||
uint32_t offset = writeOffset + (maxSize - (4 * sizeof(uint32_t)));
|
||||
spiNorFlash.Write(offset, reinterpret_cast<const uint8_t *>(magic), 4 * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void DfuService::DfuImage::Erase() {
|
||||
for (size_t erased = 0; erased < maxSize; erased += 0x1000) {
|
||||
spiNorFlash.SectorErase(writeOffset + erased);
|
||||
}
|
||||
}
|
||||
|
||||
bool DfuService::DfuImage::Validate() {
|
||||
uint32_t chunkSize = 200;
|
||||
size_t currentOffset = 0;
|
||||
uint16_t crc = 0;
|
||||
|
||||
bool first = true;
|
||||
while (currentOffset < totalSize) {
|
||||
uint32_t readSize = (totalSize - currentOffset) > chunkSize ? chunkSize : (totalSize - currentOffset);
|
||||
|
||||
spiNorFlash.Read(writeOffset + currentOffset, tempBuffer, readSize);
|
||||
if (first) {
|
||||
crc = ComputeCrc(tempBuffer, readSize, NULL);
|
||||
first = false;
|
||||
} else
|
||||
crc = ComputeCrc(tempBuffer, readSize, &crc);
|
||||
currentOffset += readSize;
|
||||
}
|
||||
|
||||
return (crc == expectedCrc);
|
||||
}
|
||||
|
||||
uint16_t DfuService::DfuImage::ComputeCrc(uint8_t const *p_data, uint32_t size, uint16_t const *p_crc) {
|
||||
uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
|
||||
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
crc = (uint8_t) (crc >> 8) | (crc << 8);
|
||||
crc ^= p_data[i];
|
||||
crc ^= (uint8_t) (crc & 0xFF) >> 4;
|
||||
crc ^= (crc << 8) << 4;
|
||||
crc ^= ((crc & 0xFF) << 4) << 1;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool DfuService::DfuImage::IsComplete() {
|
||||
if(!ready) return false;
|
||||
return totalWriteIndex == totalSize;
|
||||
}
|
161
src/components/Ble/DfuService.h
Normal file
161
src/components/Ble/DfuService.h
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
76
src/components/Ble/ImmediateAlertService.cpp
Normal file
76
src/components/Ble/ImmediateAlertService.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "ImmediateAlertService.h"
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include "AlertNotificationService.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid16_t ImmediateAlertService::immediateAlertServiceUuid;
|
||||
constexpr ble_uuid16_t ImmediateAlertService::alertLevelUuid;
|
||||
|
||||
namespace {
|
||||
int AlertLevelCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto *immediateAlertService = static_cast<ImmediateAlertService *>(arg);
|
||||
return immediateAlertService->OnAlertLevelChanged(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
const char* ToString(ImmediateAlertService::Levels level) {
|
||||
switch (level) {
|
||||
case ImmediateAlertService::Levels::NoAlert: return "Alert : None";
|
||||
case ImmediateAlertService::Levels::HighAlert: return "Alert : High";
|
||||
case ImmediateAlertService::Levels::MildAlert: return "Alert : Mild";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask &systemTask,
|
||||
Pinetime::Controllers::NotificationManager ¬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.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, alertString, strlen(alertString));
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
46
src/components/Ble/ImmediateAlertService.h
Normal file
46
src/components/Ble/ImmediateAlertService.h
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
129
src/components/Ble/MusicService.cpp
Normal file
129
src/components/Ble/MusicService.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include "MusicService.h"
|
||||
|
||||
int MSCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto musicService = static_cast<Pinetime::Controllers::MusicService*>(arg);
|
||||
return musicService->OnCommand(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
Pinetime::Controllers::MusicService::MusicService(Pinetime::System::SystemTask &system) : m_system(system)
|
||||
{
|
||||
msUuid.value[11] = msId[0];
|
||||
msUuid.value[12] = msId[1];
|
||||
msEventCharUuid.value[11] = msEventCharId[0];
|
||||
msEventCharUuid.value[12] = msEventCharId[1];
|
||||
msStatusCharUuid.value[11] = msStatusCharId[0];
|
||||
msStatusCharUuid.value[12] = msStatusCharId[1];
|
||||
msTrackCharUuid.value[11] = msTrackCharId[0];
|
||||
msTrackCharUuid.value[12] = msTrackCharId[1];
|
||||
msArtistCharUuid.value[11] = msArtistCharId[0];
|
||||
msArtistCharUuid.value[12] = msArtistCharId[1];
|
||||
msAlbumCharUuid.value[11] = msAlbumCharId[0];
|
||||
msAlbumCharUuid.value[12] = msAlbumCharId[1];
|
||||
|
||||
characteristicDefinition[0] = { .uuid = (ble_uuid_t*)(&msEventCharUuid),
|
||||
.access_cb = MSCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = &m_eventHandle
|
||||
};
|
||||
characteristicDefinition[1] = { .uuid = (ble_uuid_t*)(&msStatusCharUuid),
|
||||
.access_cb = MSCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||
};
|
||||
characteristicDefinition[2] = { .uuid = (ble_uuid_t*)(&msTrackCharUuid),
|
||||
.access_cb = MSCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||
};
|
||||
characteristicDefinition[3] = { .uuid = (ble_uuid_t*)(&msArtistCharUuid),
|
||||
.access_cb = MSCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||
};
|
||||
characteristicDefinition[4] = { .uuid = (ble_uuid_t*)(&msAlbumCharUuid),
|
||||
.access_cb = MSCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ
|
||||
};
|
||||
characteristicDefinition[5] = {0};
|
||||
|
||||
serviceDefinition[0] = {
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *) &msUuid,
|
||||
.characteristics = characteristicDefinition
|
||||
};
|
||||
serviceDefinition[1] = {0};
|
||||
|
||||
m_artist = "Waiting for";
|
||||
m_album = "";
|
||||
m_track = "track information...";
|
||||
}
|
||||
|
||||
void Pinetime::Controllers::MusicService::Init()
|
||||
{
|
||||
int res = 0;
|
||||
res = ble_gatts_count_cfg(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
|
||||
res = ble_gatts_add_svcs(serviceDefinition);
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
int Pinetime::Controllers::MusicService::OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt) {
|
||||
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
|
||||
uint8_t data[notifSize + 1];
|
||||
data[notifSize] = '\0';
|
||||
os_mbuf_copydata(ctxt->om, 0, notifSize, data);
|
||||
char *s = (char *) &data[0];
|
||||
NRF_LOG_INFO("DATA : %s", s);
|
||||
if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msArtistCharUuid) == 0) {
|
||||
m_artist = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msTrackCharUuid) == 0) {
|
||||
m_track = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msAlbumCharUuid) == 0) {
|
||||
m_album = s;
|
||||
} else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t *)&msStatusCharUuid) == 0) {
|
||||
m_status = s[0];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string Pinetime::Controllers::MusicService::album()
|
||||
{
|
||||
return m_album;
|
||||
}
|
||||
|
||||
std::string Pinetime::Controllers::MusicService::artist()
|
||||
{
|
||||
return m_artist;
|
||||
}
|
||||
|
||||
std::string Pinetime::Controllers::MusicService::track()
|
||||
{
|
||||
return m_track;
|
||||
}
|
||||
|
||||
unsigned char Pinetime::Controllers::MusicService::status()
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
void Pinetime::Controllers::MusicService::event(char event)
|
||||
{
|
||||
auto *om = ble_hs_mbuf_from_flat(&event, 1);
|
||||
|
||||
uint16_t connectionHandle = m_system.nimble().connHandle();
|
||||
|
||||
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ble_gattc_notify_custom(connectionHandle, m_eventHandle, om);
|
||||
}
|
||||
|
92
src/components/Ble/MusicService.h
Normal file
92
src/components/Ble/MusicService.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_uuid.h>
|
||||
#include <string>
|
||||
|
||||
//c7e50000-78fc-48fe-8e23-43b37a1942d0
|
||||
#define MUSIC_SERVICE_UUID_BASE {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0xe5, 0xc7}
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
class SystemTask;
|
||||
}
|
||||
namespace Controllers {
|
||||
|
||||
class MusicService {
|
||||
public:
|
||||
MusicService(Pinetime::System::SystemTask &system);
|
||||
void Init();
|
||||
int OnCommand(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt);
|
||||
|
||||
std::string artist();
|
||||
std::string track();
|
||||
std::string album();
|
||||
unsigned char status();
|
||||
|
||||
void event(char event);
|
||||
|
||||
static const char EVENT_MUSIC_OPEN = 0xe0;
|
||||
static const char EVENT_MUSIC_PLAY = 0x00;
|
||||
static const char EVENT_MUSIC_PAUSE = 0x01;
|
||||
static const char EVENT_MUSIC_NEXT = 0x03;
|
||||
static const char EVENT_MUSIC_PREV = 0x04;
|
||||
static const char EVENT_MUSIC_VOLUP = 0x05;
|
||||
static const char EVENT_MUSIC_VOLDOWN = 0x06;
|
||||
static const char STATUS_MUSIC_PAUSED = 0x00;
|
||||
static const char STATUS_MUSIC_PLAYING = 0x01;
|
||||
|
||||
private:
|
||||
static constexpr uint8_t msId[2] = {0x00, 0x01};
|
||||
static constexpr uint8_t msEventCharId[2] = {0x00, 0x02};
|
||||
static constexpr uint8_t msStatusCharId[2] = {0x00, 0x03};
|
||||
static constexpr uint8_t msArtistCharId[2] = {0x00, 0x04};
|
||||
static constexpr uint8_t msTrackCharId[2] = {0x00, 0x05};
|
||||
static constexpr uint8_t msAlbumCharId[2] = {0x00, 0x06};
|
||||
|
||||
ble_uuid128_t msUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
|
||||
ble_uuid128_t msEventCharUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
ble_uuid128_t msStatusCharUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
ble_uuid128_t msArtistCharUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
ble_uuid128_t msTrackCharUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
ble_uuid128_t msAlbumCharUuid {
|
||||
.u = { .type = BLE_UUID_TYPE_128 },
|
||||
.value = MUSIC_SERVICE_UUID_BASE
|
||||
};
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[6];
|
||||
struct ble_gatt_svc_def serviceDefinition[2];
|
||||
|
||||
uint16_t m_eventHandle;
|
||||
|
||||
std::string m_artist;
|
||||
std::string m_album;
|
||||
std::string m_track;
|
||||
|
||||
unsigned char m_status;
|
||||
|
||||
Pinetime::System::SystemTask& m_system;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
337
src/components/Ble/NimbleController.cpp
Normal file
337
src/components/Ble/NimbleController.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
|
||||
#include <Components/DateTime/DateTimeController.h>
|
||||
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include <Components/Ble/NotificationManager.h>
|
||||
#include <hal/nrf_rtc.h>
|
||||
|
||||
#include "NimbleController.h"
|
||||
#include "MusicService.h"
|
||||
#include <services/gatt/ble_svc_gatt.h>
|
||||
#include <services/gap/ble_svc_gap.h>
|
||||
#include <host/util/util.h>
|
||||
#include <host/ble_hs_id.h>
|
||||
#include <host/ble_hs.h>
|
||||
#include <host/ble_gap.h>
|
||||
|
||||
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
// TODO I'm not satisfied by how this code looks like (AlertNotificationClient and CurrentTimeClient must
|
||||
// expose too much data, too many callbacks -> NimbleController -> CTS/ANS client.
|
||||
// Let's try to improve this code (and keep it working!)
|
||||
|
||||
NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
|
||||
Pinetime::Controllers::Ble& bleController,
|
||||
DateTime& dateTimeController,
|
||||
Pinetime::Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Battery& batteryController,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
|
||||
systemTask{systemTask},
|
||||
bleController{bleController},
|
||||
dateTimeController{dateTimeController},
|
||||
notificationManager{notificationManager},
|
||||
spiNorFlash{spiNorFlash},
|
||||
dfuService{systemTask, bleController, spiNorFlash},
|
||||
currentTimeClient{dateTimeController},
|
||||
anService{systemTask, notificationManager},
|
||||
alertNotificationClient{systemTask, notificationManager},
|
||||
currentTimeService{dateTimeController},
|
||||
musicService{systemTask},
|
||||
batteryInformationService{batteryController},
|
||||
immediateAlertService{systemTask, notificationManager} {
|
||||
|
||||
}
|
||||
|
||||
int GAPEventCallback(struct ble_gap_event *event, void *arg) {
|
||||
auto nimbleController = static_cast<NimbleController*>(arg);
|
||||
return nimbleController->OnGAPEvent(event);
|
||||
}
|
||||
|
||||
int CurrentTimeCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
||||
const struct ble_gatt_chr *chr, void *arg) {
|
||||
auto client = static_cast<NimbleController*>(arg);
|
||||
return client->OnCTSCharacteristicDiscoveryEvent(conn_handle, error, chr);
|
||||
}
|
||||
|
||||
int AlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
||||
const struct ble_gatt_chr *chr, void *arg) {
|
||||
auto client = static_cast<NimbleController*>(arg);
|
||||
return client->OnANSCharacteristicDiscoveryEvent(conn_handle, error, chr);
|
||||
}
|
||||
|
||||
int CurrentTimeReadCallback(uint16_t conn_handle, const struct ble_gatt_error *error,
|
||||
struct ble_gatt_attr *attr, void *arg) {
|
||||
auto client = static_cast<NimbleController*>(arg);
|
||||
return client->OnCurrentTimeReadResult(conn_handle, error, attr);
|
||||
}
|
||||
|
||||
int AlertNotificationDescriptorDiscoveryEventCallback(uint16_t conn_handle,
|
||||
const struct ble_gatt_error *error,
|
||||
uint16_t chr_val_handle,
|
||||
const struct ble_gatt_dsc *dsc,
|
||||
void *arg) {
|
||||
auto client = static_cast<NimbleController*>(arg);
|
||||
return client->OnANSDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
|
||||
}
|
||||
|
||||
void NimbleController::Init() {
|
||||
while (!ble_hs_synced()) {}
|
||||
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
|
||||
deviceInformationService.Init();
|
||||
currentTimeClient.Init();
|
||||
currentTimeService.Init();
|
||||
musicService.Init();
|
||||
anService.Init();
|
||||
dfuService.Init();
|
||||
batteryInformationService.Init();
|
||||
immediateAlertService.Init();
|
||||
int res;
|
||||
res = ble_hs_util_ensure_addr(0);
|
||||
ASSERT(res == 0);
|
||||
res = ble_hs_id_infer_auto(0, &addrType);
|
||||
ASSERT(res == 0);
|
||||
res = ble_svc_gap_device_name_set(deviceName);
|
||||
ASSERT(res == 0);
|
||||
Pinetime::Controllers::Ble::BleAddress address;
|
||||
res = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
|
||||
ASSERT(res == 0);
|
||||
bleController.AddressType((addrType == 0) ? Ble::AddressTypes::Public : Ble::AddressTypes::Random);
|
||||
bleController.Address(std::move(address));
|
||||
|
||||
res = ble_gatts_start();
|
||||
ASSERT(res == 0);
|
||||
}
|
||||
|
||||
void NimbleController::StartAdvertising() {
|
||||
if(ble_gap_adv_active()) return;
|
||||
|
||||
ble_svc_gap_device_name_set(deviceName);
|
||||
|
||||
/* set adv parameters */
|
||||
struct ble_gap_adv_params adv_params;
|
||||
struct ble_hs_adv_fields fields;
|
||||
/* advertising payload is split into advertising data and advertising
|
||||
response, because all data cannot fit into single packet; name of device
|
||||
is sent as response to scan request */
|
||||
struct ble_hs_adv_fields rsp_fields;
|
||||
|
||||
/* fill all fields and parameters with zeros */
|
||||
memset(&adv_params, 0, sizeof(adv_params));
|
||||
memset(&fields, 0, sizeof(fields));
|
||||
memset(&rsp_fields, 0, sizeof(rsp_fields));
|
||||
|
||||
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN |
|
||||
BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
// fields.uuids128 = BLE_UUID128(BLE_UUID128_DECLARE(
|
||||
// 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
// 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff));
|
||||
fields.uuids128 = &dfuServiceUuid;
|
||||
fields.num_uuids128 = 1;
|
||||
fields.uuids128_is_complete = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
|
||||
rsp_fields.name = (uint8_t *)deviceName;
|
||||
rsp_fields.name_len = strlen(deviceName);
|
||||
rsp_fields.name_is_complete = 1;
|
||||
|
||||
ble_gap_adv_set_fields(&fields);
|
||||
// ASSERT(res == 0); // TODO this one sometimes fails with error 22 (notsync)
|
||||
|
||||
ble_gap_adv_rsp_set_fields(&rsp_fields);
|
||||
// ASSERT(res == 0);
|
||||
|
||||
ble_gap_adv_start(addrType, NULL, 180000,
|
||||
&adv_params, GAPEventCallback, this);
|
||||
// ASSERT(res == 0);// TODO I've disabled these ASSERT as they sometime asserts and reset the mcu.
|
||||
// For now, the advertising is restarted as soon as it ends. There may be a race condition
|
||||
// that prevent the advertising from restarting reliably.
|
||||
// I remove the assert to prevent this uncesseray crash, but in the long term, the management of
|
||||
// the advertising should be improve (better error handling, and advertise for 3 minutes after
|
||||
// the application has been woken up, for example.
|
||||
}
|
||||
|
||||
int OnAllSvrDisco(uint16_t conn_handle,
|
||||
const struct ble_gatt_error *error,
|
||||
const struct ble_gatt_svc *service,
|
||||
void *arg) {
|
||||
auto nimbleController = static_cast<NimbleController*>(arg);
|
||||
return nimbleController->OnDiscoveryEvent(conn_handle, error, service);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimbleController::OnGAPEvent(ble_gap_event *event) {
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
|
||||
NRF_LOG_INFO("advertise complete; reason=%dn status=%d", event->adv_complete.reason, event->connect.status);
|
||||
break;
|
||||
case BLE_GAP_EVENT_CONNECT: {
|
||||
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
|
||||
|
||||
/* A new connection was established or a connection attempt failed. */
|
||||
NRF_LOG_INFO("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed",
|
||||
event->connect.status);
|
||||
|
||||
if (event->connect.status != 0) {
|
||||
/* Connection failed; resume advertising. */
|
||||
StartAdvertising();
|
||||
bleController.Disconnect();
|
||||
} else {
|
||||
bleController.Connect();
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleConnected);
|
||||
connectionHandle = event->connect.conn_handle;
|
||||
// Service discovery is deffered via systemtask
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_DISCONNECT");
|
||||
NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
|
||||
|
||||
/* Connection terminated; resume advertising. */
|
||||
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||
bleController.Disconnect();
|
||||
StartAdvertising();
|
||||
break;
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONN_UPDATE");
|
||||
/* The central has updated the connection parameters. */
|
||||
NRF_LOG_INFO("connection updated; status=%d ", event->conn_update.status);
|
||||
break;
|
||||
case BLE_GAP_EVENT_ENC_CHANGE:
|
||||
/* Encryption has been enabled or disabled for this connection. */
|
||||
NRF_LOG_INFO("encryption change event; status=%d ", event->enc_change.status);
|
||||
return 0;
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
NRF_LOG_INFO("subscribe event; conn_handle=%d attr_handle=%d "
|
||||
"reason=%d prevn=%d curn=%d previ=%d curi=???\n",
|
||||
event->subscribe.conn_handle,
|
||||
event->subscribe.attr_handle,
|
||||
event->subscribe.reason,
|
||||
event->subscribe.prev_notify,
|
||||
event->subscribe.cur_notify,
|
||||
event->subscribe.prev_indicate);
|
||||
return 0;
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
NRF_LOG_INFO("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
|
||||
event->mtu.conn_handle,
|
||||
event->mtu.channel_id,
|
||||
event->mtu.value);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_REPEAT_PAIRING: {
|
||||
/* We already have a bond with the peer, but it is attempting to
|
||||
* establish a new secure link. This app sacrifices security for
|
||||
* convenience: just throw away the old bond and accept the new link.
|
||||
*/
|
||||
|
||||
/* Delete the old bond. */
|
||||
struct ble_gap_conn_desc desc;
|
||||
ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
|
||||
ble_store_util_delete_peer(&desc.peer_id_addr);
|
||||
|
||||
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
|
||||
* continue with the pairing operation.
|
||||
*/
|
||||
}
|
||||
return BLE_GAP_REPEAT_PAIRING_RETRY;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_RX: {
|
||||
/* Peer sent us a notification or indication. */
|
||||
size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
|
||||
|
||||
NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
|
||||
"attr_len=%d",
|
||||
event->notify_rx.indication ?
|
||||
"indication" :
|
||||
"notification",
|
||||
event->notify_rx.conn_handle,
|
||||
event->notify_rx.attr_handle,
|
||||
notifSize);
|
||||
|
||||
alertNotificationClient.OnNotification(event);
|
||||
return 0;
|
||||
}
|
||||
/* Attribute data is contained in event->notify_rx.attr_data. */
|
||||
|
||||
default:
|
||||
// NRF_LOG_INFO("Advertising event : %d", event->type);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimbleController::OnDiscoveryEvent(uint16_t i, const ble_gatt_error *error, const ble_gatt_svc *service) {
|
||||
if(service == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("Service Discovery complete");
|
||||
if(currentTimeClient.IsDiscovered()) {
|
||||
ble_gattc_disc_all_chrs(connectionHandle, currentTimeClient.StartHandle(), currentTimeClient.EndHandle(),
|
||||
CurrentTimeCharacteristicDiscoveredCallback, this);
|
||||
|
||||
} else if(alertNotificationClient.IsDiscovered()) {
|
||||
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(),
|
||||
AlertNotificationCharacteristicDiscoveredCallback, this);
|
||||
}
|
||||
}
|
||||
|
||||
alertNotificationClient.OnDiscoveryEvent(i, error, service);
|
||||
currentTimeClient.OnDiscoveryEvent(i, error, service);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimbleController::OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic) {
|
||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("CTS characteristic Discovery complete");
|
||||
ble_gattc_read(connectionHandle, currentTimeClient.CurrentTimeHandle(), CurrentTimeReadCallback, this);
|
||||
return 0;
|
||||
}
|
||||
return currentTimeClient.OnCharacteristicDiscoveryEvent(connectionHandle, error, characteristic);
|
||||
}
|
||||
|
||||
int NimbleController::OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic) {
|
||||
if(characteristic == nullptr && error->status == BLE_HS_EDONE) {
|
||||
NRF_LOG_INFO("ANS characteristic Discovery complete");
|
||||
ble_gattc_disc_all_dscs(connectionHandle,
|
||||
alertNotificationClient.NewAlerthandle(), alertNotificationClient.EndHandle(),
|
||||
AlertNotificationDescriptorDiscoveryEventCallback, this);
|
||||
return 0;
|
||||
}
|
||||
return alertNotificationClient.OnCharacteristicsDiscoveryEvent(connectionHandle, error, characteristic);
|
||||
}
|
||||
|
||||
int NimbleController::OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute) {
|
||||
currentTimeClient.OnCurrentTimeReadResult(connectionHandle, error, attribute);
|
||||
|
||||
if (alertNotificationClient.IsDiscovered()) {
|
||||
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(),
|
||||
alertNotificationClient.EndHandle(),
|
||||
AlertNotificationCharacteristicDiscoveredCallback, this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NimbleController::OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
uint16_t characteristicValueHandle,
|
||||
const ble_gatt_dsc *descriptor) {
|
||||
return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor);
|
||||
}
|
||||
|
||||
void NimbleController::StartDiscovery() {
|
||||
ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this);
|
||||
}
|
||||
|
||||
|
||||
uint16_t NimbleController::connHandle() {
|
||||
return connectionHandle;
|
||||
}
|
||||
|
75
src/components/Ble/NimbleController.h
Normal file
75
src/components/Ble/NimbleController.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "AlertNotificationService.h"
|
||||
#include "AlertNotificationClient.h"
|
||||
#include "DeviceInformationService.h"
|
||||
#include "CurrentTimeClient.h"
|
||||
#include "DfuService.h"
|
||||
#include "CurrentTimeService.h"
|
||||
#include "MusicService.h"
|
||||
#include "BatteryInformationService.h"
|
||||
#include "ImmediateAlertService.h"
|
||||
#include <host/ble_gap.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
class SpiNorFlash;
|
||||
}
|
||||
namespace Controllers {
|
||||
class DateTime;
|
||||
|
||||
class NimbleController {
|
||||
|
||||
public:
|
||||
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
|
||||
DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
void Init();
|
||||
void StartAdvertising();
|
||||
int OnGAPEvent(ble_gap_event *event);
|
||||
|
||||
int OnDiscoveryEvent(uint16_t i, const ble_gatt_error *pError, const ble_gatt_svc *pSvc);
|
||||
int OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic);
|
||||
int OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
const ble_gatt_chr *characteristic);
|
||||
int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
|
||||
int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
|
||||
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
|
||||
|
||||
void StartDiscovery();
|
||||
|
||||
Pinetime::Controllers::MusicService& music() {return musicService;};
|
||||
|
||||
uint16_t connHandle();
|
||||
|
||||
private:
|
||||
static constexpr const char* deviceName = "InfiniTime";
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Pinetime::Controllers::Ble& bleController;
|
||||
DateTime& dateTimeController;
|
||||
Pinetime::Controllers::NotificationManager& notificationManager;
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
|
||||
Pinetime::Controllers::DfuService dfuService;
|
||||
|
||||
DeviceInformationService deviceInformationService;
|
||||
CurrentTimeClient currentTimeClient;
|
||||
AlertNotificationService anService;
|
||||
AlertNotificationClient alertNotificationClient;
|
||||
CurrentTimeService currentTimeService;
|
||||
MusicService musicService;
|
||||
BatteryInformationService batteryInformationService;
|
||||
ImmediateAlertService immediateAlertService;
|
||||
|
||||
uint8_t addrType; // 1 = Random, 0 = PUBLIC
|
||||
uint16_t connectionHandle = 0;
|
||||
|
||||
ble_uuid128_t dfuServiceUuid {
|
||||
.u { .type = BLE_UUID_TYPE_128},
|
||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
|
||||
0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
30
src/components/Ble/NotificationManager.cpp
Normal file
30
src/components/Ble/NotificationManager.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include <cstring>
|
||||
#include "NotificationManager.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
void NotificationManager::Push(Pinetime::Controllers::NotificationManager::Categories category,
|
||||
const char *message, uint8_t currentMessageSize) {
|
||||
// TODO handle edge cases on read/write index
|
||||
auto checkedSize = std::min(currentMessageSize, uint8_t{18});
|
||||
auto& notif = notifications[writeIndex];
|
||||
std::memcpy(notif.message.data(), message, checkedSize);
|
||||
notif.message[checkedSize] = '\0';
|
||||
notif.category = category;
|
||||
|
||||
writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0;
|
||||
if(!empty && writeIndex == readIndex)
|
||||
readIndex = writeIndex + 1;
|
||||
}
|
||||
|
||||
NotificationManager::Notification Pinetime::Controllers::NotificationManager::Pop() {
|
||||
// TODO handle edge cases on read/write index
|
||||
NotificationManager::Notification notification = notifications[readIndex];
|
||||
|
||||
if(readIndex != writeIndex) {
|
||||
readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
|
||||
}
|
||||
|
||||
// TODO Check move optimization on return
|
||||
return notification;
|
||||
}
|
29
src/components/Ble/NotificationManager.h
Normal file
29
src/components/Ble/NotificationManager.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class NotificationManager {
|
||||
public:
|
||||
enum class Categories {Unknown, SimpleAlert, Email, News, IncomingCall, MissedCall, Sms, VoiceMail, Schedule, HighProriotyAlert, InstantMessage };
|
||||
static constexpr uint8_t MessageSize{18};
|
||||
|
||||
struct Notification {
|
||||
std::array<char, MessageSize+1> message;
|
||||
Categories category = Categories::Unknown;
|
||||
};
|
||||
|
||||
void Push(Categories category, const char* message, uint8_t messageSize);
|
||||
Notification Pop();
|
||||
|
||||
|
||||
private:
|
||||
static constexpr uint8_t TotalNbNotifications = 5;
|
||||
std::array<Notification, TotalNbNotifications> notifications;
|
||||
uint8_t readIndex = 0;
|
||||
uint8_t writeIndex = 0;
|
||||
bool empty = true;
|
||||
};
|
||||
}
|
||||
}
|
70
src/components/Brightness/BrightnessController.cpp
Normal file
70
src/components/Brightness/BrightnessController.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
28
src/components/Brightness/BrightnessController.h
Normal file
28
src/components/Brightness/BrightnessController.h
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
66
src/components/DateTime/DateTimeController.cpp
Normal file
66
src/components/DateTime/DateTimeController.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
39
src/components/DateTime/DateTimeController.h
Normal file
39
src/components/DateTime/DateTimeController.h
Normal file
@@ -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};
|
||||
};
|
||||
}
|
||||
}
|
20
src/components/FirmwareValidator/FirmwareValidator.cpp
Normal file
20
src/components/FirmwareValidator/FirmwareValidator.cpp
Normal file
@@ -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();
|
||||
}
|
18
src/components/FirmwareValidator/FirmwareValidator.h
Normal file
18
src/components/FirmwareValidator/FirmwareValidator.h
Normal file
@@ -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};
|
||||
};
|
||||
}
|
||||
}
|
207
src/components/Gfx/Gfx.cpp
Normal file
207
src/components/Gfx/Gfx.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <libraries/svc/nrf_svci.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
#include "Gfx.h"
|
||||
#include "../../drivers/St7789.h"
|
||||
using namespace Pinetime::Components;
|
||||
|
||||
Gfx::Gfx(Pinetime::Drivers::St7789 &lcd) : lcd{lcd} {
|
||||
}
|
||||
|
||||
void Gfx::Init() {
|
||||
|
||||
}
|
||||
|
||||
void Gfx::ClearScreen() {
|
||||
SetBackgroundColor(0x0000);
|
||||
|
||||
state.remainingIterations = 240 + 1;
|
||||
state.currentIteration = 0;
|
||||
state.busy = true;
|
||||
state.action = Action::FillRectangle;
|
||||
state.taskToNotify = xTaskGetCurrentTaskHandle();
|
||||
|
||||
lcd.BeginDrawBuffer(0, 0, width, height);
|
||||
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
|
||||
WaitTransfertFinished();
|
||||
|
||||
}
|
||||
|
||||
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
|
||||
SetBackgroundColor(color);
|
||||
|
||||
state.remainingIterations = h;
|
||||
state.currentIteration = 0;
|
||||
state.busy = true;
|
||||
state.action = Action::FillRectangle;
|
||||
state.color = color;
|
||||
state.taskToNotify = xTaskGetCurrentTaskHandle();
|
||||
|
||||
lcd.BeginDrawBuffer(x, y, w, h);
|
||||
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
|
||||
|
||||
WaitTransfertFinished();
|
||||
}
|
||||
|
||||
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
|
||||
state.remainingIterations = h;
|
||||
state.currentIteration = 0;
|
||||
state.busy = true;
|
||||
state.action = Action::FillRectangle;
|
||||
state.color = 0x00;
|
||||
state.taskToNotify = xTaskGetCurrentTaskHandle();
|
||||
|
||||
lcd.BeginDrawBuffer(x, y, w, h);
|
||||
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(b), width * 2);
|
||||
|
||||
WaitTransfertFinished();
|
||||
}
|
||||
|
||||
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) {
|
||||
if (y > (height - p_font->height)) {
|
||||
// Not enough space to write even single char.
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t current_x = x;
|
||||
uint8_t current_y = y;
|
||||
|
||||
for (size_t i = 0; text[i] != '\0'; i++) {
|
||||
if (text[i] == '\n') {
|
||||
current_x = x;
|
||||
current_y += p_font->height + p_font->height / 10;
|
||||
} else {
|
||||
DrawChar(p_font, (uint8_t) text[i], ¤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);
|
||||
WaitTransfertFinished();
|
||||
|
||||
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
|
||||
}
|
||||
|
||||
void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
|
||||
lcd.DrawPixel(x, y, color);
|
||||
}
|
||||
|
||||
void Gfx::Sleep() {
|
||||
lcd.Sleep();
|
||||
}
|
||||
|
||||
void Gfx::Wakeup() {
|
||||
lcd.Wakeup();
|
||||
}
|
||||
|
||||
void Gfx::SetBackgroundColor(uint16_t color) {
|
||||
for(int i = 0; i < width; i++) {
|
||||
buffer[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
bool Gfx::GetNextBuffer(uint8_t **data, size_t &size) {
|
||||
if(!state.busy) return false;
|
||||
state.remainingIterations--;
|
||||
if (state.remainingIterations == 0) {
|
||||
state.busy = false;
|
||||
NotifyEndOfTransfert(state.taskToNotify);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(state.action == Action::FillRectangle) {
|
||||
*data = reinterpret_cast<uint8_t *>(buffer);
|
||||
size = width * 2;
|
||||
} else if(state.action == Action::DrawChar) {
|
||||
uint16_t bg = 0x0000;
|
||||
uint8_t char_idx = state.character - state.font->startChar;
|
||||
uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
|
||||
|
||||
for (uint16_t j = 0; j < bytes_in_line; j++) {
|
||||
for (uint8_t k = 0; k < 8; k++) {
|
||||
if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration+1) * bytes_in_line) + j]) {
|
||||
buffer[(j*8)+k] = state.color;
|
||||
}
|
||||
else {
|
||||
buffer[(j*8)+k] = bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*data = reinterpret_cast<uint8_t *>(buffer);
|
||||
size = bytes_in_line*8*2;
|
||||
}
|
||||
|
||||
state.currentIteration++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gfx::NotifyEndOfTransfert(TaskHandle_t task) {
|
||||
if(task != nullptr) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
}
|
||||
|
||||
void Gfx::WaitTransfertFinished() const {
|
||||
ulTaskNotifyTake(pdTRUE, 500);
|
||||
}
|
||||
|
||||
void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
|
||||
lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
|
||||
}
|
||||
|
||||
void Gfx::SetScrollStartLine(uint16_t line) {
|
||||
lcd.VerticalScrollStartAddress(line);
|
||||
}
|
||||
|
60
src/components/Gfx/Gfx.h
Normal file
60
src/components/Gfx/Gfx.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <nrf_font.h>
|
||||
#include <drivers/BufferProvider.h>
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
class St7789;
|
||||
}
|
||||
namespace Components {
|
||||
class Gfx : public Pinetime::Drivers::BufferProvider {
|
||||
public:
|
||||
explicit Gfx(Drivers::St7789& lcd);
|
||||
void Init();
|
||||
void ClearScreen();
|
||||
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO *p_font, bool wrap);
|
||||
void DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color);
|
||||
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
|
||||
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
|
||||
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
|
||||
void SetScrollStartLine(uint16_t line);
|
||||
|
||||
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
bool GetNextBuffer(uint8_t **buffer, size_t &size) override;
|
||||
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
|
||||
|
||||
|
||||
private:
|
||||
static constexpr uint8_t width = 240;
|
||||
static constexpr uint8_t height = 240;
|
||||
|
||||
enum class Action { None, FillRectangle, DrawChar};
|
||||
struct State {
|
||||
State() : busy{false}, action{Action::None}, remainingIterations{0}, currentIteration{0} {}
|
||||
volatile bool busy;
|
||||
volatile Action action;
|
||||
volatile uint16_t remainingIterations;
|
||||
volatile uint16_t currentIteration;
|
||||
volatile FONT_INFO *font;
|
||||
volatile uint16_t color;
|
||||
volatile uint8_t character;
|
||||
volatile TaskHandle_t taskToNotify = nullptr;
|
||||
};
|
||||
|
||||
volatile State state;
|
||||
|
||||
uint16_t buffer[width]; // 1 line buffer
|
||||
Drivers::St7789& lcd;
|
||||
|
||||
void SetBackgroundColor(uint16_t color);
|
||||
void WaitTransfertFinished() const;
|
||||
void NotifyEndOfTransfert(TaskHandle_t task);
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user