mirror of https://github.com/espruino/BangleApps
BT HRV - Adds app for taking HRV measurements
parent
0b4d2b5b5d
commit
dc435c3733
|
@ -0,0 +1,11 @@
|
|||
0.01: New App!
|
||||
0.02: Make overriding the HRM event optional
|
||||
Emit BTHRM event for external sensor
|
||||
Add recorder app plugin
|
||||
0.03: Prevent readings from internal sensor mixing into BT values
|
||||
Mark events with src property
|
||||
Show actual source of event in app
|
||||
0.04: Allow reading additional data if available: HRM battery and position
|
||||
Better caching of scanned BT device properties
|
||||
New setting for not starting the BTHRM together with HRM
|
||||
Save some RAM by not definining functions if disabled in settings
|
|
@ -0,0 +1,11 @@
|
|||
# Bluetooth Heart Rate Variance
|
||||
|
||||
This app uses [BTHRM](https://banglejs.com/apps/#bthrm) and can calculate the HRV if the used bluetooth heart rate monitor delivers interval data.
|
||||
|
||||
## Usage
|
||||
|
||||
Just install and start the app. Select button resets the already measured values.
|
||||
|
||||
## Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwJC/ABUMAokcAq0eAok+Aok2AgcCm0EAoUHmw2DAoMOAgMDh9jEgPAg/98cfn/gg/58cbv/ggcB8cz8HADIPjmIECgHB8OAAoVB8AFDgPgIQcBCwYFMAH4ARA"))
|
|
@ -0,0 +1,143 @@
|
|||
var btm = g.getHeight()-1;
|
||||
var ui = false;
|
||||
|
||||
function clear(y){
|
||||
g.reset();
|
||||
g.clearRect(0,y,g.getWidth(),g.getHeight());
|
||||
}
|
||||
|
||||
var startingTime;
|
||||
var currentSlot = 0;
|
||||
var hrvSlots = [10,20,30,60,120,300];
|
||||
var hrvValues = {};
|
||||
var rrRmsProgress;
|
||||
var saved = false;
|
||||
|
||||
var rrNumberOfValues = 0;
|
||||
var rrSquared = 0;
|
||||
var rrLastValue
|
||||
var rrMax;
|
||||
var rrMin;
|
||||
|
||||
function calcHrv(rr){
|
||||
//Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/
|
||||
for (currentRr of rr){
|
||||
if (!rrMax) rrMax = currentRr;
|
||||
if (!rrMin) rrMin = currentRr;
|
||||
rrMax = Math.max(rrMax, currentRr);
|
||||
rrMin = Math.min(rrMin, currentRr);
|
||||
//print("Calc for: " + currentRr);
|
||||
rrNumberOfValues++;
|
||||
if (!rrLastValue){
|
||||
rrLastValue = currentRr;
|
||||
continue;
|
||||
}
|
||||
rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr);
|
||||
|
||||
//print("rr²: " + rrSquared);
|
||||
rrLastValue = currentRr;
|
||||
}
|
||||
var rms = Math.sqrt(rrSquared / rrNumberOfValues);
|
||||
//print("rms: " + rms);
|
||||
return rms;
|
||||
}
|
||||
|
||||
|
||||
function draw(y, hrv) {
|
||||
clear(y);
|
||||
var px = g.getWidth()/2;
|
||||
var str = hrv.toFixed(1) + "ms";
|
||||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
|
||||
for (var i = 0; i < hrvSlots.length; i++){
|
||||
str = hrvSlots[i] + "s: ";
|
||||
if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms";
|
||||
g.setFontVector(16).drawString(str,px,y+44+(i*17));
|
||||
}
|
||||
|
||||
g.setRotation(3);
|
||||
g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10);
|
||||
g.setRotation(0);
|
||||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
if (e.rr && !startingTime) Bangle.buzz(500);
|
||||
if (e.rr && !startingTime) startingTime=Date.now();
|
||||
//print("Event:" + e.rr);
|
||||
|
||||
var hrv = calcHrv(e.rr);
|
||||
if (hrv){
|
||||
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
|
||||
hrvValues[hrvSlots[currentSlot]] = hrv;
|
||||
currentSlot++;
|
||||
}
|
||||
}
|
||||
if (!saved && currentSlot == hrvSlots.length){
|
||||
var file = require('Storage').open("bthrv.csv", "a");
|
||||
var data = new Date(startingTime).toISOString();
|
||||
for (var c of hrvSlots){
|
||||
data+=","+hrvValues[c];
|
||||
}
|
||||
data+="," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
||||
data+="\n";
|
||||
file.write(data);
|
||||
saved = true;
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
if (hrv){
|
||||
if (!ui){
|
||||
Bangle.setUI("leftright", ()=>{
|
||||
resetHrv();
|
||||
clear(30);
|
||||
});
|
||||
ui = true;
|
||||
}
|
||||
draw(30, hrv);
|
||||
}
|
||||
}
|
||||
|
||||
function resetHrv(){
|
||||
hrvValues={};
|
||||
startingTime=undefined;
|
||||
currentSlot=0;
|
||||
saved=false;
|
||||
rrNumberOfValues = 0;
|
||||
rrSquared = 0;
|
||||
rrLastValue = undefined;
|
||||
rrMax = undefined;
|
||||
rrMin = undefined;
|
||||
}
|
||||
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
||||
if (Bangle.setBTHRMPower){
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.setBTHRMPower(1,'bthrv');
|
||||
|
||||
if (require('Storage').list(/bthrv.csv/).length == 0){
|
||||
var file = require('Storage').open("bthrv.csv", "a");
|
||||
var data = "Time";
|
||||
for (var c of hrvSlots){
|
||||
data+="," + c + "s";
|
||||
}
|
||||
data+=",RR_max,RR_min,Measurements";
|
||||
data+="\n";
|
||||
file.write(data);
|
||||
}
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
} else {
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
}
|
||||
|
||||
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrv'));
|
Binary file not shown.
After Width: | Height: | Size: 670 B |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "bthrv",
|
||||
"name": "Bluetooth Heart Rate variance calculator",
|
||||
"shortName": "BT HRV",
|
||||
"version": "0.01",
|
||||
"description": "Calculates HRV from a a BT HRM with interval data",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthrv.app.js","url":"app.js"},
|
||||
{"name":"bthrv.recorder.js","url":"recorder.js"},
|
||||
{"name":"bthrv.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
(function(recorders) {
|
||||
recorders.bthrv = function() {
|
||||
var lastGetValue = 0;
|
||||
var lastUpdate = 0;
|
||||
var rrHistory = [];
|
||||
var hrv = "";
|
||||
function onHRM(h) {
|
||||
if(!h.rr) return;
|
||||
if (lastUpdate + 3000 < Date.now()){
|
||||
rrHistory = [];
|
||||
}
|
||||
rrHistory = rrHistory.concat(h.rr);
|
||||
lastUpdate=Date.now();
|
||||
}
|
||||
return {
|
||||
name : "BT HRV",
|
||||
fields : ["BT HRV"],
|
||||
getValues : () => {
|
||||
if (lastGetValue + 10000 < Date.now()){
|
||||
lastGetValue = Date.now();
|
||||
|
||||
if (rrHistory.length > 0){
|
||||
if (rrHistory.length > 1){
|
||||
var squaredSum = 0;
|
||||
var last = rrHistory[0]
|
||||
for (var i = 1; i < rrHistory.length; i++){
|
||||
squaredSum += (last - rrHistory[i])*(last - rrHistory[i]);
|
||||
last = rrHistory[i];
|
||||
}
|
||||
hrv = Math.sqrt(squaredSum/rrHistory.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
result = [hrv];
|
||||
hrv = "";
|
||||
rrHistory = [];
|
||||
return result;
|
||||
},
|
||||
start : () => {
|
||||
Bangle.on('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor((rrHistory.length > 0)?"#00f":"#008").drawImage(atob("DAwBAAAACECECECEDGClacEEAAAA"),x,y)
|
||||
};
|
||||
}
|
||||
})
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Loading…
Reference in New Issue