Merge pull request #218 from msdeibel/master

Workout HRM
pull/224/head^2
Gordon Williams 2020-04-04 12:56:27 +01:00 committed by GitHub
commit 616bfcffe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 344 additions and 0 deletions

View File

@ -864,6 +864,19 @@
{"name":"torch.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "wohrm",
"name": "Workout HRM",
"icon": "app.png",
"version":"0.05",
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
"tags": "hrm,workout",
"type": "app",
"allow_emulator":true,
"storage": [
{"name":"wohrm.app.js","url":"app.js"},
{"name":"wohrm.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "widid",
"name": "Bluetooth ID Widget",
"icon": "widget.png",

5
apps/wohrm/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.05: Improved buzz timing and rendering
0.04: Only buzz on high confidence (>85%)
0.03: Optimized rendering for the background
0.02: Adapted to new App code layout
0.01: Only tested on the emulator.

1
apps/wohrm/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4AVgnd5tABI3c7oJGAAUs5gAC4gJDpgJD4QWGhoMDAAPQBJYADBgoABBJYAChgJD5oDC4AJEAAfAC4fcBIfUDYYJEEogWCgQJEoYSHAAsgIw3MmYqIn89JAoXFn5DH4f/+YXFWQnE/4GEAAXP///ZgooE4X/ngvMPAQXEBoIXHHIJfDC4ss5nf+f9OosjFwgXF5oTBp8z+gMBMQPTn5dBNIgXCAwPDEQM/mQmCJQNP/8zDIJRDO4SnB6fz7k/poXEJwIJBmanGhvMl//loxC7nE/jUCon/6gzBC4PQC4MDKIJFDn9M4YXB5nUKYbACmAXBgE/+YMBOoMvngXDJIKDB6YvBOwRgDaoINB788p5wDn7HELwQABghWCBoPD/s/YwNN5i+Bc4dAC4bBCC4fyPIPU+Z0BDAZGEJAffYgPC+ZxBG4KkB6f/C4JGEAAQsBcIX/+QEBCgP9A4IXBCwwwB5pxDPYJoDcgIuIGASJH5rvBAwIWIeYQABl5jBAAXDIwLrCABCcC76gDAoP0RgwAFYYJ7DJAcsFxYABaYJ7DAAXECxhJEAAgWOPQgACIpoADUwb1BCyBJERZgYKkAXUglACygA/AH4AFA=="))

325
apps/wohrm/app.js Normal file
View File

@ -0,0 +1,325 @@
/* eslint-disable no-undef */
const Setter = {
NONE: "none",
UPPER: 'upper',
LOWER: 'lower'
};
const shortBuzzTimeInMs = 80;
const longBuzzTimeInMs = 400;
let upperLimit = 130;
let upperLimitChanged = true;
let lowerLimit = 100;
let lowerLimitChanged = true;
let limitSetter = Setter.NONE;
let currentHeartRate = 0;
let hrConfidence = -1;
let hrChanged = true;
let confidenceChanged = true;
let setterHighlightTimeout;
function renderUpperLimitBackground() {
g.setColor(1,0,0);
g.fillRect(125,40, 210, 70);
g.fillRect(180,70, 210, 200);
//Round top left corner
g.fillEllipse(115,40,135,70);
//Round top right corner
g.setColor(0,0,0);
g.fillRect(205,40, 210, 45);
g.setColor(1,0,0);
g.fillEllipse(190,40,210,50);
//Round inner corner
g.fillRect(174,71, 179, 76);
g.setColor(0,0,0);
g.fillEllipse(160,71,179,82);
//Round bottom
g.setColor(1,0,0);
g.fillEllipse(180,190, 210, 210);
}
function renderLowerLimitBackground() {
g.setColor(0,0,1);
g.fillRect(10, 180, 100, 210);
g.fillRect(10, 50, 40, 180);
//Rounded top
g.setColor(0,0,1);
g.fillEllipse(10,40, 40, 60);
//Round bottom right corner
g.setColor(0,0,1);
g.fillEllipse(90,180,110,210);
//Round inner corner
g.setColor(0,0,1);
g.fillRect(40,175,45,180);
g.setColor(0,0,0);
g.fillEllipse(41,170,60,179);
//Round bottom left corner
g.setColor(0,0,0);
g.fillRect(10,205, 15, 210);
g.setColor(0,0,1);
g.fillEllipse(10,200,30,210);
}
function drawTrainingHeartRate() {
//Only redraw if the display is on
if (Bangle.isLCDOn()) {
renderUpperLimit();
renderCurrentHeartRate();
renderLowerLimit();
renderConfidenceBars();
}
buzz();
}
function renderUpperLimit() {
if(!upperLimitChanged) { return; }
g.setColor(1,0,0);
g.fillRect(125,40, 210, 70);
if(limitSetter === Setter.UPPER){
g.setColor(255,255, 0);
} else {
g.setColor(255,255,255);
}
g.setFontVector(13);
g.drawString("Upper : " + upperLimit, 130,50);
upperLimitChanged = false;
}
function renderCurrentHeartRate() {
if(!hrChanged) { return; }
g.setColor(255,255,255);
g.fillRect(55, 110, 165, 150);
g.setColor(0,0,0);
g.setFontVector(24);
g.setFontAlign(1, -1, 0);
g.drawString(currentHeartRate, 130, 117);
//Reset alignment to defaults
g.setFontAlign(-1, -1, 0);
hrChanged = false;
}
function renderLowerLimit() {
if(!lowerLimitChanged) { return; }
g.setColor(0,0,1);
g.fillRect(10, 180, 100, 210);
if(limitSetter === Setter.LOWER){
g.setColor(255,255, 0);
} else {
g.setColor(255,255,255);
}
g.setFontVector(13);
g.drawString("Lower : " + lowerLimit, 20,190);
lowerLimitChanged = false;
}
function renderConfidenceBars(){
if(!confidenceChanged) { return; }
if(hrConfidence >= 85){
g.setColor(0, 255, 0);
} else if (hrConfidence >= 50) {
g.setColor(255, 255, 0);
} else if(hrConfidence >= 0){
g.setColor(255, 0, 0);
} else {
g.setColor(255, 255, 255);
}
g.fillRect(45, 110, 55, 150);
g.fillRect(165, 110, 175, 150);
confidenceChanged = false;
}
function renderButtonIcons() {
g.setColor(255,255,255);
g.setFontVector(14);
//+ for Btn1
g.drawString("+", 222,50);
//Home for Btn2
g.drawLine(220, 118, 227, 110);
g.drawLine(227, 110, 234, 118);
g.drawPoly([222,117,222,125,232,125,232,117], false);
g.drawRect(226,120,229,125);
//- for Btn3
g.drawString("-", 222,165);
}
function buzz()
{
// Do not buzz if not confident
if(hrConfidence < 85) { return; }
if(currentHeartRate > upperLimit)
{
Bangle.buzz(shortBuzzTimeInMs);
setTimeout(() => { Bangle.buzz(shortBuzzTimeInMs); }, shortBuzzTimeInMs * 2);
}
if(currentHeartRate < lowerLimit)
{
Bangle.buzz(longBuzzTimeInMs);
}
}
function onHrm(hrm){
if(currentHeartRate !== hrm.bpm){
currentHeartRate = hrm.bpm;
hrChanged = true;
}
if(hrConfidence !== hrm.confidence) {
hrConfidence = hrm.confidence;
confidenceChanged = true;
}
}
function setLimitSetterToLower() {
resetHighlightTimeout();
limitSetter = Setter.LOWER;
console.log("Limit setter is lower");
upperLimitChanged = true;
lowerLimitChanged = true;
renderUpperLimit();
renderLowerLimit();
}
function setLimitSetterToUpper() {
resetHighlightTimeout();
limitSetter = Setter.UPPER;
console.log("Limit setter is upper");
upperLimitChanged = true;
lowerLimitChanged = true;
renderLowerLimit();
renderUpperLimit();
}
function setLimitSetterToNone() {
limitSetter = Setter.NONE;
console.log("Limit setter is none");
upperLimitChanged = true;
lowerLimitChanged = true;
renderLowerLimit();
renderUpperLimit();
}
function incrementLimit(){
resetHighlightTimeout();
if (limitSetter === Setter.UPPER) {
upperLimit++;
renderUpperLimit();
console.log("Upper limit: " + upperLimit);
upperLimitChanged = true;
} else if(limitSetter === Setter.LOWER) {
lowerLimit++;
renderLowerLimit();
console.log("Lower limit: " + lowerLimit);
lowerLimitChanged = true;
}
}
function decrementLimit(){
resetHighlightTimeout();
if (limitSetter === Setter.UPPER) {
upperLimit--;
renderUpperLimit();
console.log("Upper limit: " + upperLimit);
upperLimitChanged = true;
} else if(limitSetter === Setter.LOWER) {
lowerLimit--;
renderLowerLimit();
console.log("Lower limit: " + lowerLimit);
lowerLimitChanged = true;
}
}
function resetHighlightTimeout() {
if (setterHighlightTimeout) {
clearTimeout(setterHighlightTimeout);
}
setterHighlightTimeout = setTimeout(setLimitSetterToNone, 2000);
}
// Show launcher when middle button pressed
function switchOffApp(){
Bangle.setHRMPower(0);
Bangle.showLauncher();
}
// special function to handle display switch on
Bangle.on('lcdPower', (on) => {
g.clear();
if (on) {
Bangle.drawWidgets();
renderButtonIcons();
// call your app function here
renderLowerLimitBackground();
renderUpperLimitBackground();
lowerLimitChanged = true;
upperLimitChanged = true;
drawTrainingHeartRate();
}
});
Bangle.setHRMPower(1);
Bangle.on('HRM', onHrm);
setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(decrementLimit, BTN3, {edge:"rising", debounce:50, repeat:true});
setWatch(setLimitSetterToLower, BTN4, {edge:"rising", debounce:50, repeat:true});
setWatch(setLimitSetterToUpper, BTN5, { edge: "rising", debounce: 50, repeat: true });
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
//drawTrainingHeartRate();
renderButtonIcons();
renderLowerLimitBackground();
renderUpperLimitBackground();
// refesh every sec
setInterval(drawTrainingHeartRate, 1000);

BIN
apps/wohrm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB