Merge pull request #1983 from halemmerich/hrmaccevents

Hrmaccevents - Allow recording to bangle storage and show status info on display
master
Gordon Williams 2022-06-27 11:28:10 +01:00 committed by GitHub
commit b96b5db6b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 109 deletions

View File

@ -1 +1,3 @@
0.01: New App!
0.02: Show status info on display
Allow recording to Bangle

View File

@ -0,0 +1,18 @@
# Record HRM and accelerometer events
Record events as they happen via bluetooth or to a file.
This app can use [BTHRM](https://banglejs.com/apps/#bthrm) as a reference.
## Steps for usage
* (Optional) Install [BTHRM](https://banglejs.com/apps/#bthrm) as reference (use ECG based sensor for best accuracy).
* Configure BTHRM to "Both"-Mode. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data.
* Click "Start" in browser.
* Wait until the "Events" number starts to grow, that means there are events recorded.
* Record for some time, since BTHRM and HRM often need some seconds to start getting useful values. Consider 2000 events a useful minimum.
* (Recording to file) Stop the recording with a long press of the button and download log.csv with the Espruino IDE.
* (Recording to browser) Click "Stop" followed by "Save" and store the resulting file on your device.
## Creator
[halemmerich](https://github.com/halemmerich)

View File

@ -4,130 +4,181 @@
</head>
<body>
<script src="https://www.puck-js.com/puck.js"></script>
<button id="btnConnect">Connect</button>
<button id="btnStop">Stop</button>
<button id="btnReset">Reset</button>
<button id="btnSave">Save CSV</button>
<p></div><input type="checkbox" id="chkLocal">Store on Bangle (file named log.csv, download with IDE)</input></p>
<p>
<button id="btnConnect">Start</button>
<button id="btnStop">Stop</button>
<button id="btnReset">Reset</button>
<button id="btnSave">Save CSV</button>
</p>
<p id="result"></p>
<script>
var BANGLE_CODE = String.raw`
var accData=[];
var maxSize=0;
var filename="log.csv";
//0 print, 1 BT, 2 File
var method=1;
function createCode(){
//modes: 1 BT, 2 File
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
var accData=[];
var maxSize=0;
var filename="log.csv";
var running = true;
var gotHRMraw = false;
var gotBTHRM = false;
var gotHRM = false;
var gotAcc = false;
var gotHRMraw = false;
var gotBTHRM = false;
var gotHRM = false;
var gotAcc = false;
var events = -1;
var hrmRaw,hrmPulse,bthrmPulse
function gotAll(){
return running && gotBTHRM && gotHRM && gotHRMraw && gotAcc;
}
Bangle.setHRMPower(1);
if (Bangle.setBTHRMPower){
print("Use BTHRM");
Bangle.setBTHRMPower(1);
Bangle.setBTHRMPower(1);
} else {
gotBTHRM = true;
}
var write=null;
if (method == 2){
var f = require('Storage').open(filename,"w");
f.erase();
f = require('Storage').open(filename,"a");
write = function(str){f.write(str);};
} else if (method == 1){
write = function(str){Bluetooth.print("DATA: " + str);};
} else {
write=print;
}
write("Time,Acc_x,Acc_y,Acc_z,HRM_b,HRM_c,HRM_r,HRM_f,PPG_r,PPG_o,BTHRM\n");
function writeAcc(e){
gotAcc = true;
e.date=Date.now();
accData.push(e);
accData.splice(0, accData.length - maxSize);
}
function writeAccDirect(e){
gotAcc = true;
if (!gotAll()) return;
write(Date.now()+","+e.x+","+e.y+","+e.z+",,,,,,,,\n");
}
function writeBTHRM(e){
gotBTHRM = true;
if (!gotAll()) return;
write(Date.now()+",,,,,,,,,,"+e.bpm+"\n");
}
function writeHRM(e){
gotHRM = true;
if (!gotAll()) return;
while(accData.length > 0){
var c = accData.shift();
if (c) write(c.date+","+c.x+","+c.y+","+c.z+",,,,,,,,\n");
function gotAll(){
return gotBTHRM && gotHRM && gotHRMraw && gotAcc;
}
write(Date.now()+",,,,"+e.bpm+","+e.confidence+",,,,\n");
}
function writeHRMraw(e){
gotHRMraw = true;
if (!gotAll()) return;
write(Date.now()+",,,,,,"+e.raw+","+e.filt+","+e.vcPPG+","+e.vcPPGoffs+",\n");
}
if(maxSize){
Bangle.on("accel", writeAcc);
} else {
Bangle.on("accel", writeAccDirect);
}
Bangle.on("HRM-raw", writeHRMraw);
Bangle.on("HRM", writeHRM);
Bangle.on("BTHRM", writeBTHRM);
g.clear();
g.setColor(1,0,0);
g.fillRect(0,0,g.getWidth(),g.getHeight());
var intervalId = -1;
intervalId = setInterval(()=>{
print("Checking... Acc:" + gotAcc + " BTHRM:" + gotBTHRM + " HRM:" + gotHRM + " HRM raw:" + gotHRMraw);
if (gotAll()){
g.setColor(0,1,0);
g.fillRect(0,0,g.getWidth(),g.getHeight());
clearInterval(intervalId);
Bangle.setHRMPower(1);
if (Bangle.setBTHRMPower){
Bangle.setBTHRMPower(1);
} else {
gotBTHRM = true;
}
}, 1000);
if (Bangle.setBTHRMPower){
var write=null;
if (method == 2){
var f = require('Storage').open(filename,"w");
f.erase();
f = require('Storage').open(filename,"a");
write = function(str){f.write(str);events++;};
} else if (method == 1){
write = function(str){Bluetooth.print("DATA: " + str);events++;};
}
write("Time,Acc_x,Acc_y,Acc_z,HRM_b,HRM_c,HRM_r,HRM_f,PPG_r,PPG_o,BTHRM\n");
function writeAcc(e){
gotAcc = true;
acc = e;
e.date=Date.now();
accData.push(e);
accData.splice(0, accData.length - maxSize);
}
function writeAccDirect(e){
gotAcc = true;
acc = e;
if (!gotAll()) return;
write(Date.now()+","+e.x+","+e.y+","+e.z+",,,,,,,,\n");
}
function writeBTHRM(e){
gotBTHRM = true;
bthrmPulse = e.bpm;
if (!gotAll()) return;
write(Date.now()+",,,,,,,,,,"+e.bpm+"\n");
}
function writeHRM(e){
gotHRM = true;
hrmPulse = e.bpm;
if (!gotAll()) return;
while(accData.length > 0){
var c = accData.shift();
if (c) write(c.date+","+c.x+","+c.y+","+c.z+",,,,,,,,\n");
}
write(Date.now()+",,,,"+e.bpm+","+e.confidence+",,,,\n");
}
function writeHRMraw(e){
gotHRMraw = true;
hrmRaw = e.raw;
if (!gotAll()) return;
write(Date.now()+",,,,,,"+e.raw+","+e.filt+","+e.vcPPG+","+e.vcPPGoffs+",\n");
}
if(maxSize){
Bangle.on("accel", writeAcc);
} else {
Bangle.on("accel", writeAccDirect);
}
Bangle.on("HRM-raw", writeHRMraw);
Bangle.on("HRM", writeHRM);
Bangle.on("BTHRM", writeBTHRM);
g.clear();
function drawStatusText(name, y){
g.setFont12x20();
g.setColor(g.theme.fg);
g.drawString(name, 24, y * 22 + 2);
}
function drawStatus(isOk, y, value){
g.setFont12x20();
if (isOk) g.setColor(0,1,0); else g.setColor(1,0,0);
g.fillRect(0,y * 22, 20, y * 22 + 20);
g.setColor(g.theme.bg);
let x = 120
g.fillRect(x,y*22,g.getWidth(),y*22+20);
g.setColor(g.theme.fg);
if (value) g.drawString(value, x, y * 22 + 2);
}
function updateStatus(){
let h = 1;
drawStatus(gotAcc, h++);
drawStatus(gotBTHRM, h++, bthrmPulse); bthrmPulse = null;
drawStatus(gotHRM, h++, hrmPulse); hrmPulse = null;
drawStatus(gotHRMraw, h++, hrmRaw); hrmRaw = null;
drawStatus(events>0, h++, Math.max(events,0));
if (method == 2){
let free = require('Storage').getFree();
drawStatus(free>0.25*process.env.STORAGE, h++, Math.floor(free/1024) + "K");
}
}
var intervalId = -1;
g.setFont12x20();
g.setColor(g.theme.fg);
g.drawString("Target " + (method==2?"log.csv":"Bluetooth"), 0, 2);
let h = 1;
drawStatusText("Acc", h++);
drawStatusText("BTHRM", h++);
drawStatusText("HRM", h++);
drawStatusText("HRM_r", h++);
drawStatusText("Events", h++);
if (method == 2) drawStatusText("Storage", h++);
updateStatus();
intervalId = setInterval(()=>{
if (!Bangle.isBTHRMOn()) Bangle.setBTHRMPower(1);
}, 5000);
updateStatus();
}, 1000);
if (Bangle.setBTHRMPower){
intervalId = setInterval(()=>{
if (!Bangle.isBTHRMOn()) Bangle.setBTHRMPower(1);
}, 5000);
}
`;
}
`;
var connection;
var lineCount=-1;
function stop (){
connection.write("running = false; \n");
connection.close();
connection.reconnect((c)=>{
c.write("load();\n");
c.close();
connection = undefined;
});
}
document.getElementById("chkLocal").addEventListener("click", function() {
document.getElementById("btnSave").disabled = document.getElementById("chkLocal").checked;
document.getElementById("btnReset").disabled = document.getElementById("chkLocal").checked;
document.getElementById("btnStop").disabled = document.getElementById("chkLocal").checked;
});
document.getElementById("btnSave").addEventListener("click", function() {
var h = document.createElement('a');
h.href = 'data:text/csv;charset=utf-8,' + encodeURI(localStorage.getItem("data"));
@ -150,10 +201,10 @@ document.getElementById("btnStop").addEventListener("click", function() {
});
document.getElementById("btnConnect").addEventListener("click", function() {
localStorage.setItem("data", "");
lineCount=-1;
if (connection) {
stop();
document.getElementById("result").innerText="0";
lineCount=-1;
}
Puck.connect(function(c) {
if (!c) {
@ -170,7 +221,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
});
connection.write("reset();\n", function() {
setTimeout(function() {
connection.write("\x03\x10if(1){"+BANGLE_CODE+"}\n",
connection.write("\x03\x10if(1){"+createCode()+"}\n",
function() { console.log("Ready..."); });
}, 1500);
});

View File

@ -2,13 +2,14 @@
"id": "hrmaccevents",
"name": "HRM Accelerometer event recorder",
"shortName": "HRM ACC recorder",
"version": "0.01",
"version": "0.02",
"type": "RAM",
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
"icon": "app.png",
"tags": "debug",
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"customConnect": true,
"customConnect": false,
"readme": "README.md",
"storage": [ ]
}