summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichele Bini <michele.bini@gmail.com>2022-03-30 20:50:42 (GMT)
committerMichele Bini <michele.bini@gmail.com>2022-03-31 00:12:29 (GMT)
commit12e938959eb1e2146fdec3fee7b9a304aa6889d2 (patch)
treef57f54615ad00327e8dc33c958ec67773c49f655
parentff68f91be9cce2fb27d3b3aea9ab26fa224ea0eb (diff)
with heart
-rw-r--r--src/CMakeLists.txt21
-rw-r--r--src/components/ble/HeartRateService.cpp82
-rw-r--r--src/components/ble/HeartRateService.h42
-rw-r--r--src/components/ble/NimbleController.cpp13
-rw-r--r--src/components/ble/NimbleController.h5
-rw-r--r--src/components/heartrate/Biquad.cpp26
-rw-r--r--src/components/heartrate/Biquad.h22
-rw-r--r--src/components/heartrate/HeartRateController.cpp35
-rw-r--r--src/components/heartrate/HeartRateController.h40
-rw-r--r--src/components/heartrate/Ppg.cpp99
-rw-r--r--src/components/heartrate/Ppg.h31
-rw-r--r--src/components/heartrate/Ptagc.cpp27
-rw-r--r--src/components/heartrate/Ptagc.h17
-rw-r--r--src/displayapp/Apps.h1
-rw-r--r--src/displayapp/DisplayApp.cpp7
-rw-r--r--src/displayapp/DisplayApp.h3
-rw-r--r--src/displayapp/DisplayAppRecovery.cpp1
-rw-r--r--src/displayapp/DisplayAppRecovery.h2
-rw-r--r--src/displayapp/screens/ApplicationList.cpp1
-rw-r--r--src/displayapp/screens/Clock.cpp3
-rw-r--r--src/displayapp/screens/Clock.h3
-rw-r--r--src/displayapp/screens/HeartRate.cpp115
-rw-r--r--src/displayapp/screens/HeartRate.h40
-rw-r--r--src/displayapp/screens/WatchFaceDigital.cpp27
-rw-r--r--src/displayapp/screens/WatchFaceDigital.h7
-rw-r--r--src/heartratetask/HeartRateTask.cpp100
-rw-r--r--src/heartratetask/HeartRateTask.h40
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/README.md9
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/pkg.yml40
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/src/blehr_sens.h46
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/src/gatt_svr.c177
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/src/main.c261
-rw-r--r--src/libs/mynewt-nimble/apps/blehr/syscfg.yml35
-rw-r--r--src/main.cpp10
-rw-r--r--src/systemtask/SystemTask.cpp15
-rw-r--r--src/systemtask/SystemTask.h7
36 files changed, 1407 insertions, 3 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c1bb32f..7ad520a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -397,6 +397,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/ApplicationList.cpp
displayapp/screens/Notifications.cpp
displayapp/screens/FlashLight.cpp
+ displayapp/screens/HeartRate.cpp
displayapp/screens/List.cpp
displayapp/screens/Error.cpp
displayapp/screens/Styles.cpp
@@ -443,6 +444,7 @@ list(APPEND SOURCE_FILES
components/ble/BatteryInformationService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
+ components/ble/HeartRateService.cpp
components/firmwarevalidator/FirmwareValidator.cpp
components/motor/MotorController.cpp
components/settings/Settings.cpp
@@ -460,6 +462,12 @@ list(APPEND SOURCE_FILES
systemtask/SystemMonitor.cpp
drivers/TwiMaster.cpp
+ heartratetask/HeartRateTask.cpp
+ components/heartrate/Ppg.cpp
+ components/heartrate/Biquad.cpp
+ components/heartrate/Ptagc.cpp
+ components/heartrate/HeartRateController.cpp
+
buttonhandler/ButtonHandler.cpp
touchhandler/TouchHandler.cpp
)
@@ -497,6 +505,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/BatteryInformationService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
+ components/ble/HeartRateService.cpp
components/firmwarevalidator/FirmwareValidator.cpp
components/settings/Settings.cpp
drivers/Cst816s.cpp
@@ -509,6 +518,11 @@ list(APPEND RECOVERY_SOURCE_FILES
drivers/TwiMaster.cpp
components/gfx/Gfx.cpp
components/rle/RleDecoder.cpp
+ components/heartrate/HeartRateController.cpp
+ heartratetask/HeartRateTask.cpp
+ components/heartrate/Ppg.cpp
+ components/heartrate/Biquad.cpp
+ components/heartrate/Ptagc.cpp
components/motor/MotorController.cpp
buttonhandler/ButtonHandler.cpp
touchhandler/TouchHandler.cpp
@@ -559,6 +573,7 @@ set(INCLUDE_FILES
displayapp/screens/ApplicationList.h
displayapp/Apps.h
displayapp/screens/Notifications.h
+ displayapp/screens/HeartRate.h
displayapp/Colors.h
drivers/St7789.h
drivers/SpiNorFlash.h
@@ -591,6 +606,7 @@ set(INCLUDE_FILES
components/ble/ImmediateAlertService.h
components/ble/ServiceDiscovery.h
components/ble/BleClient.h
+ components/ble/HeartRateService.h
components/settings/Settings.h
drivers/Cst816s.h
FreeRTOS/portmacro.h
@@ -609,6 +625,11 @@ set(INCLUDE_FILES
systemtask/SystemMonitor.h
displayapp/screens/Symbols.h
drivers/TwiMaster.h
+ heartratetask/HeartRateTask.h
+ components/heartrate/Ppg.h
+ components/heartrate/Biquad.h
+ components/heartrate/Ptagc.h
+ components/heartrate/HeartRateController.h
components/motor/MotorController.h
buttonhandler/ButtonHandler.h
touchhandler/TouchHandler.h
diff --git a/src/components/ble/HeartRateService.cpp b/src/components/ble/HeartRateService.cpp
new file mode 100644
index 0000000..4824a6b
--- /dev/null
+++ b/src/components/ble/HeartRateService.cpp
@@ -0,0 +1,82 @@
+#include "components/ble/HeartRateService.h"
+#include "components/heartrate/HeartRateController.h"
+#include "systemtask/SystemTask.h"
+#include <nrf_log.h>
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t HeartRateService::heartRateServiceUuid;
+constexpr ble_uuid16_t HeartRateService::heartRateMeasurementUuid;
+
+namespace {
+ int HeartRateServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto* heartRateService = static_cast<HeartRateService*>(arg);
+ return heartRateService->OnHeartRateRequested(conn_handle, attr_handle, ctxt);
+ }
+}
+
+// TODO Refactoring - remove dependency to SystemTask
+HeartRateService::HeartRateService(Pinetime::System::SystemTask& system, Controllers::HeartRateController& heartRateController)
+ : system {system},
+ heartRateController {heartRateController},
+ characteristicDefinition {{.uuid = &heartRateMeasurementUuid.u,
+ .access_cb = HeartRateServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &heartRateMeasurementHandle},
+ {0}},
+ serviceDefinition {
+ {/* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = &heartRateServiceUuid.u,
+ .characteristics = characteristicDefinition},
+ {0},
+ } {
+ // TODO refactor to prevent this loop dependency (service depends on controller and controller depends on service)
+ heartRateController.SetService(this);
+}
+
+void HeartRateService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int HeartRateService::OnHeartRateRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
+ if (attributeHandle == heartRateMeasurementHandle) {
+ NRF_LOG_INFO("HEARTRATE : handle = %d", heartRateMeasurementHandle);
+ uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value
+
+ int res = os_mbuf_append(context->om, buffer, 2);
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ return 0;
+}
+
+void HeartRateService::OnNewHeartRateValue(uint8_t heartRateValue) {
+ if(!heartRateMeasurementNotificationEnable) return;
+
+ uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value
+ auto* om = ble_hs_mbuf_from_flat(buffer, 2);
+
+ uint16_t connectionHandle = system.nimble().connHandle();
+
+ if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return;
+ }
+
+ ble_gattc_notify_custom(connectionHandle, heartRateMeasurementHandle, om);
+}
+
+void HeartRateService::SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
+ if(attributeHandle == heartRateMeasurementHandle)
+ heartRateMeasurementNotificationEnable = true;
+}
+
+void HeartRateService::UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle) {
+ if(attributeHandle == heartRateMeasurementHandle)
+ heartRateMeasurementNotificationEnable = false;
+} \ No newline at end of file
diff --git a/src/components/ble/HeartRateService.h b/src/components/ble/HeartRateService.h
new file mode 100644
index 0000000..4e4a5a4
--- /dev/null
+++ b/src/components/ble/HeartRateService.h
@@ -0,0 +1,42 @@
+#pragma once
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#include <atomic>
+#undef max
+#undef min
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class HeartRateController;
+ class HeartRateService {
+ public:
+ HeartRateService(Pinetime::System::SystemTask& system, Controllers::HeartRateController& heartRateController);
+ void Init();
+ int OnHeartRateRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
+ void OnNewHeartRateValue(uint8_t hearRateValue);
+
+ void SubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
+ void UnsubscribeNotification(uint16_t connectionHandle, uint16_t attributeHandle);
+
+ private:
+ Pinetime::System::SystemTask& system;
+ Controllers::HeartRateController& heartRateController;
+ static constexpr uint16_t heartRateServiceId {0x180D};
+ static constexpr uint16_t heartRateMeasurementId {0x2A37};
+
+ static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
+
+ static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
+
+ struct ble_gatt_chr_def characteristicDefinition[2];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t heartRateMeasurementHandle;
+ std::atomic_bool heartRateMeasurementNotificationEnable {false};
+ };
+ }
+}
diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp
index f63d98c..bc712f4 100644
--- a/src/components/ble/NimbleController.cpp
+++ b/src/components/ble/NimbleController.cpp
@@ -27,7 +27,8 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
DateTime& dateTimeController,
NotificationManager& notificationManager,
Battery& batteryController,
- Pinetime::Drivers::SpiNorFlash& spiNorFlash
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash,
+ HeartRateController& heartRateController
)
: systemTask {systemTask},
bleController {bleController},
@@ -42,6 +43,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
currentTimeService {dateTimeController},
batteryInformationService {batteryController},
immediateAlertService {systemTask, notificationManager},
+ heartRateService {systemTask, heartRateController},
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
}
@@ -84,6 +86,7 @@ void NimbleController::Init() {
dfuService.Init();
batteryInformationService.Init();
immediateAlertService.Init();
+ heartRateService.Init();
int rc;
rc = ble_hs_util_ensure_addr(0);
@@ -240,6 +243,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
event->subscribe.prev_notify,
event->subscribe.cur_notify,
event->subscribe.prev_indicate);
+
+ if (event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
+ heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+ } else if (event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
+ heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+ } else if (event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
+ heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+ }
break;
case BLE_GAP_EVENT_MTU:
diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index c9d6420..0facca6 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -14,6 +14,7 @@
#include "components/ble/CurrentTimeService.h"
#include "components/ble/DeviceInformationService.h"
#include "components/ble/DfuService.h"
+#include "components/ble/HeartRateService.h"
#include "components/ble/ImmediateAlertService.h"
#include "components/ble/ServiceDiscovery.h"
@@ -39,7 +40,8 @@ namespace Pinetime {
DateTime& dateTimeController,
NotificationManager& notificationManager,
Battery& batteryController,
- Pinetime::Drivers::SpiNorFlash& spiNorFlash);
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash,
+ HeartRateController& heartRateController);
void Init();
void StartAdvertising();
int OnGAPEvent(ble_gap_event* event);
@@ -72,6 +74,7 @@ namespace Pinetime {
CurrentTimeService currentTimeService;
BatteryInformationService batteryInformationService;
ImmediateAlertService immediateAlertService;
+ HeartRateService heartRateService;
ServiceDiscovery serviceDiscovery;
uint8_t addrType;
diff --git a/src/components/heartrate/Biquad.cpp b/src/components/heartrate/Biquad.cpp
new file mode 100644
index 0000000..b7edd40
--- /dev/null
+++ b/src/components/heartrate/Biquad.cpp
@@ -0,0 +1,26 @@
+/*
+ SPDX-License-Identifier: LGPL-3.0-or-later
+ Original work Copyright (C) 2020 Daniel Thompson
+ C++ port Copyright (C) 2021 Jean-François Milants
+*/
+
+#include "components/heartrate/Biquad.h"
+
+using namespace Pinetime::Controllers;
+
+/** Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/ppg.py */
+Biquad::Biquad(float b0, float b1, float b2, float a1, float a2) : b0 {b0}, b1 {b1}, b2 {b2}, a1 {a1}, a2 {a2} {
+}
+
+float Biquad::Step(float x) {
+ auto v1 = this->v1;
+ auto v2 = this->v2;
+
+ auto v = x - (a1 * v1) - (a2 * v2);
+ auto y = (b0 * v) + (b1 * v1) + (b2 * v2);
+
+ this->v2 = v1;
+ this->v1 = v;
+
+ return y;
+}
diff --git a/src/components/heartrate/Biquad.h b/src/components/heartrate/Biquad.h
new file mode 100644
index 0000000..7c8ca58
--- /dev/null
+++ b/src/components/heartrate/Biquad.h
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Controllers {
+ /// Direct Form II Biquad Filter
+ class Biquad {
+ public:
+ Biquad(float b0, float b1, float b2, float a1, float a2);
+ float Step(float x);
+
+ private:
+ float b0;
+ float b1;
+ float b2;
+ float a1;
+ float a2;
+
+ float v1 = 0.0f;
+ float v2 = 0.0f;
+ };
+ }
+}
diff --git a/src/components/heartrate/HeartRateController.cpp b/src/components/heartrate/HeartRateController.cpp
new file mode 100644
index 0000000..e0d6927
--- /dev/null
+++ b/src/components/heartrate/HeartRateController.cpp
@@ -0,0 +1,35 @@
+#include "components/heartrate/HeartRateController.h"
+#include <heartratetask/HeartRateTask.h>
+#include <systemtask/SystemTask.h>
+
+using namespace Pinetime::Controllers;
+
+void HeartRateController::Update(HeartRateController::States newState, uint8_t heartRate) {
+ this->state = newState;
+ if (this->heartRate != heartRate) {
+ this->heartRate = heartRate;
+ service->OnNewHeartRateValue(heartRate);
+ }
+}
+
+void HeartRateController::Start() {
+ if (task != nullptr) {
+ state = States::NotEnoughData;
+ task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StartMeasurement);
+ }
+}
+
+void HeartRateController::Stop() {
+ if (task != nullptr) {
+ state = States::Stopped;
+ task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StopMeasurement);
+ }
+}
+
+void HeartRateController::SetHeartRateTask(Pinetime::Applications::HeartRateTask* task) {
+ this->task = task;
+}
+
+void HeartRateController::SetService(Pinetime::Controllers::HeartRateService* service) {
+ this->service = service;
+}
diff --git a/src/components/heartrate/HeartRateController.h b/src/components/heartrate/HeartRateController.h
new file mode 100644
index 0000000..a63f1a7
--- /dev/null
+++ b/src/components/heartrate/HeartRateController.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <cstdint>
+#include <components/ble/HeartRateService.h>
+
+namespace Pinetime {
+ namespace Applications {
+ class HeartRateTask;
+ }
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class HeartRateController {
+ public:
+ enum class States { Stopped, NotEnoughData, NoTouch, Running };
+
+ HeartRateController() = default;
+ void Start();
+ void Stop();
+ void Update(States newState, uint8_t heartRate);
+
+ void SetHeartRateTask(Applications::HeartRateTask* task);
+ States State() const {
+ return state;
+ }
+ uint8_t HeartRate() const {
+ return heartRate;
+ }
+
+ void SetService(Pinetime::Controllers::HeartRateService* service);
+
+ private:
+ Applications::HeartRateTask* task = nullptr;
+ States state = States::Stopped;
+ uint8_t heartRate = 0;
+ Pinetime::Controllers::HeartRateService* service = nullptr;
+ };
+ }
+} \ No newline at end of file
diff --git a/src/components/heartrate/Ppg.cpp b/src/components/heartrate/Ppg.cpp
new file mode 100644
index 0000000..a5d8369
--- /dev/null
+++ b/src/components/heartrate/Ppg.cpp
@@ -0,0 +1,99 @@
+/*
+ SPDX-License-Identifier: LGPL-3.0-or-later
+ Original work Copyright (C) 2020 Daniel Thompson
+ C++ port Copyright (C) 2021 Jean-François Milants
+*/
+
+#include "components/heartrate/Ppg.h"
+#include <vector>
+#include <nrf_log.h>
+using namespace Pinetime::Controllers;
+
+/** Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/ppg.py */
+namespace {
+ int Compare(int8_t* d1, int8_t* d2, size_t count) {
+ int e = 0;
+ for (size_t i = 0; i < count; i++) {
+ auto d = d1[i] - d2[i];
+ e += d * d;
+ }
+ return e;
+ }
+
+ int CompareShift(int8_t* d, int shift, size_t count) {
+ return Compare(d + shift, d, count - shift);
+ }
+
+ int Trough(int8_t* d, size_t size, uint8_t mn, uint8_t mx) {
+ auto z2 = CompareShift(d, mn - 2, size);
+ auto z1 = CompareShift(d, mn - 1, size);
+ for (int i = mn; i < mx + 1; i++) {
+ auto z = CompareShift(d, i, size);
+ if (z2 > z1 && z1 < z)
+ return i;
+ z2 = z1;
+ z1 = z;
+ }
+ return -1;
+ }
+}
+
+Ppg::Ppg()
+ : hpf {0.87033078, -1.74066156, 0.87033078, -1.72377617, 0.75754694},
+ agc {20, 0.971, 2},
+ lpf {0.11595249, 0.23190498, 0.11595249, -0.72168143, 0.18549138} {
+}
+
+int8_t Ppg::Preprocess(float spl) {
+ spl -= offset;
+ spl = hpf.Step(spl);
+ spl = agc.Step(spl);
+ spl = lpf.Step(spl);
+
+ auto spl_int = static_cast<int8_t>(spl);
+
+ if (dataIndex < 200)
+ data[dataIndex++] = spl_int;
+ return spl_int;
+}
+
+float Ppg::HeartRate() {
+ if (dataIndex < 200)
+ return 0;
+
+ NRF_LOG_INFO("PREPROCESS, offset = %d", offset);
+ auto hr = ProcessHeartRate();
+ dataIndex = 0;
+ return hr;
+}
+float Ppg::ProcessHeartRate() {
+ auto t0 = Trough(data.data(), dataIndex, 7, 48);
+ if (t0 < 0)
+ return 0;
+
+ float t1 = t0 * 2;
+ t1 = Trough(data.data(), dataIndex, t1 - 5, t1 + 5);
+ if (t1 < 0)
+ return 0;
+
+ float t2 = static_cast<int>(t1 * 3) / 2;
+ t2 = Trough(data.data(), dataIndex, t2 - 5, t2 + 5);
+ if (t2 < 0)
+ return 0;
+
+ float t3 = static_cast<int>(t2 * 4) / 3;
+ t3 = Trough(data.data(), dataIndex, t3 - 4, t3 + 4);
+ if (t3 < 0)
+ return static_cast<int>(60 * 24 * 3) / static_cast<int>(t2);
+
+ return static_cast<int>(60 * 24 * 4) / static_cast<int>(t3);
+}
+
+void Ppg::SetOffset(uint16_t offset) {
+ this->offset = offset;
+ dataIndex = 0;
+}
+
+void Ppg::Reset() {
+ dataIndex = 0;
+}
diff --git a/src/components/heartrate/Ppg.h b/src/components/heartrate/Ppg.h
new file mode 100644
index 0000000..7000c87
--- /dev/null
+++ b/src/components/heartrate/Ppg.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include "components/heartrate/Biquad.h"
+#include "components/heartrate/Ptagc.h"
+
+namespace Pinetime {
+ namespace Controllers {
+ class Ppg {
+ public:
+ Ppg();
+ int8_t Preprocess(float spl);
+ float HeartRate();
+
+ void SetOffset(uint16_t i);
+ void Reset();
+
+ private:
+ std::array<int8_t, 200> data;
+ size_t dataIndex = 0;
+ float offset;
+ Biquad hpf;
+ Ptagc agc;
+ Biquad lpf;
+
+ float ProcessHeartRate();
+ };
+ }
+}
diff --git a/src/components/heartrate/Ptagc.cpp b/src/components/heartrate/Ptagc.cpp
new file mode 100644
index 0000000..1c60bc2
--- /dev/null
+++ b/src/components/heartrate/Ptagc.cpp
@@ -0,0 +1,27 @@
+/*
+ SPDX-License-Identifier: LGPL-3.0-or-later
+ Original work Copyright (C) 2020 Daniel Thompson
+ C++ port Copyright (C) 2021 Jean-François Milants
+*/
+
+#include "components/heartrate/Ptagc.h"
+#include <cmath>
+
+using namespace Pinetime::Controllers;
+
+/** Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/ppg.py */
+Ptagc::Ptagc(float start, float decay, float threshold) : peak {start}, decay {decay}, boost {1.0f / decay}, threshold {threshold} {
+}
+
+float Ptagc::Step(float spl) {
+ if (std::abs(spl) > peak)
+ peak *= boost;
+ else
+ peak *= decay;
+
+ if ((spl > (peak * threshold)) || (spl < (peak * -threshold)))
+ return 0.0f;
+
+ spl = 100.0f * spl / (2.0f * peak);
+ return spl;
+}
diff --git a/src/components/heartrate/Ptagc.h b/src/components/heartrate/Ptagc.h
new file mode 100644
index 0000000..3476636
--- /dev/null
+++ b/src/components/heartrate/Ptagc.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Controllers {
+ class Ptagc {
+ public:
+ Ptagc(float start, float decay, float threshold);
+ float Step(float spl);
+
+ private:
+ float peak;
+ float decay;
+ float boost;
+ float threshold;
+ };
+ }
+}
diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h
index 3cbaa9e..ff3f4eb 100644
--- a/src/displayapp/Apps.h
+++ b/src/displayapp/Apps.h
@@ -11,6 +11,7 @@ namespace Pinetime {
NotificationsPreview,
Notifications,
FlashLight,
+ HeartRate,
QuickSettings,
Settings,
SettingTimeFormat,
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index 9219472..47b0f06 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -1,5 +1,6 @@
#include "displayapp/DisplayApp.h"
#include <libraries/log/nrf_log.h>
+#include "displayapp/screens/HeartRate.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/datetime/DateTimeController.h"
@@ -70,6 +71,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Controllers::DateTime& dateTimeController,
Drivers::WatchdogView& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController,
@@ -83,6 +85,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
dateTimeController {dateTimeController},
watchdog {watchdog},
notificationManager {notificationManager},
+ heartRateController {heartRateController},
settingsController {settingsController},
motorController {motorController},
motionController {motionController},
@@ -298,6 +301,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
bleController,
notificationManager,
settingsController,
+ heartRateController,
motionController,
*systemTask);
break;
@@ -355,6 +359,9 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
currentScreen = std::make_unique<Screens::FlashLight>(this, *systemTask, brightnessController);
ReturnApp(Apps::QuickSettings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
break;
+ case Apps::HeartRate:
+ currentScreen = std::make_unique<Screens::HeartRate>(this, heartRateController, *systemTask);
+ break;
}
currentApp = app;
}
diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h
index b79952d..60db552 100644
--- a/src/displayapp/DisplayApp.h
+++ b/src/displayapp/DisplayApp.h
@@ -31,6 +31,7 @@ namespace Pinetime {
class Ble;
class DateTime;
class NotificationManager;
+ class HeartRateController;
class MotionController;
class TouchHandler;
}
@@ -52,6 +53,7 @@ namespace Pinetime {
Controllers::DateTime& dateTimeController,
Drivers::WatchdogView& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController,
@@ -76,6 +78,7 @@ namespace Pinetime {
Pinetime::Drivers::WatchdogView& watchdog;
Pinetime::System::SystemTask* systemTask = nullptr;
Pinetime::Controllers::NotificationManager& notificationManager;
+ Pinetime::Controllers::HeartRateController& heartRateController;
Pinetime::Controllers::Settings& settingsController;
Pinetime::Controllers::MotorController& motorController;
Pinetime::Controllers::MotionController& motionController;
diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp
index a33308f..f577679 100644
--- a/src/displayapp/DisplayAppRecovery.cpp
+++ b/src/displayapp/DisplayAppRecovery.cpp
@@ -17,6 +17,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Controllers::DateTime& dateTimeController,
Drivers::WatchdogView& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController,
diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h
index 8f25cbe..4e383e2 100644
--- a/src/displayapp/DisplayAppRecovery.h
+++ b/src/displayapp/DisplayAppRecovery.h
@@ -28,6 +28,7 @@ namespace Pinetime {
class Ble;
class DateTime;
class NotificationManager;
+ class HeartRateController;
class MotionController;
class TouchHandler;
class MotorController;
@@ -50,6 +51,7 @@ namespace Pinetime {
Controllers::DateTime& dateTimeController,
Drivers::WatchdogView& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Controllers::Settings& settingsController,
Pinetime::Controllers::MotorController& motorController,
Pinetime::Controllers::MotionController& motionController,
diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp
index b7c8ebd..c8d608a 100644
--- a/src/displayapp/screens/ApplicationList.cpp
+++ b/src/displayapp/screens/ApplicationList.cpp
@@ -37,6 +37,7 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
std::unique_ptr<Screen> ApplicationList::CreateScreen1() {
std::array<Screens::Tile::Applications, 6> applications {{
{Symbols::clock},
+ {Symbols::heartBeat, Apps::HeartRate},
}};
return std::make_unique<Screens::Tile>(0, 1, app, settingsController, batteryController, dateTimeController, applications);
diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp
index ab4cafd..1893856 100644
--- a/src/displayapp/screens/Clock.cpp
+++ b/src/displayapp/screens/Clock.cpp
@@ -19,6 +19,7 @@ Clock::Clock(DisplayApp* app,
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
+ Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
System::SystemTask& systemTask)
: Screen(app),
@@ -28,6 +29,7 @@ Clock::Clock(DisplayApp* app,
bleController {bleController},
notificatioManager {notificatioManager},
settingsController {settingsController},
+ heartRateController {heartRateController},
motionController {motionController},
screen {[this, &settingsController]() {
switch (settingsController.GetClockFace()) {
@@ -59,6 +61,7 @@ std::unique_ptr<Screen> Clock::WatchFaceDigitalScreen() {
bleController,
notificatioManager,
settingsController,
+ heartRateController,
motionController,
systemTask);
}
diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h
index 5001dce..86793e9 100644
--- a/src/displayapp/screens/Clock.h
+++ b/src/displayapp/screens/Clock.h
@@ -4,6 +4,7 @@
#include <chrono>
#include <cstdint>
#include <memory>
+#include <components/heartrate/HeartRateController.h>
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "systemtask/SystemTask.h"
@@ -27,6 +28,7 @@ namespace Pinetime {
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
+ Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
System::SystemTask& systemTask);
~Clock() override;
@@ -41,6 +43,7 @@ namespace Pinetime {
Controllers::Ble& bleController;
Controllers::NotificationManager& notificatioManager;
Controllers::Settings& settingsController;
+ Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
std::unique_ptr<Screen> screen;
diff --git a/src/displayapp/screens/HeartRate.cpp b/src/displayapp/screens/HeartRate.cpp
new file mode 100644
index 0000000..6f9154b
--- /dev/null
+++ b/src/displayapp/screens/HeartRate.cpp
@@ -0,0 +1,115 @@
+#include "displayapp/screens/HeartRate.h"
+#include <lvgl/lvgl.h>
+#include <components/heartrate/HeartRateController.h>
+
+#include "displayapp/DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+
+namespace {
+ const char* ToString(Pinetime::Controllers::HeartRateController::States s) {
+ switch (s) {
+ case Pinetime::Controllers::HeartRateController::States::NotEnoughData:
+ return "Not enough data,\nplease wait...";
+ case Pinetime::Controllers::HeartRateController::States::NoTouch:
+ return "No touch detected";
+ case Pinetime::Controllers::HeartRateController::States::Running:
+ return "Measuring...";
+ case Pinetime::Controllers::HeartRateController::States::Stopped:
+ return "Stopped";
+ }
+ return "";
+ }
+
+ static void btnStartStopEventHandler(lv_obj_t* obj, lv_event_t event) {
+ HeartRate* screen = static_cast<HeartRate*>(obj->user_data);
+ screen->OnStartStopEvent(event);
+ }
+}
+
+HeartRate::HeartRate(Pinetime::Applications::DisplayApp* app,
+ Controllers::HeartRateController& heartRateController,
+ System::SystemTask& systemTask)
+ : Screen(app), heartRateController {heartRateController}, systemTask {systemTask} {
+ bool isHrRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
+ label_hr = lv_label_create(lv_scr_act(), nullptr);
+
+ lv_obj_set_style_local_text_font(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &neofont3);
+
+ if (isHrRunning)
+ lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
+ else
+ lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
+
+ lv_label_set_text(label_hr, "000");
+ lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
+
+ label_bpm = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(label_bpm, "Heart rate BPM");
+ lv_obj_align(label_bpm, label_hr, LV_ALIGN_OUT_TOP_MID, 0, -20);
+
+ label_status = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(label_status, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x222222));
+ lv_label_set_text(label_status, ToString(Pinetime::Controllers::HeartRateController::States::NotEnoughData));
+
+ lv_obj_align(label_status, label_hr, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
+
+ btn_startStop = lv_btn_create(lv_scr_act(), nullptr);
+ btn_startStop->user_data = this;
+ lv_obj_set_height(btn_startStop, 50);
+ lv_obj_set_event_cb(btn_startStop, btnStartStopEventHandler);
+ lv_obj_align(btn_startStop, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+
+ label_startStop = lv_label_create(btn_startStop, nullptr);
+ UpdateStartStopButton(isHrRunning);
+ if (isHrRunning)
+ systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
+
+ taskRefresh = lv_task_create(RefreshTaskCallback, 100, LV_TASK_PRIO_MID, this);
+}
+
+HeartRate::~HeartRate() {
+ lv_task_del(taskRefresh);
+ lv_obj_clean(lv_scr_act());
+ systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
+}
+
+void HeartRate::Refresh() {
+
+ auto state = heartRateController.State();
+ switch (state) {
+ case Controllers::HeartRateController::States::NoTouch:
+ case Controllers::HeartRateController::States::NotEnoughData:
+ // case Controllers::HeartRateController::States::Stopped:
+ lv_label_set_text(label_hr, "000");
+ break;
+ default:
+ lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
+ }
+
+ lv_label_set_text(label_status, ToString(state));
+ lv_obj_align(label_status, label_hr, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
+}
+
+void HeartRate::OnStartStopEvent(lv_event_t event) {
+ if (event == LV_EVENT_CLICKED) {
+ if (heartRateController.State() == Controllers::HeartRateController::States::Stopped) {
+ heartRateController.Start();
+ UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
+ systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
+ lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
+ } else {
+ heartRateController.Stop();
+ UpdateStartStopButton(heartRateController.State() != Controllers::HeartRateController::States::Stopped);
+ systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
+ lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
+ }
+ }
+}
+
+void HeartRate::UpdateStartStopButton(bool isRunning) {
+ if (isRunning)
+ lv_label_set_text(label_startStop, "Stop");
+ else
+ lv_label_set_text(label_startStop, "Start");
+}
diff --git a/src/displayapp/screens/HeartRate.h b/src/displayapp/screens/HeartRate.h
new file mode 100644
index 0000000..2ad0035
--- /dev/null
+++ b/src/displayapp/screens/HeartRate.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+#include "displayapp/screens/Screen.h"
+#include "systemtask/SystemTask.h"
+#include <lvgl/src/lv_core/lv_style.h>
+#include <lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Controllers {
+ class HeartRateController;
+ }
+ namespace Applications {
+ namespace Screens {
+
+ class HeartRate : public Screen {
+ public:
+ HeartRate(DisplayApp* app, Controllers::HeartRateController& HeartRateController, System::SystemTask& systemTask);
+ ~HeartRate() override;
+
+ void Refresh() override;
+
+ void OnStartStopEvent(lv_event_t event);
+
+ private:
+ Controllers::HeartRateController& heartRateController;
+ Pinetime::System::SystemTask& systemTask;
+ void UpdateStartStopButton(bool isRunning);
+ lv_obj_t* label_hr;
+ lv_obj_t* label_bpm;
+ lv_obj_t* label_status;
+ lv_obj_t* btn_startStop;
+ lv_obj_t* label_startStop;
+
+ lv_task_t* taskRefresh;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp
index 963bfe4..44421ff 100644
--- a/src/displayapp/screens/WatchFaceDigital.cpp
+++ b/src/displayapp/screens/WatchFaceDigital.cpp
@@ -10,6 +10,7 @@
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
+#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/settings/Settings.h"
#include "displayapp/fonts/neofont.h"
@@ -21,6 +22,7 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
+ Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
System::SystemTask& systemTask)
: Screen(app),
@@ -31,6 +33,7 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
bleController {bleController},
notificatioManager {notificatioManager},
settingsController {settingsController},
+ heartRateController {heartRateController},
motionController {motionController} {
if (1) {
@@ -254,6 +257,16 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, "");
+ heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
+ lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
+
+ heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_label_set_text_static(heartbeatValue, "");
+ lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
@@ -380,4 +393,18 @@ void WatchFaceDigital::Refresh() {
}
}
+ heartbeat = heartRateController.HeartRate();
+ heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
+ if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
+ if (heartbeatRunning.Get()) {
+ lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
+ } else {
+ lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
+ lv_label_set_text_static(heartbeatValue, "");
+ }
+
+ lv_obj_realign(heartbeatIcon);
+ lv_obj_realign(heartbeatValue);
+ }
}
diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h
index 8382996..93719c1 100644
--- a/src/displayapp/screens/WatchFaceDigital.h
+++ b/src/displayapp/screens/WatchFaceDigital.h
@@ -15,6 +15,7 @@ namespace Pinetime {
class Battery;
class Ble;
class NotificationManager;
+ class HeartRateController;
class MotionController;
}
@@ -29,6 +30,7 @@ namespace Pinetime {
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
+ Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
System::SystemTask& systemTask);
~WatchFaceDigital() override;
@@ -54,6 +56,8 @@ namespace Pinetime {
DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
DirtyValue<bool> motionSensorOk {};
DirtyValue<uint32_t> stepCount {};
+ DirtyValue<uint8_t> heartbeat {};
+ DirtyValue<bool> heartbeatRunning {};
DirtyValue<bool> notificationState {};
lv_obj_t* label_temp;
@@ -68,6 +72,8 @@ namespace Pinetime {
lv_obj_t* batteryIcon;
lv_obj_t* bleIcon;
lv_obj_t* batteryPlug;
+ lv_obj_t* heartbeatIcon;
+ lv_obj_t* heartbeatValue;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
@@ -77,6 +83,7 @@ namespace Pinetime {
Controllers::Ble& bleController;
Controllers::NotificationManager& notificatioManager;
Controllers::Settings& settingsController;
+ Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
lv_task_t* taskRefresh;
diff --git a/src/heartratetask/HeartRateTask.cpp b/src/heartratetask/HeartRateTask.cpp
new file mode 100644
index 0000000..2f689b9
--- /dev/null
+++ b/src/heartratetask/HeartRateTask.cpp
@@ -0,0 +1,100 @@
+#include "heartratetask/HeartRateTask.h"
+#include <drivers/Hrs3300.h>
+#include <components/heartrate/HeartRateController.h>
+#include <nrf_log.h>
+
+using namespace Pinetime::Applications;
+
+HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller)
+ : heartRateSensor {heartRateSensor}, controller {controller}, ppg{} {
+}
+
+void HeartRateTask::Start() {
+ messageQueue = xQueueCreate(10, 1);
+ controller.SetHeartRateTask(this);
+
+ if (pdPASS != xTaskCreate(HeartRateTask::Process, "Heartrate", 500, this, 0, &taskHandle))
+ APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
+}
+
+void HeartRateTask::Process(void* instance) {
+ auto* app = static_cast<HeartRateTask*>(instance);
+ app->Work();
+}
+
+void HeartRateTask::Work() {
+ int lastBpm = 0;
+ while (true) {
+ Messages msg;
+ uint32_t delay;
+ if (state == States::Running) {
+ if (measurementStarted)
+ delay = 40;
+ else
+ delay = 100;
+ } else
+ delay = portMAX_DELAY;
+
+ if (xQueueReceive(messageQueue, &msg, delay)) {
+ switch (msg) {
+ case Messages::GoToSleep:
+ StopMeasurement();
+ state = States::Idle;
+ break;
+ case Messages::WakeUp:
+ state = States::Running;
+ if (measurementStarted) {
+ lastBpm = 0;
+ StartMeasurement();
+ }
+ break;
+ case Messages::StartMeasurement:
+ if (measurementStarted)
+ break;
+ lastBpm = 0;
+ StartMeasurement();
+ measurementStarted = true;
+ break;
+ case Messages::StopMeasurement:
+ if (!measurementStarted)
+ break;
+ StopMeasurement();
+ measurementStarted = false;
+ break;
+ }
+ }
+
+ if (measurementStarted) {
+ ppg.Preprocess(static_cast<float>(heartRateSensor.ReadHrs()));
+ auto bpm = ppg.HeartRate();
+
+ if (lastBpm == 0 && bpm == 0)
+ controller.Update(Controllers::HeartRateController::States::NotEnoughData, 0);
+ if (bpm != 0) {
+ lastBpm = bpm;
+ controller.Update(Controllers::HeartRateController::States::Running, lastBpm);
+ }
+ }
+ }
+}
+
+void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
+ BaseType_t xHigherPriorityTaskWoken;
+ xHigherPriorityTaskWoken = pdFALSE;
+ xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
+ if (xHigherPriorityTaskWoken) {
+ /* Actual macro used here is port specific. */
+ // TODO : should I do something here?
+ }
+}
+
+void HeartRateTask::StartMeasurement() {
+ heartRateSensor.Enable();
+ vTaskDelay(100);
+ ppg.SetOffset(static_cast<float>(heartRateSensor.ReadHrs()));
+}
+
+void HeartRateTask::StopMeasurement() {
+ heartRateSensor.Disable();
+ vTaskDelay(100);
+}
diff --git a/src/heartratetask/HeartRateTask.h b/src/heartratetask/HeartRateTask.h
new file mode 100644
index 0000000..0796dc7
--- /dev/null
+++ b/src/heartratetask/HeartRateTask.h
@@ -0,0 +1,40 @@
+#pragma once
+#include <FreeRTOS.h>
+#include <task.h>
+#include <queue.h>
+#include <components/heartrate/Ppg.h>
+
+namespace Pinetime {
+ namespace Drivers {
+ class Hrs3300;
+ }
+ namespace Controllers {
+ class HeartRateController;
+ }
+ namespace Applications {
+ class HeartRateTask {
+ public:
+ enum class Messages : uint8_t { GoToSleep, WakeUp, StartMeasurement, StopMeasurement };
+ enum class States { Idle, Running };
+
+ explicit HeartRateTask(Drivers::Hrs3300& heartRateSensor, Controllers::HeartRateController& controller);
+ void Start();
+ void Work();
+ void PushMessage(Messages msg);
+
+ private:
+ static void Process(void* instance);
+ void StartMeasurement();
+ void StopMeasurement();
+
+ TaskHandle_t taskHandle;
+ QueueHandle_t messageQueue;
+ States state = States::Running;
+ Drivers::Hrs3300& heartRateSensor;
+ Controllers::HeartRateController& controller;
+ Controllers::Ppg ppg;
+ bool measurementStarted = false;
+ };
+
+ }
+}
diff --git a/src/libs/mynewt-nimble/apps/blehr/README.md b/src/libs/mynewt-nimble/apps/blehr/README.md
new file mode 100644
index 0000000..d06d95d
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/README.md
@@ -0,0 +1,9 @@
+# BLE Heart Rate peripheral app.
+
+The source files are located in the src/ directory.
+
+pkg.yml contains the base definition of the app.
+
+syscfg.yml contains setting definitions and overrides.
+
+
diff --git a/src/libs/mynewt-nimble/apps/blehr/pkg.yml b/src/libs/mynewt-nimble/apps/blehr/pkg.yml
new file mode 100644
index 0000000..b91ba37
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/pkg.yml
@@ -0,0 +1,40 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: apps/blehr
+pkg.type: app
+pkg.description: BLE peripheral heartrate sensor.
+pkg.author: "Szymon Czapracki"
+pkg.email: "szymon.czapracki@codecoup.pl"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+
+pkg.deps:
+ - "@apache-mynewt-core/kernel/os"
+ - "@apache-mynewt-core/sys/console/full"
+ - "@apache-mynewt-core/sys/log/full"
+ - "@apache-mynewt-core/sys/log/modlog"
+ - "@apache-mynewt-core/sys/stats/full"
+ - "@apache-mynewt-core/sys/sysinit"
+ - "@apache-mynewt-core/sys/id"
+ - nimble/controller
+ - nimble/host
+ - nimble/host/services/gap
+ - nimble/host/services/gatt
+ - nimble/host/store/config
+ - nimble/transport/ram
diff --git a/src/libs/mynewt-nimble/apps/blehr/src/blehr_sens.h b/src/libs/mynewt-nimble/apps/blehr/src/blehr_sens.h
new file mode 100644
index 0000000..a3f59ca
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/src/blehr_sens.h
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_BLEHR_SENSOR_
+#define H_BLEHR_SENSOR_
+
+#include "nimble/ble.h"
+#include "modlog/modlog.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Heart-rate configuration */
+#define GATT_HRS_UUID 0x180D
+#define GATT_HRS_MEASUREMENT_UUID 0x2A37
+#define GATT_HRS_BODY_SENSOR_LOC_UUID 0x2A38
+#define GATT_DEVICE_INFO_UUID 0x180A
+#define GATT_MANUFACTURER_NAME_UUID 0x2A29
+#define GATT_MODEL_NUMBER_UUID 0x2A24
+
+extern uint16_t hrs_hrm_handle;
+
+int gatt_svr_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/libs/mynewt-nimble/apps/blehr/src/gatt_svr.c b/src/libs/mynewt-nimble/apps/blehr/src/gatt_svr.c
new file mode 100644
index 0000000..b2e9b4e
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/src/gatt_svr.c
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "blehr_sens.h"
+
+static const char *manuf_name = "Apache Mynewt";
+static const char *model_num = "Mynewt HR Sensor";
+uint16_t hrs_hrm_handle;
+
+static int
+gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt, void *arg);
+
+static int
+gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt, void *arg);
+
+static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
+ {
+ /* Service: Heart-rate */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = BLE_UUID16_DECLARE(GATT_HRS_UUID),
+ .characteristics = (struct ble_gatt_chr_def[]) { {
+ /* Characteristic: Heart-rate measurement */
+ .uuid = BLE_UUID16_DECLARE(GATT_HRS_MEASUREMENT_UUID),
+ .access_cb = gatt_svr_chr_access_heart_rate,
+ .val_handle = &hrs_hrm_handle,
+ .flags = BLE_GATT_CHR_F_NOTIFY,
+ }, {
+ /* Characteristic: Body sensor location */
+ .uuid = BLE_UUID16_DECLARE(GATT_HRS_BODY_SENSOR_LOC_UUID),
+ .access_cb = gatt_svr_chr_access_heart_rate,
+ .flags = BLE_GATT_CHR_F_READ,
+ }, {
+ 0, /* No more characteristics in this service */
+ }, }
+ },
+
+ {
+ /* Service: Device Information */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID),
+ .characteristics = (struct ble_gatt_chr_def[]) { {
+ /* Characteristic: * Manufacturer name */
+ .uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID),
+ .access_cb = gatt_svr_chr_access_device_info,
+ .flags = BLE_GATT_CHR_F_READ,
+ }, {
+ /* Characteristic: Model number string */
+ .uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID),
+ .access_cb = gatt_svr_chr_access_device_info,
+ .flags = BLE_GATT_CHR_F_READ,
+ }, {
+ 0, /* No more characteristics in this service */
+ }, }
+ },
+
+ {
+ 0, /* No more services */
+ },
+};
+
+static int
+gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt, void *arg)
+{
+ /* Sensor location, set to "Chest" */
+ static uint8_t body_sens_loc = 0x01;
+ uint16_t uuid;
+ int rc;
+
+ uuid = ble_uuid_u16(ctxt->chr->uuid);
+
+ if (uuid == GATT_HRS_BODY_SENSOR_LOC_UUID) {
+ rc = os_mbuf_append(ctxt->om, &body_sens_loc, sizeof(body_sens_loc));
+
+ return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+
+ assert(0);
+ return BLE_ATT_ERR_UNLIKELY;
+}
+
+static int
+gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
+ struct ble_gatt_access_ctxt *ctxt, void *arg)
+{
+ uint16_t uuid;
+ int rc;
+
+ uuid = ble_uuid_u16(ctxt->chr->uuid);
+
+ if (uuid == GATT_MODEL_NUMBER_UUID) {
+ rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num));
+ return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+
+ if (uuid == GATT_MANUFACTURER_NAME_UUID) {
+ rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name));
+ return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+
+ assert(0);
+ return BLE_ATT_ERR_UNLIKELY;
+}
+
+void
+gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
+{
+ char buf[BLE_UUID_STR_LEN];
+
+ switch (ctxt->op) {
+ case BLE_GATT_REGISTER_OP_SVC:
+ MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
+ ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
+ ctxt->svc.handle);
+ break;
+
+ case BLE_GATT_REGISTER_OP_CHR:
+ MODLOG_DFLT(DEBUG, "registering characteristic %s with "
+ "def_handle=%d val_handle=%d\n",
+ ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
+ ctxt->chr.def_handle,
+ ctxt->chr.val_handle);
+ break;
+
+ case BLE_GATT_REGISTER_OP_DSC:
+ MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
+ ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
+ ctxt->dsc.handle);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+int
+gatt_svr_init(void)
+{
+ int rc;
+
+ rc = ble_gatts_count_cfg(gatt_svr_svcs);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = ble_gatts_add_svcs(gatt_svr_svcs);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
diff --git a/src/libs/mynewt-nimble/apps/blehr/src/main.c b/src/libs/mynewt-nimble/apps/blehr/src/main.c
new file mode 100644
index 0000000..bf0f3f3
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/src/main.c
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "os/mynewt.h"
+#include "console/console.h"
+#include "config/config.h"
+#include "nimble/ble.h"
+#include "host/ble_hs.h"
+#include "services/gap/ble_svc_gap.h"
+#include "blehr_sens.h"
+
+static bool notify_state;
+
+/* Connection handle */
+static uint16_t conn_handle;
+
+static const char *device_name = "blehr_sensor";
+
+static int blehr_gap_event(struct ble_gap_event *event, void *arg);
+
+static uint8_t blehr_addr_type;
+
+/* Sending notify data timer */
+static struct os_callout blehr_tx_timer;
+
+/* Variable to simulate heart beats */
+static uint8_t heartrate = 90;
+
+/*
+ * Enables advertising with parameters:
+ * o General discoverable mode
+ * o Undirected connectable mode
+ */
+static void
+blehr_advertise(void)
+{
+ struct ble_gap_adv_params adv_params;
+ struct ble_hs_adv_fields fields;
+ int rc;
+
+ /*
+ * Set the advertisement data included in our advertisements:
+ * o Flags (indicates advertisement type and other general info)
+ * o Advertising tx power
+ * o Device name
+ */
+ memset(&fields, 0, sizeof(fields));
+
+ /*
+ * Advertise two flags:
+ * o Discoverability in forthcoming advertisement (general)
+ * o BLE-only (BR/EDR unsupported)
+ */
+ fields.flags = BLE_HS_ADV_F_DISC_GEN |
+ BLE_HS_ADV_F_BREDR_UNSUP;
+
+ /*
+ * Indicate that the TX power level field should be included; have the
+ * stack fill this value automatically. This is done by assigning the
+ * special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
+ */
+ fields.tx_pwr_lvl_is_present = 1;
+ fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
+
+ fields.name = (uint8_t *)device_name;
+ fields.name_len = strlen(device_name);
+ fields.name_is_complete = 1;
+
+ rc = ble_gap_adv_set_fields(&fields);
+ if (rc != 0) {
+ MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
+ return;
+ }
+
+ /* Begin advertising */
+ memset(&adv_params, 0, sizeof(adv_params));
+ adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
+ adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+ rc = ble_gap_adv_start(blehr_addr_type, NULL, BLE_HS_FOREVER,
+ &adv_params, blehr_gap_event, NULL);
+ if (rc != 0) {
+ MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
+ return;
+ }
+}
+
+static void
+blehr_tx_hrate_stop(void)
+{
+ os_callout_stop(&blehr_tx_timer);
+}
+
+/* Reset heartrate measurment */
+static void
+blehr_tx_hrate_reset(void)
+{
+ int rc;
+
+ rc = os_callout_reset(&blehr_tx_timer, OS_TICKS_PER_SEC);
+ assert(rc == 0);
+}
+
+/* This functions simulates heart beat and notifies it to the client */
+static void
+blehr_tx_hrate(struct os_event *ev)
+{
+ static uint8_t hrm[2];
+ int rc;
+ struct os_mbuf *om;
+
+ if (!notify_state) {
+ blehr_tx_hrate_stop();
+ heartrate = 90;
+ return;
+ }
+
+ hrm[0] = 0x06; /* contact of a sensor */
+ hrm[1] = heartrate; /* storing dummy data */
+
+ /* Simulation of heart beats */
+ heartrate++;
+ if (heartrate == 160) {
+ heartrate = 90;
+ }
+
+ om = ble_hs_mbuf_from_flat(hrm, sizeof(hrm));
+
+ rc = ble_gattc_notify_custom(conn_handle, hrs_hrm_handle, om);
+
+ assert(rc == 0);
+ blehr_tx_hrate_reset();
+}
+
+static int
+blehr_gap_event(struct ble_gap_event *event, void *arg)
+{
+ switch (event->type) {
+ case BLE_GAP_EVENT_CONNECT:
+ /* A new connection was established or a connection attempt failed */
+ MODLOG_DFLT(INFO, "connection %s; status=%d\n",
+ event->connect.status == 0 ? "established" : "failed",
+ event->connect.status);
+
+ if (event->connect.status != 0) {
+ /* Connection failed; resume advertising */
+ blehr_advertise();
+ conn_handle = 0;
+ }
+ else {
+ conn_handle = event->connect.conn_handle;
+ }
+
+ break;
+
+ case BLE_GAP_EVENT_DISCONNECT:
+ MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason);
+ conn_handle = BLE_HS_CONN_HANDLE_NONE; /* reset conn_handle */
+
+ /* Connection terminated; resume advertising */
+ blehr_advertise();
+ break;
+
+ case BLE_GAP_EVENT_ADV_COMPLETE:
+ MODLOG_DFLT(INFO, "adv complete\n");
+ blehr_advertise();
+ break;
+
+ case BLE_GAP_EVENT_SUBSCRIBE:
+ MODLOG_DFLT(INFO, "subscribe event; cur_notify=%d\n value handle; "
+ "val_handle=%d\n",
+ event->subscribe.cur_notify, hrs_hrm_handle);
+ if (event->subscribe.attr_handle == hrs_hrm_handle) {
+ notify_state = event->subscribe.cur_notify;
+ blehr_tx_hrate_reset();
+ } else if (event->subscribe.attr_handle != hrs_hrm_handle) {
+ notify_state = event->subscribe.cur_notify;
+ blehr_tx_hrate_stop();
+ }
+ break;
+
+ case BLE_GAP_EVENT_MTU:
+ MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n",
+ event->mtu.conn_handle,
+ event->mtu.value);
+ break;
+
+ }
+
+ return 0;
+}
+
+static void
+blehr_on_sync(void)
+{
+ int rc;
+
+ /* Use privacy */
+ rc = ble_hs_id_infer_auto(0, &blehr_addr_type);
+ assert(rc == 0);
+
+ /* Begin advertising */
+ blehr_advertise();
+}
+
+/*
+ * main
+ *
+ * The main task for the project. This function initializes the packages,
+ * then starts serving events from default event queue.
+ *
+ * @return int NOTE: this function should never return!
+ */
+int
+main(void)
+{
+ int rc;
+
+ /* Initialize OS */
+ sysinit();
+
+ /* Initialize the NimBLE host configuration */
+ ble_hs_cfg.sync_cb = blehr_on_sync;
+
+ os_callout_init(&blehr_tx_timer, os_eventq_dflt_get(),
+ blehr_tx_hrate, NULL);
+
+ rc = gatt_svr_init();
+ assert(rc == 0);
+
+ /* Set the default device name */
+ rc = ble_svc_gap_device_name_set(device_name);
+ assert(rc == 0);
+
+ /* As the last thing, process events from default event queue */
+ while (1) {
+ os_eventq_run(os_eventq_dflt_get());
+ }
+ return 0;
+}
+
diff --git a/src/libs/mynewt-nimble/apps/blehr/syscfg.yml b/src/libs/mynewt-nimble/apps/blehr/syscfg.yml
new file mode 100644
index 0000000..a6be2e2
--- /dev/null
+++ b/src/libs/mynewt-nimble/apps/blehr/syscfg.yml
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+syscfg.vals:
+ # Disable central and observer roles.
+ BLE_ROLE_BROADCASTER: 1
+ BLE_ROLE_CENTRAL: 0
+ BLE_ROLE_OBSERVER: 0
+ BLE_ROLE_PERIPHERAL: 1
+
+ # Disable unused eddystone feature.
+ BLE_EDDYSTONE: 0
+
+ # Log reboot messages to a flash circular buffer.
+ REBOOT_LOG_FCB: 1
+ LOG_FCB: 1
+ CONFIG_FCB: 1
+
+ # Set public device address.
+ BLE_PUBLIC_DEV_ADDR: ((uint8_t[6]){0xcc, 0xbb, 0xaa, 0x33, 0x22, 0x11})
diff --git a/src/main.cpp b/src/main.cpp
index 8022595..0f68ca8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,6 +36,7 @@
#include "components/brightness/BrightnessController.h"
#include "components/motor/MotorController.h"
#include "components/datetime/DateTimeController.h"
+#include "components/heartrate/HeartRateController.h"
#include "drivers/Spi.h"
#include "drivers/SpiMaster.h"
#include "drivers/SpiNorFlash.h"
@@ -58,6 +59,7 @@ Pinetime::Logging::DummyLogger logger;
static constexpr uint8_t touchPanelTwiAddress = 0x15;
static constexpr uint8_t motionSensorTwiAddress = 0x18;
+static constexpr uint8_t heartRateSensorTwiAddress = 0x44;
Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0,
{Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
@@ -89,12 +91,16 @@ Pinetime::Drivers::Cst816S touchPanel {twiMaster, touchPanelTwiAddress};
Pinetime::Components::LittleVgl lvgl {lcd, touchPanel};
Pinetime::Drivers::Bma421 motionSensor {twiMaster, motionSensorTwiAddress};
+Pinetime::Drivers::Hrs3300 heartRateSensor {twiMaster, heartRateSensorTwiAddress};
TimerHandle_t debounceTimer;
TimerHandle_t debounceChargeTimer;
Pinetime::Controllers::Battery batteryController;
Pinetime::Controllers::Ble bleController;
+Pinetime::Controllers::HeartRateController heartRateController;
+Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
+
Pinetime::Controllers::Settings settingsController {};
Pinetime::Controllers::MotorController motorController {};
@@ -115,6 +121,7 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
dateTimeController,
watchdogView,
notificationManager,
+ heartRateController,
settingsController,
motorController,
motionController,
@@ -133,10 +140,13 @@ Pinetime::System::SystemTask systemTask(spi,
watchdog,
notificationManager,
motorController,
+ heartRateSensor,
motionController,
motionSensor,
settingsController,
+ heartRateController,
displayApp,
+ heartRateApp,
touchHandler,
buttonHandler);
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 7a0d151..4e7f3c0 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -59,10 +59,13 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
Drivers::Watchdog& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Controllers::MotorController& motorController,
+ Pinetime::Drivers::Hrs3300& heartRateSensor,
Pinetime::Controllers::MotionController& motionController,
Pinetime::Drivers::Bma421& motionSensor,
Controllers::Settings& settingsController,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp,
+ Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::ButtonHandler& buttonHandler)
: spi {spi},
@@ -77,10 +80,13 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
watchdog {watchdog},
notificationManager {notificationManager},
motorController {motorController},
+ heartRateSensor {heartRateSensor},
motionSensor {motionSensor},
settingsController {settingsController},
+ heartRateController {heartRateController},
motionController {motionController},
displayApp {displayApp},
+ heartRateApp(heartRateApp),
touchHandler {touchHandler},
buttonHandler {buttonHandler},
nimbleController(*this,
@@ -88,7 +94,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi,
dateTimeController,
notificationManager,
batteryController,
- spiNorFlash) {
+ spiNorFlash,
+ heartRateController) {
}
void SystemTask::Start() {
@@ -145,6 +152,10 @@ void SystemTask::Work() {
displayApp.Register(this);
displayApp.Start(bootError);
+ heartRateSensor.Init();
+ heartRateSensor.Disable();
+ heartRateApp.Start();
+
buttonHandler.Init(this);
// Button
@@ -227,6 +238,7 @@ void SystemTask::Work() {
lcd.Wakeup();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
+ heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
if (!bleController.IsConnected()) {
nimbleController.RestartFastAdv();
@@ -257,6 +269,7 @@ void SystemTask::Work() {
xTimerStop(idleTimer, 0);
xTimerStop(dimTimer, 0);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
+ heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break;
case Messages::OnNewTime:
ReloadIdleTimer();
diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h
index 7104115..b36c36d 100644
--- a/src/systemtask/SystemTask.h
+++ b/src/systemtask/SystemTask.h
@@ -6,6 +6,7 @@
#include <queue.h>
#include <task.h>
#include <timers.h>
+#include <heartratetask/HeartRateTask.h>
#include <components/settings/Settings.h>
#include <drivers/Bma421.h>
#include <drivers/PinMap.h>
@@ -60,10 +61,13 @@ namespace Pinetime {
Drivers::Watchdog& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Controllers::MotorController& motorController,
+ Pinetime::Drivers::Hrs3300& heartRateSensor,
Pinetime::Controllers::MotionController& motionController,
Pinetime::Drivers::Bma421& motionSensor,
Controllers::Settings& settingsController,
+ Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp,
+ Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::ButtonHandler& buttonHandler);
@@ -105,12 +109,15 @@ namespace Pinetime {
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::MotorController& motorController;
public:
+ Pinetime::Drivers::Hrs3300& heartRateSensor;
Pinetime::Drivers::Bma421& motionSensor;
private:
Pinetime::Controllers::Settings& settingsController;
+ Pinetime::Controllers::HeartRateController& heartRateController;
Pinetime::Controllers::MotionController& motionController;
Pinetime::Applications::DisplayApp& displayApp;
+ Pinetime::Applications::HeartRateTask& heartRateApp;
Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::ButtonHandler& buttonHandler;
Pinetime::Controllers::NimbleController nimbleController;