BangleApps/apps/HRV/app.js

304 lines
7.4 KiB
JavaScript
Raw Normal View History

var option = null;
2020-12-21 10:48:30 +00:00
//debugging or analysis files
var logfile = require("Storage").open("HRV_log.csv", "w");
logfile = require("Storage").open("HRV_log.csv", "a");
var csv = [
"time",
"sample count",
"HR",
2021-01-02 21:11:34 +00:00
"SDNN",
"RMSSD",
"Temp",
"movement"
];
logfile.write(csv.join(",")+"\n");
2020-12-21 10:48:30 +00:00
var debugging = true;
var samples = 0; // how many samples have we connected?
var collectData = false; // are we currently collecting data?
2020-12-21 10:48:30 +00:00
var BPM_array = [];
var raw_HR_array = new Float32Array(1536);
2020-12-21 10:48:30 +00:00
var alternate_array = new Float32Array(3072);
var pulse_array = [];
var cutoff_threshold = 0.5;
var sample_frequency = 51.6;
var gap_threshold = 0.15;
2021-01-02 21:11:34 +00:00
var movement = 0;
2020-12-21 10:48:30 +00:00
var px = g.getWidth()/2;
var py = g.getHeight()/2;
var accel; // interval for acceleration logging
function storeMyData(data, file_type) { "ram"
2020-12-21 10:48:30 +00:00
log = raw_HR_array;
// shift elements backwards - note the 4, because a Float32 is 4 bytes
log.set(new Float32Array(log.buffer, 4 /*bytes*/));
// add ad final element
log[log.length - 1] = data;
}
function average(samples) {
return E.sum(samples) / samples.length; // faster builtin
/* var sum = 0;
2020-12-21 10:48:30 +00:00
for (var i = 0; i < samples.length; i++) {
sum += parseFloat(samples[i]);
}
var avg = sum / samples.length;
return avg;*/
2020-12-21 10:48:30 +00:00
}
2021-01-02 21:11:34 +00:00
function StandardDeviation (array) {
const n = array.length;
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(E.variance(array, mean));
2020-12-21 10:48:30 +00:00
}
function turn_off() {
Bangle.setHRMPower(0);
2020-12-21 10:48:30 +00:00
g.clear();
g.drawString("processing 1/5", px, py);
2020-12-21 10:48:30 +00:00
rolling_average(raw_HR_array,5);
g.clear();
g.drawString("processing 2/5", px, py);
2020-12-21 10:48:30 +00:00
upscale();
g.clear();
g.drawString("processing 3/5", px, py);
2020-12-21 10:48:30 +00:00
rolling_average(alternate_array,5);
g.clear();
g.drawString("processing 4/5", px, py);
2020-12-21 10:48:30 +00:00
apply_cutoff();
find_peaks();
2020-12-21 10:48:30 +00:00
g.clear();
g.drawString("processing 5/5", px, py);
2020-12-21 10:48:30 +00:00
calculate_HRV();
}
function bernstein(A, B, C, D, E, t) { "ram"
2020-12-21 10:48:30 +00:00
s = 1 - 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));
return x;
}
function upscale() { "ram"
2020-12-21 10:48:30 +00:00
var index = 0;
for (let i = raw_HR_array.length - 1; i > 5; i -= 5) {
p0 = raw_HR_array[i];
p1 = raw_HR_array[i - 1];
p2 = raw_HR_array[i - 2];
p3 = raw_HR_array[i - 3];
p4 = raw_HR_array[i - 4];
for (let T = 0; T < 100; T += 10) {
x = T / 100;
D = bernstein(p0, p1, p2, p3, p4, x);
alternate_array[index] = D;
index++;
}
}
}
function rolling_average(values, count) { "ram"
var temp_array = [];
2020-12-21 10:48:30 +00:00
for (let i = 0; i < values.length; i++) {
temp_array = [];
for (let x = 0; x < count; x++)
temp_array.push(values[i + x]);
values[i] = average(temp_array);
}
}
function apply_cutoff() { "ram"
2020-12-21 10:48:30 +00:00
var x;
for (let i = 0; i < alternate_array.length; i++) {
x = alternate_array[i];
if (x < cutoff_threshold)
x = cutoff_threshold;
alternate_array[i] = x;
}
}
function find_peaks() { "ram"
2020-12-21 10:48:30 +00:00
var previous;
var previous_slope = 0;
var slope;
var gap_size = 0;
var temp_array = [];
for (let i = 0; i < alternate_array.length; i++) {
if (previous == null)
previous = alternate_array[i];
slope = alternate_array[i] - previous;
if (slope * previous_slope < 0) {
if (gap_size > 30) {
pulse_array.push(gap_size);
gap_size = 0;
}
}
else {
gap_size++;
}
previous_slope = slope;
previous = alternate_array[i];
}
}
function RMSSD(samples){ "ram"
2021-01-02 21:11:34 +00:00
var sum = 0;
var square = 0;
2021-01-02 22:53:01 +00:00
var data = [];
var value = 0;
2021-01-02 22:53:01 +00:00
for (let i = 0; i < samples.length-1; i++) {
value = Math.abs(samples[i]-samples[i+1])*((1 / (sample_frequency * 2)) * 1000);
data.push(value);
}
2021-01-02 21:11:34 +00:00
for (let i = 0; i < data.length; i++) {
square = data[i] * data[i];
Math.round(square);
sum += square;
}
2021-01-02 22:53:01 +00:00
2021-01-02 21:11:34 +00:00
var meansquare = sum/data.length;
var RMS = Math.sqrt(meansquare);
RMS = parseInt(RMS);
return RMS;
}
2020-12-21 10:48:30 +00:00
function calculate_HRV() {
var gap_average = average(pulse_array);
var temp_array = [];
var gap_max = (1 + gap_threshold) * gap_average;
var gap_min = (1 - gap_threshold) * gap_average;
for (let i = 0; i < pulse_array.length; i++) {
if (pulse_array[i] > gap_min && pulse_array[i] < gap_max)
temp_array.push(pulse_array[i]);
}
gap_average = average(temp_array);
var calculatedHR = (sample_frequency*60)/(gap_average/2);
if(option == 0)
Bangle.setLCDPower(1);
2020-12-21 10:48:30 +00:00
g.clear();
//var display_stdv = StandardDeviation(pulse_array).toFixed(1);
2021-01-02 21:11:34 +00:00
var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0);
var RMS_SD = RMSSD(temp_array);
g.drawString("SDNN:" + SDNN
+"\nRMSSD:" + RMS_SD
+ "\nHR:" + calculatedHR.toFixed(0)
+"\nSample Count:" + temp_array.length, px, py);
Bangle.setLCDPower(1);
if(option == 0) { // single run
Bangle.buzz(500,1);
option = null;
drawButtons();
} else {
var csv = [
0|getTime(),
temp_array.length,
calculatedHR.toFixed(0),
2021-01-02 21:11:34 +00:00
SDNN,
RMS_SD,
E.getTemperature(),
movement.toFixed(5)
];
logfile.write(csv.join(",")+"\n");
2021-01-02 21:11:34 +00:00
// for (let i = 0; i < raw_HR_array.length; i++) {
// raw_HR_array[i] = null;
//}
turn_on();
}
}
function btn1Pressed() {
if(option === null){
g.clear();
g.drawString("one-off assessment", px, py);
option = 0;
turn_on();
}
}
function btn3Pressed() {
if(option === null){
2021-01-02 21:11:34 +00:00
logfile.write(""); //reset HRV log
g.clear();
g.drawString("continuous mode", px, py);
option = 1;
turn_on();
}
2020-12-21 10:48:30 +00:00
}
function turn_on() {
BPM_array = [];
pulse_array = [];
samples = 0;
if (accel) clearInterval(accel);
movement = 0;
accel = setInterval(function () {
movement = movement + Bangle.getAccel().diff;
}, 1000);
Bangle.setHRMPower(1);
collectData = true;
}
2021-01-02 21:11:34 +00:00
function drawButtons() {
g.setColor("#00ff7f");
g.setFont("6x8", 2);
g.setFontAlign(-1,1);
g.drawString("continuous", 120, 210);
g.drawString("one-time", 140, 50);
g.setColor("#ffffff");
g.setFontAlign(0, 0);
}
2020-12-21 10:48:30 +00:00
g.clear();
drawButtons();
g.setFont("6x8", 2);
g.setColor("#ffffff");
g.setFontAlign(0, 0); // center font
g.drawString("check app README\nfor more info", px, py);
2020-12-21 10:48:30 +00:00
setWatch(btn1Pressed, BTN1, {repeat:true});
setWatch(btn3Pressed, BTN3, {repeat:true});
2020-12-21 10:48:30 +00:00
Bangle.on('HRM-raw', function (e) {
if (!collectData) return;
storeMyData(e.raw, 0);
if (!(samples & 7)) {
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);
}
if (samples > raw_HR_array.length) {
collectData = false;
turn_off();
}
samples++;
2020-12-21 10:48:30 +00:00
});