HRV 0.04: Modify to work with new heart rate API, but still not sure it's working correctly

pull/797/head
Gordon Williams 2021-09-03 10:14:58 +01:00
parent 95e037a09f
commit e0b6887bd8
3 changed files with 89 additions and 88 deletions

View File

@ -2735,7 +2735,7 @@
"name": "Heart Rate Variability monitor", "name": "Heart Rate Variability monitor",
"shortName":"HRV monitor", "shortName":"HRV monitor",
"icon": "hrv.png", "icon": "hrv.png",
"version":"0.03", "version":"0.04",
"description": "Heart Rate Variability monitor, see Readme for more info", "description": "Heart Rate Variability monitor, see Readme for more info",
"tags": "", "tags": "",
"readme": "README.md", "readme": "README.md",

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset 0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset
0.03: Add RMSSD recording 0.03: Add RMSSD recording
0.04: Modify to work with new heart rate API, but still not sure it's working correctly

View File

@ -17,22 +17,23 @@ var csv = [
logfile.write(csv.join(",")+"\n"); logfile.write(csv.join(",")+"\n");
var debugging = true; var debugging = true;
var samples = 0; // how many samples have we connected?
var collectData = false; // are we currently collecting data?
var first_signals = 0; // ignore the first several signals
var heartrate = [];
var BPM_array = []; var BPM_array = [];
var raw_HR_array = new Float32Array(1536); var raw_HR_array = new Float32Array(1536);
var alternate_array = new Float32Array(3072); var alternate_array = new Float32Array(3072);
var pulse_array = []; var pulse_array = [];
var pulsecount = 0;
var cutoff_threshold = 0.5; var cutoff_threshold = 0.5;
var sample_frequency = 51.6; var sample_frequency = 51.6;
var gap_threshold = 0.15; var gap_threshold = 0.15;
var hr_min = 40;
var hr_max = 160;
var movement = 0; var movement = 0;
function storeMyData(data, file_type) { var px = g.getWidth()/2;
var py = g.getHeight()/2;
var accel; // interval for acceleration logging
function storeMyData(data, file_type) { "ram"
log = raw_HR_array; log = raw_HR_array;
// shift elements backwards - note the 4, because a Float32 is 4 bytes // shift elements backwards - note the 4, because a Float32 is 4 bytes
log.set(new Float32Array(log.buffer, 4 /*bytes*/)); log.set(new Float32Array(log.buffer, 4 /*bytes*/));
@ -41,59 +42,58 @@ function storeMyData(data, file_type) {
} }
function average(samples) { function average(samples) {
var sum = 0; return E.sum(samples) / samples.length; // faster builtin
/* var sum = 0;
for (var i = 0; i < samples.length; i++) { for (var i = 0; i < samples.length; i++) {
sum += parseFloat(samples[i]); sum += parseFloat(samples[i]);
} }
var avg = sum / samples.length; var avg = sum / samples.length;
return avg; return avg;*/
} }
function StandardDeviation (array) { function StandardDeviation (array) {
const n = array.length; const n = array.length;
const mean = array.reduce((a, b) => a + b) / n; const mean = E.sum(array) / n; //array.reduce((a, b) => a + b) / n;
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n); //return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
return Math.sqrt(E.variance(array, mean));
} }
function turn_off() { function turn_off() {
Bangle.setHRMPower(0); Bangle.setHRMPower(0);
var accel = setInterval(function () {
movement = movement + Bangle.getAccel().diff;
}, 1000);
g.clear(); g.clear();
g.drawString("processing 1/5", 120, 120); g.drawString("processing 1/5", px, py);
rolling_average(raw_HR_array,5); rolling_average(raw_HR_array,5);
g.clear(); g.clear();
g.drawString("processing 2/5", 120, 120); g.drawString("processing 2/5", px, py);
upscale(); upscale();
g.clear(); g.clear();
g.drawString("processing 3/5", 120, 120); g.drawString("processing 3/5", px, py);
rolling_average(alternate_array,5); rolling_average(alternate_array,5);
g.clear(); g.clear();
g.drawString("processing 4/5", 120, 120); g.drawString("processing 4/5", px, py);
apply_cutoff(); apply_cutoff();
find_peaks(); find_peaks();
g.clear(); g.clear();
g.drawString("processing 5/5", 120, 120); g.drawString("processing 5/5", px, py);
calculate_HRV(); calculate_HRV();
} }
function bernstein(A, B, C, D, E, t) { function bernstein(A, B, C, D, E, t) { "ram"
s = 1 - t; s = 1 - t;
x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t) x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t)
+ (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4)); + (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4));
return x; return x;
} }
function upscale() { function upscale() { "ram"
var index = 0; var index = 0;
for (let i = raw_HR_array.length - 1; i > 5; i -= 5) { for (let i = raw_HR_array.length - 1; i > 5; i -= 5) {
p0 = raw_HR_array[i]; p0 = raw_HR_array[i];
@ -110,19 +110,18 @@ function upscale() {
} }
} }
function rolling_average(values, count) { function rolling_average(values, count) { "ram"
var temp_array = []; var temp_array = [];
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
temp_array = []; temp_array = [];
for (let x = 0; x < count; x++) for (let x = 0; x < count; x++)
temp_array.push(values[i + x]); temp_array.push(values[i + x]);
values[i] = average(temp_array); values[i] = average(temp_array);
} }
} }
function apply_cutoff() { function apply_cutoff() { "ram"
var x; var x;
for (let i = 0; i < alternate_array.length; i++) { for (let i = 0; i < alternate_array.length; i++) {
x = alternate_array[i]; x = alternate_array[i];
@ -132,7 +131,7 @@ function apply_cutoff() {
} }
} }
function find_peaks() { function find_peaks() { "ram"
var previous; var previous;
var previous_slope = 0; var previous_slope = 0;
var slope; var slope;
@ -157,7 +156,7 @@ function find_peaks() {
} }
} }
function RMSSD(samples){ function RMSSD(samples){ "ram"
var sum = 0; var sum = 0;
var square = 0; var square = 0;
var data = []; var data = [];
@ -192,7 +191,7 @@ function calculate_HRV() {
gap_average = average(temp_array); gap_average = average(temp_array);
var calculatedHR = (sample_frequency*60)/(gap_average/2); var calculatedHR = (sample_frequency*60)/(gap_average/2);
if(option == 0) if(option == 0)
g.flip(); Bangle.setLCDPower(1);
g.clear(); g.clear();
//var display_stdv = StandardDeviation(pulse_array).toFixed(1); //var display_stdv = StandardDeviation(pulse_array).toFixed(1);
var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0); var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0);
@ -200,14 +199,13 @@ function calculate_HRV() {
g.drawString("SDNN:" + SDNN g.drawString("SDNN:" + SDNN
+"\nRMSSD:" + RMS_SD +"\nRMSSD:" + RMS_SD
+ "\nHR:" + calculatedHR.toFixed(0) + "\nHR:" + calculatedHR.toFixed(0)
+"\nSample Count:" + temp_array.length, 120, 120); +"\nSample Count:" + temp_array.length, px, py);
Bangle.setLCDPower(1);
if(option == 0){ if(option == 0) { // single run
Bangle.buzz(500,1); Bangle.buzz(500,1);
clearInterval(routine); option = null;
} drawButtons();
} else {
else{
var csv = [ var csv = [
0|getTime(), 0|getTime(),
temp_array.length, temp_array.length,
@ -219,85 +217,87 @@ function calculate_HRV() {
]; ];
logfile.write(csv.join(",")+"\n"); logfile.write(csv.join(",")+"\n");
movement = 0;
// for (let i = 0; i < raw_HR_array.length; i++) { // for (let i = 0; i < raw_HR_array.length; i++) {
// raw_HR_array[i] = null; // raw_HR_array[i] = null;
//} //}
turn_on();
} }
} }
function btn1Pressed() { function btn1Pressed() {
if(option === null){ if(option === null){
clearInterval(accel);
g.clear(); g.clear();
g.drawString("one-off assessment", 120, 120); g.drawString("one-off assessment", px, py);
option = 0; option = 0;
Bangle.setHRMPower(1);
turn_on();
} }
} }
function btn3Pressed() { function btn3Pressed() {
if(option === null){ if(option === null){
logfile.write(""); //reset HRV log logfile.write(""); //reset HRV log
clearInterval(accel);
g.clear(); g.clear();
g.drawString("continuous mode", 120, 120); g.drawString("continuous mode", px, py);
option = 1; option = 1;
Bangle.setHRMPower(1);
turn_on();
} }
} }
var routine = setInterval(function () { function turn_on() {
clearInterval(accel);
first_signals = 0; // ignore the first several signals
pulsecount = 0;
BPM_array = []; BPM_array = [];
heartrate = [];
pulse_array = []; pulse_array = [];
Bangle.setHRMPower(1); samples = 0;
}, 180000); if (accel) clearInterval(accel);
movement = 0;
var accel = setInterval(function () { accel = setInterval(function () {
movement = movement + Bangle.getAccel().diff; movement = movement + Bangle.getAccel().diff;
}, 1000); }, 1000);
Bangle.setHRMPower(1);
collectData = true;
}
g.clear(); function drawButtons() {
g.setColor("#00ff7f"); g.setColor("#00ff7f");
g.setFont("6x8", 2); g.setFont("6x8", 2);
g.setFontAlign(-1,1); g.setFontAlign(-1,1);
g.drawString("continuous", 120, 210); g.drawString("continuous", 120, 210);
g.setFontAlign(-1,1);
g.drawString("one-time", 140, 50); g.drawString("one-time", 140, 50);
g.setColor("#ffffff");
g.setFontAlign(0, 0);
}
g.clear();
drawButtons();
g.setFont("6x8", 2);
g.setColor("#ffffff"); g.setColor("#ffffff");
g.setFontAlign(0, 0); // center font g.setFontAlign(0, 0); // center font
g.drawString("check app README", 120, 120); g.drawString("check app README\nfor more info", px, py);
g.drawString("for more info", 120, 140);
setWatch(btn1Pressed, BTN1, {repeat:true}); setWatch(btn1Pressed, BTN1, {repeat:true});
setWatch(btn3Pressed, BTN3, {repeat:true}); setWatch(btn3Pressed, BTN3, {repeat:true});
Bangle.on('HRM', function (hrm) {
if(option == 0)
g.flip(); Bangle.on('HRM-raw', function (e) {
if (first_signals < 3) { if (!collectData) return;
g.clear(); storeMyData(e.raw, 0);
g.drawString("setting up...\nremain still " + first_signals * 20 + "%", 120, 120); if (!(samples & 7)) {
first_signals++; Bangle.setLCDPower(1);
g.clearRect(0, py-10, g.getWidth(), py+22);
if (samples < 100)
g.drawString("setting up...\nremain still " + samples + "%", px, py, true);
else
g.drawString("logging: " + (samples*100/raw_HR_array.length).toFixed(0) + "%", px, py, true);
} }
else { if (samples > raw_HR_array.length) {
BPM_array = hrm.raw; collectData = false;
if(hrm.bpm > hr_min && hrm.bpm < hr_max)
heartrate.push(hrm.bpm);
if (pulsecount < 7) {
for (let i = 0; i < 256; i++) {
storeMyData(BPM_array[i], 0);
}
g.clear();
g.drawString("logging: " + ((pulsecount/6)*100).toFixed(0) + "%", 120, 120);
}
if(pulsecount == 6)
turn_off(); turn_off();
pulsecount++;
} }
samples++;
}); });