From 9aaa451c5e5411a863ad2e239543418820f351b6 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Tue, 14 Nov 2023 19:32:26 +0100 Subject: [PATCH] Add widhrzone Widget that displays the current out of five heart rate zones --- apps/widhrzone/metadata.json | 17 ++++++ apps/widhrzone/screenshot.png | Bin 0 -> 3230 bytes apps/widhrzone/settings.js | 26 +++++++++ apps/widhrzone/widget.js | 98 ++++++++++++++++++++++++++++++++++ apps/widhrzone/widget.png | Bin 0 -> 1777 bytes 5 files changed, 141 insertions(+) create mode 100644 apps/widhrzone/metadata.json create mode 100644 apps/widhrzone/screenshot.png create mode 100644 apps/widhrzone/settings.js create mode 100644 apps/widhrzone/widget.js create mode 100644 apps/widhrzone/widget.png diff --git a/apps/widhrzone/metadata.json b/apps/widhrzone/metadata.json new file mode 100644 index 000000000..9616e61fa --- /dev/null +++ b/apps/widhrzone/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "widhrzone", + "name": "Heart rate zone widget", + "shortName": "HRzone widget", + "version": "0.01", + "description": "Widget that displays the current out of five heart rate training zones 1. HEALTH (50-60% of max. HR, Recovery, grey), 2. FAT-B (60-70% of max. HR, burns fat, blue), 3. AROBIC (70-80% of max. HR, Endurance, green), 4. ANAROB (80-90% of max. HR, Speed, yellow), 5. MAX (90-100% of max. HR, red). Only visible when heart rate monitor is active and inside one of the zones. Requires to set the maximum heart rate in settings (if unsure set to 220-age).", + "icon": "widget.png", + "type": "widget", + "tags": "widget,health", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots" : [ { "url":"screenshot.png" } ], + "storage": [ + {"name":"widhrzone.wid.js","url":"widget.js"}, + {"name":"widhrzone.settings.js","url":"settings.js"} + ], + "data": [{"name":"widhrzone.json"}] +} diff --git a/apps/widhrzone/screenshot.png b/apps/widhrzone/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..29f7a2729696d4c9d3dc772b3662656a8498da65 GIT binary patch literal 3230 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 literal 0 HcmV?d00001 diff --git a/apps/widhrzone/settings.js b/apps/widhrzone/settings.js new file mode 100644 index 000000000..7165baea5 --- /dev/null +++ b/apps/widhrzone/settings.js @@ -0,0 +1,26 @@ +(function(back) { + const CONFIGFILE = "widhrzone.json"; + // Load settings + const settings = Object.assign({ + maxHrm: 200, + }, require("Storage").readJSON(CONFIGFILE,1) || {}); + + function writeSettings() { + require('Storage').writeJSON(CONFIGFILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "HRzone widget" }, + "< Back" : () => back(), + /*LANG*/'HR max': { + format: v => v, + value: settings.maxHrm, + min: 30, max: 220, + onchange: v => { + settings.maxHrm = v; + writeSettings(); + } + }, + }); +}); diff --git a/apps/widhrzone/widget.js b/apps/widhrzone/widget.js new file mode 100644 index 000000000..428c75c7f --- /dev/null +++ b/apps/widhrzone/widget.js @@ -0,0 +1,98 @@ +(() => { + const config = Object.assign({ + maxHrm: 200, + }, require("Storage").readJSON("widhrzone.json",1) || {}); + + require("FontTeletext5x9Ascii").add(Graphics); + + const calczone = (bpm) => { + if (bpm <= config.maxHrm*0.5) { + return 0; + } else if (bpm <= config.maxHrm*0.60) { + return 1; + } else if (bpm <= config.maxHrm*0.70) { + return 2; + } else if (bpm <= config.maxHrm*0.80) { + return 3; + } else if (bpm <= config.maxHrm*0.90) { + return 4; + } else { // > 0.9 + return 5; + } + }; + + const zoneToText = (zone) => { + switch(zone) { + case 1: + return "HEALTH"; + case 2: + return "FAT-B"; + case 3: + return "AROBIC"; + case 4: + return "ANAROB"; + default: + return "MAX"; + } + }; + + const zoneToColor = (zone) => { + switch(zone) { + case 1: + return ["#888888", "#000000"]; // bg, fg + case 2: + return ["#0000ff", "#ffffff"]; + case 3: + return ["#00ff00", "#000000"]; + case 4: + return ["#ffff00", "#000000"]; + default: + return ["#ff0000", "#ffffff"]; + } + }; + + Bangle.on('HRM', function(hrm) { + if (hrm.confidence >= 90) { + const zone = calczone(hrm.bpm); + if (WIDGETS.widhrzone.zone != zone) { + WIDGETS.widhrzone.zone = zone; + WIDGETS.widhrzone.draw(); + } + } + }); + + WIDGETS.widhrzone = { + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 0, // default hide, only show for valid hrm + draw: function() { + if (this.zone > 0 && Bangle.isHRMOn()) { + if (this.width === 0) { + this.width = 6*6; + // width changed, re-layout + Bangle.drawWidgets(); + } + // https://icons8.com/icon/set/heart/color 12x12, compression, transparency, inverted + g.reset(); + // background + const colors = zoneToColor(this.zone); + g.setColor(colors[0]).fillRect(this.x, this.y, this.x+this.width, this.y+24); + // icon + const icon = require("heatshrink").decompress(atob("hkMgIFCv/m/1D8EGgFhjkXwHwBwQ")); + g.setColor(colors[1]).setBgColor(colors[0]).drawImage(icon, this.x, this.y); + // number upper right + g.setBgColor(colors[0]).setColor(colors[1]); + g.setFont("4x6:2x2").drawString(this.zone, this.x+18, this.y); + // text bottom + g.setFont("Teletext5x9Ascii:1x1").drawString(zoneToText(this.zone), this.x, this.y+12); + g.setColor(g.theme.fg).setBgColor(g.theme.bg); // restore + } else { + if (this.width !== 0) { + this.width = 0; + // width changed, re-layout + Bangle.drawWidgets(); + } + } + }, + zone: 0 + }; +})(); diff --git a/apps/widhrzone/widget.png b/apps/widhrzone/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc07d6474a68433d2610723b1dcf85b80fde104 GIT binary patch literal 1777 zcmVgEM_r_S=>n|FG`Wd%7r%OY9)LN zxECk`OpxoK`hX**HvT%Nz2l-Me@(&rw9Q%+j{@bu=Ku{{1}=)Q&$MJu@uBX4+afSp zS6$|qvIThFO*ktw!kdOAKh6|5(pb4rWBw9httWr5(-8Bg#o?1zawag+5L^TNS)gQ^ zKvOX!$c8{$_nV2r;rd{!QtX(CKjj$D`j2$HovzbJCme35-Yl?t2EhOV3iZZtef19~ z{RLk^v1_LO#BlyL)KK+uy3Ujl7-rFnPDksi%fytkUcQtH%)_jz!44nDrZyCP269d}gFj{XzUn`Gd@v?R`pS;w{M_p> zo69OOmn@r3EkOQ~2M9RM#Fj)soIs?pav`Fg^Xkbguf$wZp2yt0LBaaby6Upndz^qR z#u}zy+M-?*W_bmwbUwy{vg!5n3sak{j=jVQDAX#i9*i&Dk1Cz-win-lQBd;DcrKGd zzU0O9^v(?}5B?bY5aPatq->foMOJ5E5rNvG`l&Qr<8bLL<^un4q9 zhCa(&2g<;BU{T(ZPK3DI3?O+Ky*yRXC|gKD>kAlPymwNzWI0$q^!X3bXWnyVY3jgS zyp)2qo)@)T5zjTT20oaoN z^vBwhQ%Gb_l9Im!>yZWke6=f4CGP*oEPoIH-PeQeKjq4%)`4ka25Vf~rk1=DNX#`( zU|c!-6b#4Stw)>Py$(P@Ay(~5jJxl_xN8A2bQ9;od7LW)$fws+>cCw3AZl)@>!-Fc zPCgyXiXg?m*+ucT*D#k?dC7m&9xMDuJkMBTeyI1RePCa|`SqG$qP87AfK|T=Uvmp+ zx6XCndmz#F+0$4xwHS-baR%L~X{~r9YHY{JPwan-g71DGU(1up=?L*1S>cA48QrvQ zn*7!F(qqRyjlCu9h3vsg&k-4wxbJIM8T->V#wQZ_Ag@6J9^$ZDX zt&9v~9}PtVrUubxqswVU5UZjJM6f%LxN7u#w4@g6JSw7x+q9>y zlD5uI3~`Ruf;M?x>IsLCR1l(!ld#n`-i_jnSBmcSPCY?auem8_fBDp3%?kM$S zri+4%$HwAtPnQx|3)le8O7W7A?vx4oP?;i#ti#~`JvvNjc@C=Y`DI99ZI$X{u#O$ z1NEblw$AK(h%C6>>pq4XS1d*J8^DqrP0uMYp7Dn}QvZxOAg3Bghmz3A)BYQ}LKIs_ zIICuX2(rU(-m1LaW3VWoxoSp22-5ZsP)