2020-06-22 23:51:06 +02:00
|
|
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
# Copyright (C) 2020 Daniel Thompson
|
|
|
|
|
2021-01-13 22:51:17 +01:00
|
|
|
"""Heart rate monitor
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
A graphing heart rate monitor using a PPG sensor.
|
|
|
|
|
2023-03-07 02:02:07 +01:00
|
|
|
.. figure:: res/screenshots/HeartApp.png
|
2021-01-13 22:51:17 +01:00
|
|
|
:width: 179
|
2021-05-05 22:06:39 +02:00
|
|
|
|
|
|
|
This program also implements some (entirely optional) debug features to
|
|
|
|
store the raw heart data to the filesystem so that the samples can be used
|
|
|
|
to further refine the heart rate detection algorithm.
|
|
|
|
|
|
|
|
To enable the logging feature select the heart rate application using the
|
|
|
|
watch UI and then run the following command via wasptool:
|
|
|
|
|
|
|
|
.. code-block:: sh
|
|
|
|
|
|
|
|
./tools/wasptool --eval 'wasp.system.app.debug = True'
|
|
|
|
|
|
|
|
Once debug has been enabled then the watch will automatically log heart
|
|
|
|
rate data whenever the heart rate application is running (and only
|
|
|
|
when it is running). Setting the debug flag to False will disable the
|
|
|
|
logging when the heart rate monitor next exits.
|
|
|
|
|
|
|
|
Finally to download the logs for analysis try:
|
|
|
|
|
|
|
|
.. code-block:: sh
|
|
|
|
|
|
|
|
./tools/wasptool --pull hrs.data
|
2021-01-13 22:51:17 +01:00
|
|
|
"""
|
|
|
|
|
2020-06-22 23:51:06 +02:00
|
|
|
import wasp
|
|
|
|
import machine
|
2020-06-25 22:59:32 +02:00
|
|
|
import ppg
|
2020-06-24 22:20:18 +02:00
|
|
|
|
2020-06-22 23:51:06 +02:00
|
|
|
class HeartApp():
|
2021-01-13 22:51:17 +01:00
|
|
|
"""Heart rate monitor application."""
|
2020-06-22 23:51:06 +02:00
|
|
|
NAME = 'Heart'
|
|
|
|
|
2021-05-05 22:06:39 +02:00
|
|
|
def __init__(self):
|
|
|
|
self._debug = False
|
|
|
|
self._hrdata = None
|
|
|
|
|
2020-06-22 23:51:06 +02:00
|
|
|
def foreground(self):
|
|
|
|
"""Activate the application."""
|
|
|
|
wasp.watch.hrs.enable()
|
|
|
|
|
|
|
|
# There is no delay after the enable because the redraw should
|
|
|
|
# take long enough it is not needed
|
|
|
|
draw = wasp.watch.drawable
|
|
|
|
draw.fill()
|
2021-01-03 15:46:47 +01:00
|
|
|
draw.set_color(wasp.system.theme('bright'))
|
2020-06-22 23:51:06 +02:00
|
|
|
draw.string('PPG graph', 0, 6, width=240)
|
|
|
|
|
2020-06-25 22:59:32 +02:00
|
|
|
wasp.system.request_tick(1000 // 8)
|
2020-06-22 23:51:06 +02:00
|
|
|
|
2020-06-25 22:59:32 +02:00
|
|
|
self._hrdata = ppg.PPG(wasp.watch.hrs.read_hrs())
|
2021-05-05 22:06:39 +02:00
|
|
|
if self._debug:
|
|
|
|
self._hrdata.enable_debug()
|
2020-06-22 23:51:06 +02:00
|
|
|
self._x = 0
|
|
|
|
|
|
|
|
def background(self):
|
|
|
|
wasp.watch.hrs.disable()
|
2021-05-05 22:06:39 +02:00
|
|
|
self._hrdata = None
|
2020-06-22 23:51:06 +02:00
|
|
|
|
2020-06-24 22:20:18 +02:00
|
|
|
def _subtick(self, ticks):
|
2020-06-22 23:51:06 +02:00
|
|
|
"""Notify the application that its periodic tick is due."""
|
2020-06-24 22:20:18 +02:00
|
|
|
draw = wasp.watch.drawable
|
|
|
|
|
2020-06-25 22:59:32 +02:00
|
|
|
spl = self._hrdata.preprocess(wasp.watch.hrs.read_hrs())
|
2020-06-24 22:20:18 +02:00
|
|
|
|
2020-06-25 22:59:32 +02:00
|
|
|
if len(self._hrdata.data) >= 240:
|
2021-01-03 15:46:47 +01:00
|
|
|
draw.set_color(wasp.system.theme('bright'))
|
2020-06-25 22:59:32 +02:00
|
|
|
draw.string('{} bpm'.format(self._hrdata.get_heart_rate()),
|
|
|
|
0, 6, width=240)
|
2020-06-22 23:51:06 +02:00
|
|
|
|
2020-06-25 22:59:32 +02:00
|
|
|
# Graph is orange by default...
|
2020-12-31 20:12:38 +01:00
|
|
|
color = wasp.system.theme('spot1')
|
2020-06-22 23:51:06 +02:00
|
|
|
|
|
|
|
# If the maths goes wrong lets show it in the chart!
|
|
|
|
if spl > 100 or spl < -100:
|
|
|
|
color = 0xffff
|
|
|
|
if spl > 104 or spl < -104:
|
|
|
|
spl = 0
|
2020-06-24 22:20:18 +02:00
|
|
|
spl += 104
|
2020-06-22 23:51:06 +02:00
|
|
|
|
|
|
|
x = self._x
|
|
|
|
draw.fill(0, x, 32, 1, 208-spl)
|
|
|
|
draw.fill(color, x, 239-spl, 1, spl)
|
2021-09-20 17:23:26 +02:00
|
|
|
if x < 238:
|
|
|
|
draw.fill(0, x+1, 32, 2, 208)
|
2020-06-22 23:51:06 +02:00
|
|
|
x += 2
|
|
|
|
if x >= 240:
|
|
|
|
x = 0
|
|
|
|
self._x = x
|
|
|
|
|
|
|
|
def tick(self, ticks):
|
|
|
|
"""This is an outrageous hack but, at present, the RTC can only
|
|
|
|
wake us up every 125ms so we implement sub-ticks using a regular
|
|
|
|
timer to ensure we can read the sensor at 24Hz.
|
|
|
|
"""
|
|
|
|
t = machine.Timer(id=1, period=8000000)
|
|
|
|
t.start()
|
2020-06-24 22:20:18 +02:00
|
|
|
self._subtick(1)
|
2020-06-22 23:51:06 +02:00
|
|
|
wasp.system.keep_awake()
|
|
|
|
|
|
|
|
while t.time() < 41666:
|
|
|
|
pass
|
2020-06-24 22:20:18 +02:00
|
|
|
self._subtick(1)
|
2020-06-22 23:51:06 +02:00
|
|
|
|
|
|
|
while t.time() < 83332:
|
|
|
|
pass
|
2020-06-24 22:20:18 +02:00
|
|
|
self._subtick(1)
|
2020-06-22 23:51:06 +02:00
|
|
|
|
|
|
|
t.stop()
|
|
|
|
del t
|
2021-05-05 22:06:39 +02:00
|
|
|
|
|
|
|
@property
|
|
|
|
def debug(self):
|
|
|
|
return self._debug
|
|
|
|
|
|
|
|
@debug.setter
|
|
|
|
def debug(self, value):
|
|
|
|
self._debug = value
|
|
|
|
if value and self._hrdata:
|
|
|
|
self._hrdata.enable_debug()
|