mirror of https://github.com/espruino/BangleApps
Add HeartZone app
parent
dc273a7a12
commit
8a84dc31f7
|
@ -0,0 +1 @@
|
|||
1.0: Initial release.
|
|
@ -0,0 +1,35 @@
|
|||
# HeartZone
|
||||
|
||||
HeartZone continuously monitors your heart rate. If your heart rate is outside of your configured limits, you get a configurable buzz.
|
||||
|
||||
Inspired by [Workout HRM](https://github.com/espruino/BangleApps/tree/master/apps/wohrm), but I wanted the following features:
|
||||
|
||||
* Larger text, more contrast, and color-coding for better readability while exercising.
|
||||
* Configurable buzz interval, instead of at every heart rate reading (which was too distracting).
|
||||
* Pause for a rest and resume afterwards without having to restart the heart rate sensor (which takes several seconds each time to stabilize).
|
||||
* Configure the minimum heart rate confidence threshold (bad readings cause buzzes that have to be ignored).
|
||||
|
||||
However, compared to Workout HRM, HeartZone doesn't support:
|
||||
|
||||
* In-app configuration of the heart rate thresholds - you can only do it in the Settings app.
|
||||
* Bangle.js 1 - this only supports Bangle.js 2.
|
||||
|
||||
## Usage
|
||||
|
||||
When you first start the app, it will begin displaying your heart rate after a few seconds. Until the heart rate confidence is above your configured minimum confidence, the background will be colored red:
|
||||
|
||||

|
||||
|
||||
After the heart rate confidence is at an acceptable level, the background will be colored white, and you will receive buzzes on your wrist while your heart rate is out of the configured range. By default, the BPM-too-low buzz is 200ms, while the BPM-too-high buzz is 1000ms:
|
||||
|
||||

|
||||
|
||||
If you're taking a break, swipe down to turn off the buzzes while continuing to measure and display your heart rate (swipe up again to end your break):
|
||||
|
||||

|
||||
|
||||
When you're done, simply press the side button to exit the app.
|
||||
|
||||
## Creator
|
||||
|
||||
[Uberi](https://github.com/Uberi)
|
|
@ -0,0 +1,2 @@
|
|||
// generated by http://www.espruino.com/Image+Converter from icon.png
|
||||
require("heatshrink").decompress(atob("mEwwkBiIA/AHqFCAxQWJ5gABCIQGGABEQB4QABgIGGC5MMCAnAAwwuOABAwIC64/FABBIIC68ADBnAVJEP+AXLBoJ2H/4XN/54GBAIXOGAouBBAMAABQXBGAoHCAB4wDFwQARGAYvWL7CPDbBXAR46/DiAXJgK/Id4URGBHABobwHEAIwIBQQuHAAcYGA3AwIUKC4eAC4sIC5+IGAnAxAXQkAXDgQXRkQwC4EiC6QwCgQXTl0M4HiC6nghwXV93uC9MRC44WOGAIXFFx4ABC4oWQiMSC4chC6MRC4YWSiMeC4PhC6cRC4IWUGAIuVAH4AVA="))
|
|
@ -0,0 +1,87 @@
|
|||
// clear screen and draw widgets
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var statusRect = {x: Bangle.appRect.x, y: Bangle.appRect.y, w: Bangle.appRect.w, h: 32};
|
||||
var settingsRect = {x: Bangle.appRect.x, y: Bangle.appRect.y2 - 16, w: Bangle.appRect.w, h: 16};
|
||||
var hrmRect = {x: Bangle.appRect.x, y: statusRect.y + statusRect.h, w: Bangle.appRect.w, h: Bangle.appRect.h - statusRect.h - settingsRect.h};
|
||||
|
||||
var isPaused = false;
|
||||
var settings = Object.assign({
|
||||
minBpm: 120,
|
||||
maxBpm: 160,
|
||||
minConfidence: 60,
|
||||
minBuzzIntervalSeconds: 5,
|
||||
tooLowBuzzDurationMillis: 200,
|
||||
tooHighBuzzDurationMillis: 1000,
|
||||
}, require('Storage').readJSON("heartzone.settings.json", true) || {});
|
||||
|
||||
// draw current settings at the bottom
|
||||
g.setFont6x15(1).setFontAlign(0, -1, 0);
|
||||
g.drawString(settings.minBpm + "<BPM<" + settings.maxBpm + ", >=" + settings.minConfidence + "% conf.", settingsRect.x + (settingsRect.w / 2), settingsRect.y + 4);
|
||||
|
||||
function drawStatus(status) { // draw status bar at the top
|
||||
g.setBgColor(g.theme.bg).setColor(g.theme.fg);
|
||||
g.clearRect(statusRect);
|
||||
|
||||
g.setFontVector(statusRect.h - 4).setFontAlign(0, -1, 0);
|
||||
g.drawString(status, statusRect.x + (statusRect.w / 2), statusRect.y + 2);
|
||||
}
|
||||
|
||||
function drawHRM(hrmInfo) { // draw HRM info display
|
||||
g.setBgColor(hrmInfo.confidence > settings.minConfidence ? '#fff' : '#f00').setColor(hrmInfo.confidence > settings.minConfidence ? '#000' : '#fff');
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.clearRect(hrmRect);
|
||||
|
||||
var px = hrmRect.x + 10, py = hrmRect.y + 10;
|
||||
g.setFontVector((hrmRect.h / 2) - 20);
|
||||
g.drawString(hrmInfo.bpm, px, py);
|
||||
g.setFontVector(16);
|
||||
g.drawString('BPM', px + g.stringWidth(hrmInfo.bpm.toString()) + 32, py);
|
||||
py += hrmRect.h / 2;
|
||||
|
||||
g.setFontVector((hrmRect.h / 2) - 20);
|
||||
g.drawString(hrmInfo.confidence, px, py);
|
||||
g.setFontVector(16);
|
||||
g.drawString('% conf.', px + g.stringWidth(hrmInfo.confidence.toString()) + 32, py);
|
||||
}
|
||||
|
||||
drawHRM({bpm: '?', confidence: '?'});
|
||||
drawStatus('RUNNING');
|
||||
|
||||
var lastBuzz = getTime();
|
||||
Bangle.on('HRM', function(hrmInfo) {
|
||||
if (!isPaused) {
|
||||
var currentTime;
|
||||
if (hrmInfo.confidence > settings.minConfidence) {
|
||||
if (hrmInfo.bpm < settings.minBpm) {
|
||||
currentTime = getTime();
|
||||
if (currentTime - lastBuzz > settings.minBuzzIntervalSeconds) {
|
||||
lastBuzz = currentTime;
|
||||
Bangle.buzz(settings.tooLowBuzzDurationMillis);
|
||||
}
|
||||
} else if (hrmInfo.bpm > settings.maxBpm) {
|
||||
currentTime = getTime();
|
||||
if (currentTime - lastBuzz > minBuzzIntervalSeconds) {
|
||||
lastBuzz = currentTime;
|
||||
Bangle.buzz(settings.tooHighBuzzDurationMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawHRM(hrmInfo);
|
||||
});
|
||||
|
||||
Bangle.setUI('updown', function(action) {
|
||||
if (action == -1) { // up
|
||||
isPaused = false;
|
||||
drawStatus("RUNNING");
|
||||
} else if (action == 1) { // down
|
||||
isPaused = true;
|
||||
drawStatus("PAUSED");
|
||||
}
|
||||
});
|
||||
setWatch(function() { Bangle.setHRMPower(false, "heartzone"); load(); }, BTN1);
|
||||
|
||||
Bangle.setHRMPower(true, "heartzone");
|
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "heartzone",
|
||||
"name": "HeartZone",
|
||||
"version": "1.0",
|
||||
"description": "Exercise app for keeping your heart rate in the aerobic zone. Buzzes the watch at configurable intervals when your heart rate is outside of configured limits.",
|
||||
"screenshots": [
|
||||
{"url": "screenshots/start.png"},
|
||||
{"url": "screenshots/running.png"},
|
||||
{"url": "screenshots/paused.png"}
|
||||
],
|
||||
"icon": "icon.png",
|
||||
"tags": "health",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"heartzone.app.js","url":"app.js"},
|
||||
{"name":"heartzone.settings.js","url":"settings.js"},
|
||||
{"name":"heartzone.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"heartzone.settings.json"}]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,27 @@
|
|||
(function(back) {
|
||||
var FILE = "heartzone.settings.json";
|
||||
var settings = Object.assign({
|
||||
minBpm: 120,
|
||||
maxBpm: 160,
|
||||
minConfidence: 60,
|
||||
minBuzzIntervalSeconds: 5,
|
||||
tooLowBuzzDurationMillis: 200,
|
||||
tooHighBuzzDurationMillis: 1000,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "HeartZone" },
|
||||
"< Save & Return" : () => { writeSettings(); back(); },
|
||||
'Min BPM': {value: 0 | settings.minBpm, min: 80, max: 200, step: 10, onchange: v => { settings.minBpm = v; }},
|
||||
'Max BPM': {value: 0 | settings.maxBpm, min: 80, max: 200, step: 10, onchange: v => { settings.maxBpm = v; }},
|
||||
'Min % conf.': {value: 0 | settings.minConfidence, min: 30, max: 100, step: 5, onchange: v => { settings.minConfidence = v; }},
|
||||
'Min buzz int. (sec)': {value: 0 | settings.minBuzzIntervalSeconds, min: 1, max: 30, onchange: v => { settings.minBuzzIntervalSeconds = v; }},
|
||||
'BPM too low buzz (ms)': {value: 0 | settings.tooLowBuzzDurationMillis, min: 0, max: 3000, step: 100, onchange: v => { settings.tooLowBuzzDurationMillis = v; }},
|
||||
'BPM too high buzz (ms)': {value: 0 | settings.tooHighBuzzDurationMillis, min: 0, max: 3000, step: 100, onchange: v => { settings.tooHighBuzzDurationMillis = v; }},
|
||||
});
|
||||
})
|
Loading…
Reference in New Issue