Espruino based HRV monitor using the Bangle.JS. This should be quite easy to transfer for use on other HR trackers that can run espruino. I've been doing some initial checking against a Polar H10 chest strap and it's generally giving readings in the same ballpark but most of the time, for me, it does give slightly lower than what the Polar says so will look to see if anything needs tweaking etc. I've also tested it with a couple other people who are much fitter and the app correctly reports higher HRV readings.
File contents:
- HRV.js: script to be uploaded to the Bangle.js
- HRV_node_version.js: This is a JS script that can be used with node for general use - you can process larger ammounts of data on a PC this way rather than trying to do it all on the Bangle. You just need to keep a HR_log.csv file in the same folder - this log files is generated by the HRV.js script when run and that script can be modified to take readings over a longer period.
- HR_logs: These are sample logs, along with the associated raw HR readings that show how the signal looks after each stage of processing, the images below are from the same set. These logs can be generated by running the node version of the script outlined above.
Essentially the HRV.js works this way:
- Take raw hrm readings for ~30 seconds
- Rolling average filter to ommit noise
- Upscale the signal – I’ve used interpolation of bezier curves to double the sample count
- Another slight rolling average
- The next stage is to find peaks in the data and so before doing that the script applies clipping so it doesn’t search for lower level peaks that don’t matter
- Find all peaks – I’ve used a slope inversion algorythm for this
- Standard deviation of the gaps between peaks and then mutiply by the frequency to get HRV. The script also calculates HR based on gap sizes and then displays these details
I'd like to have taken more readings than just 30 seconds but there are memory limitations to contend with, to get around this it's quite easy to just take several readings and do an average for yourself; taking several HRV readings over 5mins or so when resting is generally recommended anyway to get a meaningful metric. Also it might be possible to use 8bit Int arrays rather than floats and do some conversions to save on memory if you wanted to process more readings.
a few other notes:
- it might be possible to get away with just linear interpolation or something faster but similar than what i've done for steps 3 & 4
- sometimes the processing loop gets executed twice and that's probably because of my ignorance of asynchronous programming in Javascript rather than an actual bug. May need to revise the event handling to fix this - it doesn't actually affect the end result and the final readings though.
Random images of the signal at different stages of processing - these aren't all the same part of the signal I've just quickly taken screenshots of line graphs.
Raw Reading usually it isn't as ragged as this but it can be
Rolling Average
Upscale
Clipping
Peaks found You can see here the algorythm appears to correctly identify pretty much every relevant peak from the PPG data. It's also sadly clear that I have a crap HRV right now.