mirror of https://github.com/espruino/BangleApps
adding files to phystrax
parent
6d5bcd72a0
commit
d49809920a
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwZC/AH4ABgVJkmAA4cEyVJCIwIBkmSAwUBAoIIBCAgaCBYNIA4IFCFgwIDF4Q7CBAWQFg4CBkESCIg+DhIRFAQ9ACKYOLMRWf5IRQ/IRP/4RlzwRKyf5k4RC/xcFCISPBKwMn+YRB/4RFUImf/4RCEwIRIa4P/AAPz/YRDHwLXEgP//1P/+T/8vJQIZCCIkAn/yCIOSv8/2YQCCIOQCIY+CCIYmB8g1CCIkECIM8CII4CLIeACIcAMQd//mSvYRDCAkAiQRFWYcgCIsCCJIQFbQl/8jCGAAq2ByVPCIiwCAAq2BCILCECA6SEAQaMEAAqSCRhIAFCIoQKSQiMHQBJ6IQBB6IQBAQNNwRoLAH4AoA="))
|
|
@ -0,0 +1,114 @@
|
||||||
|
let isMeasuring = false;
|
||||||
|
let currentHeartRate = null;
|
||||||
|
let lcdTimeout;
|
||||||
|
let logData = [];
|
||||||
|
|
||||||
|
function startMeasure() {
|
||||||
|
isMeasuring = true;
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
|
lcdTimeout = setTimeout(() => {
|
||||||
|
Bangle.setLCDTimeout(50);
|
||||||
|
}, 50000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
Bangle.setHRMPower(1);
|
||||||
|
Bangle.on('HRM', handleHeartRate);
|
||||||
|
Bangle.beep(400, 1000); // Buzz to indicate measurement start
|
||||||
|
drawScreen();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopMeasure() {
|
||||||
|
isMeasuring = false;
|
||||||
|
clearTimeout(lcdTimeout);
|
||||||
|
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
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw the title
|
||||||
|
g.setColor('#000000'); // Set text color to black
|
||||||
|
g.drawString('Heart Rate Monitor', g.getWidth() / 2, 10);
|
||||||
|
|
||||||
|
if (isMeasuring) {
|
||||||
|
// Draw measuring status
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw instructions
|
||||||
|
g.setFont('6x8', 1.5);
|
||||||
|
g.drawString('Press button to stop', g.getWidth() / 2, g.getHeight() / 2 + 42);
|
||||||
|
} else {
|
||||||
|
// 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) {
|
||||||
|
g.setFont('6x8', 4);
|
||||||
|
g.drawString(currentHeartRate.toString(), g.getWidth() / 2, g.getHeight() / 2 + 10);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the display
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
setWatch(function() {
|
||||||
|
if (!isMeasuring) {
|
||||||
|
startMeasure();
|
||||||
|
} else {
|
||||||
|
stopMeasure();
|
||||||
|
}
|
||||||
|
}, BTN1, { repeat: true, edge: 'rising' });
|
||||||
|
|
||||||
|
drawScreen();
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,64 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="data"></div>
|
||||||
|
<button class="btn btn-default" id="btnSave">Save</button>
|
||||||
|
<button class="btn btn-default" id="btnDelete">Delete</button>
|
||||||
|
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script>
|
||||||
|
var dataElement = document.getElementById("data");
|
||||||
|
var csvData = "";
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
// Show loading window
|
||||||
|
Util.showModal("Loading...");
|
||||||
|
// Get the data
|
||||||
|
dataElement.innerHTML = "";
|
||||||
|
Util.readStorageFile(`heart_rate_data.csv`, data => {
|
||||||
|
csvData = data.trim();
|
||||||
|
// Remove loading window
|
||||||
|
Util.hideModal();
|
||||||
|
// If no data, report it and exit
|
||||||
|
if (data.length == 0) {
|
||||||
|
dataElement.innerHTML = "<b>No data found</b>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise parse the data and output it as a table
|
||||||
|
dataElement.innerHTML = `<table>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Heart Rate</th>
|
||||||
|
</tr>` + data.trim().split("\n").map(l => {
|
||||||
|
l = l.split(",");
|
||||||
|
return `<tr>
|
||||||
|
<td>${l[0]}</td>
|
||||||
|
<td>${l[1]}</td>
|
||||||
|
</tr>`
|
||||||
|
}).join("\n") + "</table>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call a utility function to save the data
|
||||||
|
document.getElementById("btnSave").addEventListener("click", function() {
|
||||||
|
Util.saveCSV("heart_rate_data", csvData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete the file
|
||||||
|
document.getElementById("btnDelete").addEventListener("click", function() {
|
||||||
|
Util.showModal("Deleting...");
|
||||||
|
Util.eraseStorageFile("heart_rate_data.csv", function() {
|
||||||
|
Util.hideModal();
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Called when the app starts
|
||||||
|
function onInit() {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "phystrax",
|
||||||
|
"name": "PhysTrax",
|
||||||
|
"shortName":"PhysTrax",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Tracking physiological measurements to support active learning in classrooms",
|
||||||
|
"tags": "health",
|
||||||
|
"interface": "interface.html",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"phystrax.app.js","url":"app.js"},
|
||||||
|
{"name":"phystrax.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue