diff --git a/apps/phystrax/app.js b/apps/phystrax/app.js index 663652c14..ec2409c94 100644 --- a/apps/phystrax/app.js +++ b/apps/phystrax/app.js @@ -1,8 +1,15 @@ -let isMeasuring = false; -let currentHeartRate = null; -let lcdTimeout; -let logData = []; +var isMeasuring = false; +var currentHeartRate = null; +var lcdTimeout; +var bpmValues = []; +var logData = []; +var lastHeartRateLogTimestamp = 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); @@ -13,7 +20,7 @@ function startMeasure() { setTimeout(() => { Bangle.setHRMPower(1); Bangle.on('HRM', handleHeartRate); - Bangle.beep(400, 1000); // Buzz to indicate measurement start + Bangle.buzz(500, 20); drawScreen(); }, 500); } @@ -24,37 +31,22 @@ function stopMeasure() { Bangle.setLCDTimeout(10); Bangle.setHRMPower(0); Bangle.removeAllListeners('HRM'); - saveDataToCSV(); // Save data to CSV when measurement stops - Bangle.beep(400, 800); // Buzz to indicate measurement stop + Bangle.buzz(500, 20); drawScreen(); } -function handleHeartRate(hrm) { - if (hrm.confidence > 90) { - currentHeartRate = hrm.bpm; - 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}`; // Concatenate date, time, and seconds - logData.push({ timestamp: timestamp, heartRate: currentHeartRate }); - drawScreen(); - } -} - - 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 @@ -65,9 +57,9 @@ function drawScreen(message) { g.setFont('6x8', 2); g.drawString('Measuring...', g.getWidth() / 2, g.getHeight() / 2 - 10); - // Draw current heart rate if available - g.setFont('6x8', 4); - if (currentHeartRate !== null) { + // 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); g.setFont('6x8', 1.6); g.drawString(' BPM', g.getWidth() / 2 + 42, g.getHeight() / 2 + 20); @@ -83,6 +75,8 @@ function drawScreen(message) { if (currentHeartRate !== null && currentHeartRate > 0) { g.setFont('6x8', 4); g.drawString(currentHeartRate.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); @@ -96,14 +90,78 @@ function drawScreen(message) { } function saveDataToCSV() { - let csvContent = "Timestamp,Heart Rate\n"; - logData.forEach(entry => { - csvContent += `${entry.timestamp},${entry.heartRate}\n`; - }); - require("Storage").write("heart_rate_data.csv", csvContent); + 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); + } } -setWatch(function() { +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 meanOfDifferencesSquared = sumOfDifferencesSquared / (rrIntervals.length - 1); + return Math.sqrt(meanOfDifferencesSquared); +} + +setWatch(function () { if (!isMeasuring) { startMeasure(); } else {