2024-01-03 09:52:30 +00:00
|
|
|
var option = null;
|
|
|
|
|
|
|
|
//debugging or analysis files
|
2024-01-04 14:54:24 +00:00
|
|
|
//var logfile = require("Storage").open("HRV_log.csv", "w");
|
2024-01-03 09:52:30 +00:00
|
|
|
|
2024-01-04 15:14:02 +00:00
|
|
|
var logfile = require("Storage").open("HRV_logs.csv", "a");
|
2024-01-03 09:52:30 +00:00
|
|
|
|
|
|
|
var csv = [
|
|
|
|
"time",
|
|
|
|
"sample count",
|
|
|
|
"HR",
|
|
|
|
"SDNN",
|
|
|
|
"RMSSD",
|
|
|
|
"Temp",
|
|
|
|
"movement"
|
|
|
|
];
|
|
|
|
logfile.write(csv.join(",")+"\n");
|
|
|
|
|
|
|
|
var samples = 0; // how many samples have we connected?
|
|
|
|
var collectData = false; // are we currently collecting data?
|
|
|
|
|
2024-03-04 20:34:50 +00:00
|
|
|
//var BPM_array = [];
|
2024-01-03 09:52:30 +00:00
|
|
|
var raw_HR_array = new Float32Array(1536);
|
|
|
|
var alternate_array = new Float32Array(3072);
|
|
|
|
var pulse_array = [];
|
|
|
|
var cutoff_threshold = 0.5;
|
|
|
|
var sample_frequency = 51.6;
|
|
|
|
var gap_threshold = 0.15;
|
|
|
|
var movement = 0;
|
|
|
|
|
|
|
|
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;
|
|
|
|
// 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
|
2024-01-04 15:14:02 +00:00
|
|
|
/* var sum = 0;
|
|
|
|
for (var i = 0; i < samples.length; i++) {
|
|
|
|
sum += parseFloat(samples[i]);
|
|
|
|
}
|
|
|
|
var avg = sum / samples.length;
|
|
|
|
return avg;*/
|
2024-01-03 09:52:30 +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));
|
|
|
|
}
|
|
|
|
|
|
|
|
function turn_off() {
|
|
|
|
Bangle.setHRMPower(0);
|
|
|
|
|
|
|
|
|
|
|
|
g.clear();
|
|
|
|
g.drawString("processing 1/5", px, py);
|
|
|
|
|
|
|
|
rolling_average(raw_HR_array,5);
|
|
|
|
g.clear();
|
|
|
|
g.drawString("processing 2/5", px, py);
|
|
|
|
|
|
|
|
upscale();
|
|
|
|
g.clear();
|
|
|
|
g.drawString("processing 3/5", px, py);
|
|
|
|
|
|
|
|
rolling_average(alternate_array,5);
|
|
|
|
g.clear();
|
|
|
|
g.drawString("processing 4/5", px, py);
|
|
|
|
|
|
|
|
apply_cutoff();
|
|
|
|
find_peaks();
|
|
|
|
|
|
|
|
g.clear();
|
|
|
|
g.drawString("processing 5/5", px, py);
|
|
|
|
|
|
|
|
calculate_HRV();
|
|
|
|
}
|
|
|
|
|
|
|
|
function bernstein(A, B, C, D, E, t) { "ram"
|
|
|
|
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"
|
|
|
|
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 = [];
|
|
|
|
|
|
|
|
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"
|
|
|
|
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"
|
|
|
|
var previous;
|
|
|
|
var previous_slope = 0;
|
|
|
|
var slope;
|
|
|
|
var gap_size = 0;
|
|
|
|
|
|
|
|
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"
|
|
|
|
var sum = 0;
|
|
|
|
var square = 0;
|
|
|
|
var data = [];
|
|
|
|
var value = 0;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
square = data[i] * data[i];
|
|
|
|
Math.round(square);
|
|
|
|
sum += square;
|
|
|
|
}
|
|
|
|
|
|
|
|
var meansquare = sum/data.length;
|
|
|
|
var RMS = Math.sqrt(meansquare);
|
|
|
|
RMS = parseInt(RMS);
|
|
|
|
return RMS;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
g.clear();
|
|
|
|
//var display_stdv = StandardDeviation(pulse_array).toFixed(1);
|
|
|
|
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),
|
|
|
|
SDNN,
|
|
|
|
RMS_SD,
|
|
|
|
E.getTemperature(),
|
|
|
|
movement.toFixed(5)
|
|
|
|
];
|
|
|
|
logfile.write(csv.join(",")+"\n");
|
2024-01-04 15:14:02 +00:00
|
|
|
|
|
|
|
|
2024-01-03 09:52:30 +00:00
|
|
|
turn_on();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function btn3Pressed() {
|
|
|
|
if(option === null){
|
|
|
|
logfile.write(""); //reset HRV log
|
|
|
|
g.clear();
|
|
|
|
g.drawString("continuous mode", px, py);
|
|
|
|
option = 1;
|
|
|
|
|
|
|
|
turn_on();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function turn_on() {
|
2024-03-04 20:34:50 +00:00
|
|
|
//BPM_array = [];
|
2024-01-03 09:52:30 +00:00
|
|
|
pulse_array = [];
|
|
|
|
samples = 0;
|
|
|
|
if (accel) clearInterval(accel);
|
|
|
|
movement = 0;
|
|
|
|
accel = setInterval(function () {
|
|
|
|
movement = movement + Bangle.getAccel().diff;
|
|
|
|
}, 1000);
|
|
|
|
Bangle.setHRMPower(1);
|
|
|
|
collectData = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function drawButtons() {
|
|
|
|
g.setColor("#00ff7f");
|
|
|
|
g.setFont("6x8", 2);
|
|
|
|
g.setFontAlign(-1,1);
|
2024-01-04 15:14:02 +00:00
|
|
|
g.drawString("start recording HRV", 120, 210);
|
2024-01-03 09:52:30 +00:00
|
|
|
g.setColor("#ffffff");
|
|
|
|
g.setFontAlign(0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
g.clear();
|
|
|
|
|
|
|
|
drawButtons();
|
|
|
|
|
|
|
|
g.setFont("6x8", 2);
|
|
|
|
g.setColor("#ffffff");
|
|
|
|
g.setFontAlign(0, 0); // center font
|
|
|
|
|
|
|
|
setWatch(btn3Pressed, BTN3, {repeat:true});
|
|
|
|
|
|
|
|
|
2024-01-04 15:14:02 +00:00
|
|
|
|
2024-01-03 09:52: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++;
|
|
|
|
});
|