From 8a84dc31f739ab0e68bfbe117a63cc8b93755ab1 Mon Sep 17 00:00:00 2001 From: Anthony Zhang Date: Mon, 18 Apr 2022 04:32:13 -0400 Subject: [PATCH] Add HeartZone app --- apps/heartzone/ChangeLog | 1 + apps/heartzone/README.md | 35 ++++++++++ apps/heartzone/app-icon.js | 2 + apps/heartzone/app.js | 87 +++++++++++++++++++++++++ apps/heartzone/icon.png | Bin 0 -> 4926 bytes apps/heartzone/metadata.json | 20 ++++++ apps/heartzone/screenshots/paused.png | Bin 0 -> 3325 bytes apps/heartzone/screenshots/running.png | Bin 0 -> 3510 bytes apps/heartzone/screenshots/start.png | Bin 0 -> 3405 bytes apps/heartzone/settings.js | 27 ++++++++ 10 files changed, 172 insertions(+) create mode 100644 apps/heartzone/ChangeLog create mode 100644 apps/heartzone/README.md create mode 100644 apps/heartzone/app-icon.js create mode 100644 apps/heartzone/app.js create mode 100644 apps/heartzone/icon.png create mode 100644 apps/heartzone/metadata.json create mode 100644 apps/heartzone/screenshots/paused.png create mode 100644 apps/heartzone/screenshots/running.png create mode 100644 apps/heartzone/screenshots/start.png create mode 100644 apps/heartzone/settings.js diff --git a/apps/heartzone/ChangeLog b/apps/heartzone/ChangeLog new file mode 100644 index 000000000..cc2219306 --- /dev/null +++ b/apps/heartzone/ChangeLog @@ -0,0 +1 @@ +1.0: Initial release. diff --git a/apps/heartzone/README.md b/apps/heartzone/README.md new file mode 100644 index 000000000..4de60355d --- /dev/null +++ b/apps/heartzone/README.md @@ -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: + +![Start screen](screenshots/start.png) + +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: + +![Screen while we are monitoring](screenshots/running.png) + +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): + +![Screen while we are paused](screenshots/paused.png) + +When you're done, simply press the side button to exit the app. + +## Creator + +[Uberi](https://github.com/Uberi) diff --git a/apps/heartzone/app-icon.js b/apps/heartzone/app-icon.js new file mode 100644 index 000000000..07c8be0b8 --- /dev/null +++ b/apps/heartzone/app-icon.js @@ -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=")) \ No newline at end of file diff --git a/apps/heartzone/app.js b/apps/heartzone/app.js new file mode 100644 index 000000000..9ec3f005d --- /dev/null +++ b/apps/heartzone/app.js @@ -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 + "=" + 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"); diff --git a/apps/heartzone/icon.png b/apps/heartzone/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60ece4b32311242bf1e9d3a1f165ce94742d1bc5 GIT binary patch literal 4926 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 literal 0 HcmV?d00001 diff --git a/apps/heartzone/metadata.json b/apps/heartzone/metadata.json new file mode 100644 index 000000000..04297d76c --- /dev/null +++ b/apps/heartzone/metadata.json @@ -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"}] +} diff --git a/apps/heartzone/screenshots/paused.png b/apps/heartzone/screenshots/paused.png new file mode 100644 index 0000000000000000000000000000000000000000..5f89ed07601e1e47fe20c6b54c61604d961e19d3 GIT binary patch literal 3325 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 literal 0 HcmV?d00001 diff --git a/apps/heartzone/screenshots/start.png b/apps/heartzone/screenshots/start.png new file mode 100644 index 0000000000000000000000000000000000000000..195957fa0448dde63a66c3231b97a85c8a950b79 GIT binary patch literal 3405 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 Dr7cj6 literal 0 HcmV?d00001 diff --git a/apps/heartzone/settings.js b/apps/heartzone/settings.js new file mode 100644 index 000000000..64030b0a3 --- /dev/null +++ b/apps/heartzone/settings.js @@ -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; }}, + }); +})