mirror of https://github.com/espruino/BangleApps
HRV 0.04: Modify to work with new heart rate API, but still not sure it's working correctly
parent
95e037a09f
commit
e0b6887bd8
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
146
apps/HRV/app.js
146
apps/HRV/app.js
|
@ -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++;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue