forked from FOSS/BangleApps
edited app.js
parent
27d745f1cc
commit
8fd8cf56ea
|
@ -1,15 +1,10 @@
|
|||
var isMeasuring = false;
|
||||
var currentHeartRate = null;
|
||||
var lcdTimeout;
|
||||
var bpmValues = [];
|
||||
var logData = [];
|
||||
var lastHeartRateLogTimestamp = 0;
|
||||
let isMeasuring = false;
|
||||
let currentHR = null;
|
||||
let lcdTimeout;
|
||||
let logData = [];
|
||||
let bpmValues = [];
|
||||
let lastLogTime = 0;
|
||||
|
||||
// HRV calculation variables
|
||||
var hrvSlots = [10, 20, 30, 60, 120, 300];
|
||||
var hrvValues = {};
|
||||
|
||||
// Heart rate monitor functions
|
||||
function startMeasure() {
|
||||
isMeasuring = true;
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
@ -18,9 +13,9 @@ function startMeasure() {
|
|||
}, 50000);
|
||||
|
||||
setTimeout(() => {
|
||||
Bangle.setHRMPower(1);
|
||||
Bangle.setHRMPower(1); // starts HRM
|
||||
Bangle.on('HRM', handleHeartRate);
|
||||
Bangle.buzz(500, 20);
|
||||
Bangle.buzz(200, 10); // Buzz to indicate measurement start
|
||||
drawScreen();
|
||||
}, 500);
|
||||
}
|
||||
|
@ -30,23 +25,76 @@ function stopMeasure() {
|
|||
clearTimeout(lcdTimeout);
|
||||
Bangle.setLCDTimeout(10);
|
||||
Bangle.setHRMPower(0);
|
||||
Bangle.removeAllListeners('HRM');
|
||||
Bangle.buzz(500, 20);
|
||||
Bangle.removeAllListeners('HRM'); //stop HRM
|
||||
saveDataToCSV(); // Save data to CSV when measurement stops
|
||||
Bangle.buzz(200, 10); // Buzz to indicate measurement stop
|
||||
drawScreen();
|
||||
}
|
||||
|
||||
function handleHeartRate(hrm) {
|
||||
if (hrm.confidence > 90) {
|
||||
currentHR = hrm.bpm;
|
||||
let currentTime = Date.now();
|
||||
let elaspedTime = currentTime - lastLogTime;
|
||||
let nextLog = 5 * 1000 - (elaspedTime % (5 * 1000)); // Calculate time to next log
|
||||
if (elaspedTime >= 5 * 1000) { // Check if it's time for the next log
|
||||
lastLogTime = currentTime - (elaspedTime % (5 * 1000)); // Set last log time to the previous 5-second boundary
|
||||
let date = new Date(lastLogTime);
|
||||
let dateStr = require("locale").date(date);
|
||||
let timeStr = require("locale").time(date, 1);
|
||||
let seconds = date.getSeconds();
|
||||
let timestamp = `${dateStr} ${timeStr}:${seconds}`; // Concatenate date, time, and seconds
|
||||
logData.push({ timestamp: timestamp, heartRate: currentHR });
|
||||
bpmValues.push(currentHR); // Store heart rate for HRV calculation
|
||||
if (bpmValues.length > 30) bpmValues.shift(); // Keep last 30 heart rate values
|
||||
// Calculate and add SDNN (standard deviation of NN intervals) to the last log entry
|
||||
logData[logData.length - 1].hrv = calcSDNN();
|
||||
drawScreen();
|
||||
}
|
||||
// Schedule next measurement
|
||||
setTimeout(() => {
|
||||
handleHeartRate(hrm);
|
||||
}, nextLog);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function calcSDNN() {
|
||||
if (bpmValues.length < 5) return 0; // No calculation if insufficient data
|
||||
|
||||
// Calculate differences between adjacent heart rate values
|
||||
const differences = [];
|
||||
for (let i = 1; i < bpmValues.length; i++) {
|
||||
differences.push(Math.abs(bpmValues[i] - bpmValues[i - 1]));
|
||||
}
|
||||
|
||||
// Calculate mean difference
|
||||
const meanDifference = differences.reduce((acc, val) => acc + val, 0) / differences.length;
|
||||
|
||||
// Calculate squared differences from mean difference
|
||||
const squaredDifferences = differences.map(diff => Math.pow(diff - meanDifference, 2));
|
||||
|
||||
// Calculate mean squared difference
|
||||
const meanSquaredDifference = squaredDifferences.reduce((acc, val) => acc + val, 0) / squaredDifferences.length;
|
||||
|
||||
// Calculate SDNN (standard deviation of NN intervals)
|
||||
const sdnn = Math.sqrt(meanSquaredDifference);
|
||||
|
||||
return sdnn;
|
||||
}
|
||||
|
||||
function drawScreen(message) {
|
||||
g.clear(); // Clear the display
|
||||
|
||||
// Set the background color
|
||||
g.setColor('#95E7FF');
|
||||
g.setColor('#95E7FF');
|
||||
|
||||
// Fill the entire display with the background color
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
// Set font and alignment for drawing text
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont('Vector', 15);
|
||||
g.setFont('Vector', 15);
|
||||
|
||||
// Draw the title
|
||||
g.setColor('#000000'); // Set text color to black
|
||||
|
@ -57,12 +105,13 @@ function drawScreen(message) {
|
|||
g.setFont('6x8', 2);
|
||||
g.drawString('Measuring...', g.getWidth() / 2, g.getHeight() / 2 - 10);
|
||||
|
||||
// Draw current heart rate if available and not zero
|
||||
if (currentHeartRate !== null && currentHeartRate > 0) {
|
||||
g.setFont('6x8', 4);
|
||||
g.drawString(currentHeartRate.toString(), g.getWidth() / 2, g.getHeight() / 2 + 20);
|
||||
// Draw current heart rate if available
|
||||
g.setFont('6x8', 4);
|
||||
if (currentHR !== null) {
|
||||
g.drawString(currentHR.toString(), g.getWidth() / 2, g.getHeight() / 2 + 20);
|
||||
g.setFont('6x8', 1.6);
|
||||
g.drawString(' BPM', g.getWidth() / 2 + 42, g.getHeight() / 2 + 20);
|
||||
|
||||
}
|
||||
|
||||
// Draw instructions
|
||||
|
@ -72,16 +121,17 @@ function drawScreen(message) {
|
|||
// Draw last heart rate
|
||||
g.setFont('Vector', 12);
|
||||
g.drawString('Last Heart Rate:', g.getWidth() / 2, g.getHeight() / 2 - 20);
|
||||
if (currentHeartRate !== null && currentHeartRate > 0) {
|
||||
if (currentHR !== null && currentHR > 0) {
|
||||
g.setFont('6x8', 4);
|
||||
g.drawString(currentHeartRate.toString(), g.getWidth() / 2, g.getHeight() / 2 + 10);
|
||||
g.drawString(currentHR.toString(), g.getWidth() / 2, g.getHeight() / 2 + 10);
|
||||
g.setFont('6x8', 1.6);
|
||||
g.drawString(' BPM', g.getWidth() / 2 + 42, g.getHeight() / 2 + 12);
|
||||
|
||||
} else {
|
||||
g.setFont('6x8', 2);
|
||||
g.drawString('No data', g.getWidth() / 2, g.getHeight() / 2 + 10);
|
||||
g.setFont('6x8', 1);
|
||||
g.drawString(message || 'Press button to start!', g.getWidth() / 2, g.getHeight() / 2 + 30);
|
||||
g.drawString(message || 'Press button to start', g.getWidth() / 2, g.getHeight() / 2 + 30);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,78 +140,14 @@ function drawScreen(message) {
|
|||
}
|
||||
|
||||
function saveDataToCSV() {
|
||||
let date = new Date();
|
||||
let dateStr = require("locale").date(date);
|
||||
let timeStr = require("locale").time(date, 1);
|
||||
let seconds = date.getSeconds();
|
||||
let timestamp = `${dateStr} ${timeStr}:${seconds}`;
|
||||
|
||||
// Check if 10 seconds have passed since the last heart rate log
|
||||
if (date.getTime() - lastHeartRateLogTimestamp >= 10 * 1000) {
|
||||
logData.push({ timestamp: timestamp, heartRate: currentHeartRate });
|
||||
lastHeartRateLogTimestamp = date.getTime(); // Update the last heart rate log timestamp
|
||||
|
||||
// Log data to CSV
|
||||
let csvContent = "Timestamp,Heart Rate,HRV (ms)\n";
|
||||
logData.forEach(entry => {
|
||||
if (entry.heartRate > 0) {
|
||||
let hrv = hrvValues[entry.timestamp] || ""; // Get HRV value for the timestamp
|
||||
csvContent += `${entry.timestamp},${entry.heartRate},${hrv}\n`;
|
||||
}
|
||||
});
|
||||
require("Storage").write("heart_rate_data.csv", csvContent);
|
||||
}
|
||||
}
|
||||
|
||||
function handleHeartRate(hrm) {
|
||||
if (isMeasuring) {
|
||||
currentHeartRate = hrm.bpm;
|
||||
drawScreen();
|
||||
|
||||
// Log data to CSV
|
||||
saveDataToCSV();
|
||||
|
||||
// Estimate RR intervals
|
||||
let rrIntervals = estimateRRIntervals(bpmValues);
|
||||
|
||||
// Calculate HRV
|
||||
calculateHRV(rrIntervals);
|
||||
}
|
||||
}
|
||||
|
||||
function estimateRRIntervals(rawData) {
|
||||
let rrIntervals = [];
|
||||
for (let i = 1; i < rawData.length; i++) {
|
||||
let rrInterval = rawData[i] - rawData[i - 1];
|
||||
rrIntervals.push(rrInterval);
|
||||
}
|
||||
return rrIntervals;
|
||||
}
|
||||
|
||||
function calculateHRV(rawData) {
|
||||
// Calculate HRV with SDNN method for each slot in hrvSlots
|
||||
let sdnn = calcSDNN(rawData);
|
||||
hrvValues[Date.now()] = sdnn; // Store HRV value with current timestamp
|
||||
|
||||
// Log HRV data if 5 minutes have elapsed
|
||||
if (Date.now() - lastHeartRateLogTimestamp >= 5 * 60 * 1000) {
|
||||
console.log("Recalculating HRV...");
|
||||
saveDataToCSV(); // Save both heart rate and HRV data to the CSV file
|
||||
lastHeartRateLogTimestamp = Date.now(); // Update the timestamp
|
||||
}
|
||||
}
|
||||
|
||||
function calcSDNN(rrIntervals) {
|
||||
let sumOfDifferencesSquared = 0;
|
||||
let meanRRInterval = rrIntervals.reduce((acc, val) => acc + val, 0) / rrIntervals.length;
|
||||
rrIntervals.forEach(rr => {
|
||||
sumOfDifferencesSquared += Math.pow(rr - meanRRInterval, 2);
|
||||
let csvContent = "Timestamp,Heart Rate(bpm),HRV(ms)\n";
|
||||
logData.forEach(entry => {
|
||||
csvContent += `${entry.timestamp},${entry.heartRate},${entry.hrv}\n`;
|
||||
});
|
||||
let meanOfDifferencesSquared = sumOfDifferencesSquared / (rrIntervals.length - 1);
|
||||
return Math.sqrt(meanOfDifferencesSquared);
|
||||
require("Storage").write("heart_rate_data.csv", csvContent);
|
||||
}
|
||||
|
||||
setWatch(function () {
|
||||
setWatch(function() {
|
||||
if (!isMeasuring) {
|
||||
startMeasure();
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue