Skip to content

Commit a0f2fa8

Browse files
authored
Merge pull request #169 from JF002/heartRateSensor
Heart rate sensor
2 parents 35d4f6d + 68674ce commit a0f2fa8

29 files changed

Lines changed: 986 additions & 23 deletions

src/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ list(APPEND SOURCE_FILES
349349
displayapp/screens/ApplicationList.cpp
350350
displayapp/screens/Notifications.cpp
351351
displayapp/screens/Twos.cpp
352+
displayapp/screens/HeartRate.cpp
352353
main.cpp
353354
drivers/St7789.cpp
354355
drivers/SpiNorFlash.cpp
@@ -357,6 +358,7 @@ list(APPEND SOURCE_FILES
357358
drivers/Watchdog.cpp
358359
drivers/DebugPins.cpp
359360
drivers/InternalFlash.cpp
361+
drivers/Hrs3300.cpp
360362
components/battery/BatteryController.cpp
361363
components/ble/BleController.cpp
362364
components/ble/NotificationManager.cpp
@@ -373,6 +375,7 @@ list(APPEND SOURCE_FILES
373375
components/ble/BatteryInformationService.cpp
374376
components/ble/ImmediateAlertService.cpp
375377
components/ble/ServiceDiscovery.cpp
378+
components/ble/HeartRateService.cpp
376379
components/firmwarevalidator/FirmwareValidator.cpp
377380
drivers/Cst816s.cpp
378381
FreeRTOS/port.c
@@ -385,6 +388,12 @@ list(APPEND SOURCE_FILES
385388

386389
systemtask/SystemTask.cpp
387390
drivers/TwiMaster.cpp
391+
392+
heartratetask/HeartRateTask.cpp
393+
components/heartrate/Ppg.cpp
394+
components/heartrate/Biquad.cpp
395+
components/heartrate/Ptagc.cpp
396+
components/heartrate/HeartRateController.cpp
388397
)
389398

390399
list(APPEND GRAPHICS_SOURCE_FILES
@@ -432,13 +441,15 @@ set(INCLUDE_FILES
432441
displayapp/screens/ApplicationList.h
433442
displayapp/Apps.h
434443
displayapp/screens/Notifications.h
444+
displayapp/screens/HeartRate.h
435445
drivers/St7789.h
436446
drivers/SpiNorFlash.h
437447
drivers/SpiMaster.h
438448
drivers/Spi.h
439449
drivers/Watchdog.h
440450
drivers/DebugPins.h
441451
drivers/InternalFlash.h
452+
drivers/Hrs3300.h
442453
components/battery/BatteryController.h
443454
components/ble/BleController.h
444455
components/ble/NotificationManager.h
@@ -454,6 +465,7 @@ set(INCLUDE_FILES
454465
components/ble/ImmediateAlertService.h
455466
components/ble/ServiceDiscovery.h
456467
components/ble/BleClient.h
468+
components/ble/HeartRateService.h.h
457469
drivers/Cst816s.h
458470
FreeRTOS/portmacro.h
459471
FreeRTOS/portmacro_cmsis.h
@@ -470,6 +482,11 @@ set(INCLUDE_FILES
470482
systemtask/SystemMonitor.h
471483
displayapp/screens/Symbols.h
472484
drivers/TwiMaster.h
485+
heartratetask/HeartRateTask.h
486+
components/heartrate/Ppg.h
487+
components/heartrate/Biquad.h
488+
components/heartrate/Ptagc.h
489+
components/heartrate/HeartRateController.h
473490
)
474491

475492
include_directories(

src/FreeRTOSConfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
#define configTICK_RATE_HZ 1024
6464
#define configMAX_PRIORITIES ( 3 )
6565
#define configMINIMAL_STACK_SIZE ( 120 )
66-
#define configTOTAL_HEAP_SIZE ( 1024*11 )
66+
#define configTOTAL_HEAP_SIZE ( 1024*14 )
6767
#define configMAX_TASK_NAME_LEN ( 4 )
6868
#define configUSE_16_BIT_TICKS 0
6969
#define configIDLE_SHOULD_YIELD 1
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include "HeartRateService.h"
2+
#include "components/heartrate/HeartRateController.h"
3+
#include "systemtask/SystemTask.h"
4+
5+
using namespace Pinetime::Controllers;
6+
7+
constexpr ble_uuid16_t HeartRateService::heartRateServiceUuid;
8+
constexpr ble_uuid16_t HeartRateService::heartRateMeasurementUuid;
9+
10+
namespace {
11+
int HeartRateServiceServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
12+
auto* heartRateService = static_cast<HeartRateService*>(arg);
13+
return heartRateService->OnHeartRateRequested(conn_handle, attr_handle, ctxt);
14+
}
15+
}
16+
17+
// TODO Refactoring - remove dependency to SystemTask
18+
HeartRateService::HeartRateService(Pinetime::System::SystemTask &system, Controllers::HeartRateController& heartRateController) :
19+
system{system},
20+
heartRateController{heartRateController},
21+
characteristicDefinition{
22+
{
23+
.uuid = (ble_uuid_t *) &heartRateMeasurementUuid,
24+
.access_cb = HeartRateServiceServiceCallback,
25+
.arg = this,
26+
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
27+
.val_handle = &heartRateMeasurementHandle
28+
},
29+
{
30+
0
31+
}
32+
},
33+
serviceDefinition{
34+
{
35+
/* Device Information Service */
36+
.type = BLE_GATT_SVC_TYPE_PRIMARY,
37+
.uuid = (ble_uuid_t *) &heartRateServiceUuid,
38+
.characteristics = characteristicDefinition
39+
},
40+
{
41+
0
42+
},
43+
}{
44+
// TODO refactor to prevent this loop dependency (service depends on controller and controller depends on service)
45+
heartRateController.SetService(this);
46+
}
47+
48+
void HeartRateService::Init() {
49+
int res = 0;
50+
res = ble_gatts_count_cfg(serviceDefinition);
51+
ASSERT(res == 0);
52+
53+
res = ble_gatts_add_svcs(serviceDefinition);
54+
ASSERT(res == 0);
55+
}
56+
57+
int HeartRateService::OnHeartRateRequested(uint16_t connectionHandle, uint16_t attributeHandle,
58+
ble_gatt_access_ctxt *context) {
59+
if(attributeHandle == heartRateMeasurementHandle) {
60+
NRF_LOG_INFO("BATTERY : handle = %d", heartRateMeasurementHandle);
61+
static uint8_t batteryValue = heartRateController.HeartRate();
62+
63+
uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value
64+
65+
int res = os_mbuf_append(context->om, buffer, 2);
66+
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
67+
}
68+
return 0;
69+
}
70+
71+
void HeartRateService::OnNewHeartRateValue(uint8_t heartRateValue) {
72+
uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value
73+
auto *om = ble_hs_mbuf_from_flat(buffer, 2);
74+
75+
uint16_t connectionHandle = system.nimble().connHandle();
76+
77+
if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
78+
return;
79+
}
80+
81+
ble_gattc_notify_custom(connectionHandle, heartRateMeasurementHandle, om);
82+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
#define min // workaround: nimble's min/max macros conflict with libstdc++
3+
#define max
4+
#include <host/ble_gap.h>
5+
#undef max
6+
#undef min
7+
8+
namespace Pinetime {
9+
namespace System {
10+
class SystemTask;
11+
}
12+
namespace Controllers {
13+
class HeartRateController;
14+
class HeartRateService {
15+
public:
16+
HeartRateService(Pinetime::System::SystemTask &system, Controllers::HeartRateController& heartRateController);
17+
void Init();
18+
int OnHeartRateRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
19+
void OnNewHeartRateValue(uint8_t hearRateValue);
20+
21+
private:
22+
Pinetime::System::SystemTask &system;
23+
Controllers::HeartRateController& heartRateController;
24+
static constexpr uint16_t heartRateServiceId {0x180D};
25+
static constexpr uint16_t heartRateMeasurementId {0x2A37};
26+
27+
static constexpr ble_uuid16_t heartRateServiceUuid {
28+
.u {.type = BLE_UUID_TYPE_16},
29+
.value = heartRateServiceId
30+
};
31+
32+
static constexpr ble_uuid16_t heartRateMeasurementUuid {
33+
.u {.type = BLE_UUID_TYPE_16},
34+
.value = heartRateMeasurementId
35+
};
36+
37+
struct ble_gatt_chr_def characteristicDefinition[3];
38+
struct ble_gatt_svc_def serviceDefinition[2];
39+
40+
uint16_t heartRateMeasurementHandle;
41+
42+
};
43+
}
44+
}

src/components/ble/NimbleController.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
2222
DateTime& dateTimeController,
2323
Pinetime::Controllers::NotificationManager& notificationManager,
2424
Controllers::Battery& batteryController,
25-
Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
25+
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
26+
Controllers::HeartRateController& heartRateController) :
2627
systemTask{systemTask},
2728
bleController{bleController},
2829
dateTimeController{dateTimeController},
@@ -36,7 +37,8 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
3637
musicService{systemTask},
3738
batteryInformationService{batteryController},
3839
immediateAlertService{systemTask, notificationManager},
39-
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
40+
serviceDiscovery({&currentTimeClient, &alertNotificationClient}),
41+
heartRateService{systemTask, heartRateController} {
4042
}
4143

4244
int GAPEventCallback(struct ble_gap_event *event, void *arg) {
@@ -58,6 +60,7 @@ void NimbleController::Init() {
5860
dfuService.Init();
5961
batteryInformationService.Init();
6062
immediateAlertService.Init();
63+
heartRateService.Init();
6164
int res;
6265
res = ble_hs_util_ensure_addr(0);
6366
ASSERT(res == 0);

src/components/ble/NimbleController.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "ImmediateAlertService.h"
1818
#include "MusicService.h"
1919
#include "ServiceDiscovery.h"
20+
#include "HeartRateService.h"
2021

2122
namespace Pinetime {
2223
namespace Drivers {
@@ -37,7 +38,8 @@ namespace Pinetime {
3738
public:
3839
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
3940
DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
40-
Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash);
41+
Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash,
42+
Controllers::HeartRateController& heartRateController);
4143
void Init();
4244
void StartAdvertising();
4345
int OnGAPEvent(ble_gap_event *event);
@@ -74,6 +76,7 @@ namespace Pinetime {
7476
MusicService musicService;
7577
BatteryInformationService batteryInformationService;
7678
ImmediateAlertService immediateAlertService;
79+
HeartRateService heartRateService;
7780

7881
uint8_t addrType; // 1 = Random, 0 = PUBLIC
7982
uint16_t connectionHandle = 0;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
SPDX-License-Identifier: LGPL-3.0-or-later
3+
Original work Copyright (C) 2020 Daniel Thompson
4+
C++ port Copyright (C) 2021 Jean-François Milants
5+
*/
6+
7+
#include "Biquad.h"
8+
9+
using namespace Pinetime::Controllers;
10+
11+
/** Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/ppg.py */
12+
Biquad::Biquad(float b0, float b1, float b2, float a1, float a2) : b0{b0}, b1{b1}, b2{b2}, a1{a1}, a2{a2} {
13+
14+
}
15+
16+
float Biquad::Step(float x) {
17+
auto v1 = this->v1;
18+
auto v2 = this->v2;
19+
20+
auto v = x - (a1 * v1) - (a2 * v2);
21+
auto y = (b0 * v) + (b1 * v1) + (b2 * v2);
22+
23+
this->v2 = v1;
24+
this->v1 = v;
25+
26+
return y;
27+
}

src/components/heartrate/Biquad.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
namespace Pinetime {
4+
namespace Controllers {
5+
/// Direct Form II Biquad Filter
6+
class Biquad {
7+
public:
8+
Biquad(float b0, float b1, float b2, float a1, float a2);
9+
float Step(float x);
10+
11+
private:
12+
float b0;
13+
float b1;
14+
float b2;
15+
float a1;
16+
float a2;
17+
18+
float v1 = 0.0f;
19+
float v2 = 0.0f;
20+
};
21+
}
22+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "HeartRateController.h"
2+
#include <heartratetask/HeartRateTask.h>
3+
#include <systemtask/SystemTask.h>
4+
5+
using namespace Pinetime::Controllers;
6+
7+
HeartRateController::HeartRateController(Pinetime::System::SystemTask &systemTask) : systemTask{systemTask} {
8+
9+
}
10+
11+
12+
void HeartRateController::Update(HeartRateController::States newState, uint8_t heartRate) {
13+
this->state = newState;
14+
if(this->heartRate != heartRate) {
15+
this->heartRate = heartRate;
16+
service->OnNewHeartRateValue(heartRate);
17+
}
18+
}
19+
20+
void HeartRateController::Start() {
21+
if(task != nullptr) {
22+
state = States::NotEnoughData;
23+
task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StartMeasurement);
24+
}
25+
}
26+
27+
void HeartRateController::Stop() {
28+
if(task != nullptr) {
29+
state = States::Stopped;
30+
task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StopMeasurement);
31+
}
32+
}
33+
34+
void HeartRateController::SetHeartRateTask(Pinetime::Applications::HeartRateTask *task) {
35+
this->task = task;
36+
}
37+
38+
void HeartRateController::SetService(Pinetime::Controllers::HeartRateService *service) {
39+
this->service = service;
40+
}
41+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <components/ble/HeartRateService.h>
5+
6+
namespace Pinetime {
7+
namespace Applications {
8+
class HeartRateTask;
9+
}
10+
namespace System {
11+
class SystemTask;
12+
}
13+
namespace Controllers {
14+
class HeartRateController {
15+
public:
16+
enum class States { Stopped, NotEnoughData, NoTouch, Running};
17+
18+
explicit HeartRateController(System::SystemTask& systemTask);
19+
20+
void Start();
21+
void Stop();
22+
void Update(States newState, uint8_t heartRate);
23+
24+
void SetHeartRateTask(Applications::HeartRateTask* task);
25+
States State() const { return state; }
26+
uint8_t HeartRate() const { return heartRate; }
27+
28+
void SetService(Pinetime::Controllers::HeartRateService *service);
29+
30+
private:
31+
System::SystemTask& systemTask;
32+
Applications::HeartRateTask* task = nullptr;
33+
States state = States::Stopped;
34+
uint8_t heartRate = 0;
35+
Pinetime::Controllers::HeartRateService* service = nullptr;
36+
};
37+
}
38+
}

0 commit comments

Comments
 (0)