From 656eeda4d1eb82b4cf18624a2f257cff6aed017b Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 21 Apr 2024 16:48:56 +0200 Subject: [PATCH 01/11] runplus - Refactor karvonen to be a library --- apps/runplus/app.js | 40 ++-- apps/runplus/karvonen.js | 410 +++++++++++++++++++++------------------ 2 files changed, 241 insertions(+), 209 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 92428d2dc..9bae8fcfd 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -1,10 +1,7 @@ -// Use widget utils to show/hide widgets -let wu = require("widget_utils"); - let runInterval; let karvonenActive = false; // Run interface wrapped in a function -let ExStats = require("exstats"); +const ExStats = require("exstats"); let B2 = process.env.HWVERSION===2; let Layout = require("Layout"); let locale = require("locale"); @@ -13,11 +10,10 @@ let fontValue = B2 ? "6x15:2" : "6x8:3"; let headingCol = "#888"; let fixCount = 0; let isMenuDisplayed = false; +const wu = require("widget_utils"); g.reset().clear(); Bangle.loadWidgets(); -Bangle.drawWidgets(); -wu.show(); // --------------------------- let settings = Object.assign({ @@ -139,7 +135,7 @@ lc.push({ type:"h", filly:1, c:[ // Now calculate the layout let layout = new Layout( { type:"v", c: lc -},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}), id:"button"}]}); +},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {run();} onStartStop();}), id:"button"}]}); delete lc; setStatus(exs.state.active); layout.render(); @@ -169,9 +165,13 @@ Bangle.on("GPS", function(fix) { } }); -// run() function used to switch between traditional run UI and karvonen UI +// run() function used to start updating traditional run ui function run() { + require("runplus_karvonen").stop(); + karvonenActive = false; wu.show(); + Bangle.drawWidgets(); + g.reset().clearRect(Bangle.appRect); layout.lazy = false; layout.render(); layout.lazy = true; @@ -179,7 +179,7 @@ function run() { if (!runInterval){ runInterval = setInterval(function() { layout.clock.label = locale.time(new Date(),1); - if (!isMenuDisplayed && !karvonenActive) layout.render(); + if (!isMenuDisplayed) layout.render(); }, 1000); } } @@ -189,25 +189,23 @@ run(); // Karvonen /////////////////////////////////////////////// -function stopRunUI() { +function karvonen(){ // stop updating and drawing the traditional run app UI - clearInterval(runInterval); + if (runInterval) clearInterval(runInterval); runInterval = undefined; + g.reset().clearRect(Bangle.appRect); + require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); karvonenActive = true; } -function stopKarvonenUI() { - g.reset().clear(); - clearInterval(karvonenInterval); - karvonenInterval = undefined; - karvonenActive = false; -} - -let karvonenInterval; // Define the function to go back and forth between the different UI's function swipeHandler(LR,_) { - if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();} - if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);} + if (!isMenuDisplayed){ + if (LR==-1 && karvonenActive) + run(); + if (LR==1 && !karvonenActive) + karvonen(); + } } // Listen for swipes with the swipeHandler Bangle.on("swipe", swipeHandler); diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index 42c4734a8..e835a866b 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -1,19 +1,15 @@ -(function karvonen(hrmSettings, exsHrmStats) { - //This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+ - //The calculation of the Heart Rate Zones is based on the Karvonen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab. - //Other methods are even more approximative. - let wu = require("widget_utils"); - wu.hide(); - let R = Bangle.appRect; +//This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+ +//The calculation of the Heart Rate Zones is based on the Karvonen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab. +//Other methods are even more approximative. +let wu = require("widget_utils"); +let R; - - g.reset().clearRect(R).setFontAlign(0,0,0); - - const x = "x"; const y = "y"; - function Rdiv(axis, divisor) { // Used when placing things on the screen - return axis=="x" ? (R.x + (R.w-1)/divisor):(R.y + (R.h-1)/divisor); - } - let linePoints = { //Not lists of points, but used to update points in the drawArrows function. +const x = "x"; const y = "y"; +function Rdiv(axis, divisor) { // Used when placing things on the screen + return axis=="x" ? (R.x + (R.w-1)/divisor):(R.y + (R.h-1)/divisor); +} + +let linePoints = { //Not lists of points, but used to update points in the drawArrows function. x: [ 175/40, 2, @@ -24,190 +20,228 @@ 175/52, 175/110, 175/122, - ], - - }; - - function drawArrows() { - g.setColor(g.theme.fg); - // Upper - g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[0]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1])); - g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[0])); - // Lower - g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[2]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3])); - g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[2])); - } - - //To calculate Heart rate zones, we need to know the heart rate reserve (HRR) - // HRR = maximum HR - Minimum HR. minhr is minimum hr, maxhr is maximum hr. - //get the hrr (heart rate reserve). - // I put random data here, but this has to come as a menu in the settings section so that users can change it. - let minhr = hrmSettings.min; - let maxhr = hrmSettings.max; - - function calculatehrr(minhr, maxhr) { - return maxhr - minhr;} - - //test input for hrr (it works). - let hrr = calculatehrr(minhr, maxhr); - console.log(hrr); - - //Test input to verify the zones work. The following value for "hr" has to be deleted and replaced with the Heart Rate Monitor input. - let hr = exsHrmStats.getValue(); - // These letiables display next and previous HR zone. - //get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonen method - //60-70% of HRR+minHR = zone2. //70-80% of HRR+minHR = zone3. //80-90% of HRR+minHR = zone4. //90-99% of HRR+minHR = zone5. //=>99% of HRR+minHR = serious risk of heart attack - let minzone2 = hrr * 0.6 + minhr; - let maxzone2 = hrr * 0.7 + minhr; - let maxzone3 = hrr * 0.8 + minhr; - let maxzone4 = hrr * 0.9 + minhr; - let maxzone5 = hrr * 0.99 + minhr; + ] +}; - // HR data: large, readable, in the middle of the screen - function drawHR() { - g.setFontAlign(-1,0,0); - g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25); - g.setColor(g.theme.fg); - g.setFont("Vector",50); - g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); + +function drawArrows() { + g.setColor(g.theme.fg); + // Upper + g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[0]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1])); + g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[0])); + // Lower + g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[2]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3])); + g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[2])); +} + +//To calculate Heart rate zones, we need to know the heart rate reserve (HRR) +// HRR = maximum HR - Minimum HR. minhr is minimum hr, maxhr is maximum hr. +//get the hrr (heart rate reserve). +// I put random data here, but this has to come as a menu in the settings section so that users can change it. +let minhr; +let maxhr; +let hrr; + +//Test input to verify the zones work. The following value for "hr" has to be deleted and replaced with the Heart Rate Monitor input. +let hr; +// These letiables display next and previous HR zone. +//get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonen method +//60-70% of HRR+minHR = zone2. //70-80% of HRR+minHR = zone3. //80-90% of HRR+minHR = zone4. //90-99% of HRR+minHR = zone5. //=>99% of HRR+minHR = serious risk of heart attack +let minzone2; +let maxzone2; +let maxzone3; +let maxzone4; +let maxzone5; + +function calculatehrr(minhr, maxhr) { + hrr = maxhr - minhr; + minzone2 = hrr * 0.6 + minhr; + maxzone2 = hrr * 0.7 + minhr; + maxzone3 = hrr * 0.8 + minhr; + maxzone4 = hrr * 0.9 + minhr; + maxzone5 = hrr * 0.99 + minhr; +} +// HR data: large, readable, in the middle of the screen +function drawHR() { + g.setFontAlign(-1,0,0); + g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25); + g.setColor(g.theme.fg); + g.setFont("Vector",50); + g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); +} + +function drawWaitHR() { + g.setColor(g.theme.fg); + // Waiting for HRM + g.setFontAlign(0,0,0); + g.setFont("Vector",50); + g.drawString("--", Rdiv(x,2)+4, Rdiv(y,2)+4); + + // Waiting for current Zone + g.setFont("Vector",24); + g.drawString("Z-", Rdiv(x,4.3)-3, Rdiv(y,2)+2); + + // waiting for upper and lower limit of current zone + g.setFont("Vector",20); + g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2)); + g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7)); +} + +//These functions call arcs to show different HR zones. + +//To shorten the code, I'll reference some letiables and reuse them. +let centreX; +let centreY; +let minRadius; +let maxRadius; + +//draw background image (dithered green zones)(I should draw different zones in different dithered colors) +const HRzones= require("graphics_utils"); +let minRadiusz; +let startAngle; +let endAngle; + +function drawBgArc() { + g.setColor(g.theme.dark==false?0xC618:"#002200"); + HRzones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); +} + +const zones = require("graphics_utils"); +//####### A function to simplify a bit the code ###### +function simplify (sA, eA, Z, currentZone, lastZone) { + let startAngle = zones.degreesToRadians(sA); + let endAngle = zones.degreesToRadians(eA); + if (currentZone == lastZone) zones.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle, endAngle); + else zones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); + g.setFont("Vector",24); + g.clearRect(Rdiv(x,4.3)-12, Rdiv(y,2)+2-12,Rdiv(x,4.3)+12, Rdiv(y,2)+2+12); + g.setFontAlign(0,0,0); + g.drawString(Z, Rdiv(x,4.3), Rdiv(y,2)+2); +} + +function zoning (max, min) { // draw values of upper and lower limit of current zone + g.setFont("Vector",20); + g.setColor(g.theme.fg); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/2)-10,Rdiv(x,2)+20*2, Rdiv(y,9/2)+10); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/7)-10,Rdiv(x,2)+20*2, Rdiv(y,9/7)+10); + g.setFontAlign(0,0,0); + g.drawString(max, Rdiv(x,2), Rdiv(y,9/2)); + g.drawString(min, Rdiv(x,2), Rdiv(y,9/7)); +} + +function clearCurrentZone() { // Clears the extension of the current zone by painting the extension area in background color + g.setColor(g.theme.bg); + HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); +} + +function getZone(zone) { + drawBgArc(); + clearCurrentZone(); + if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} + if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} + if (zone >= 2) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-20, 1.5, "Z2", 2, zone);} + if (zone >= 3) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(3, 24, "Z2", 3, zone);} + if (zone >= 4) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(25.5, 46.5, "Z3", 4, zone);} + if (zone >= 5) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(48, 69, "Z3", 5, zone);} + if (zone >= 6) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(70.5, 91.5, "Z3", 6, zone);} + if (zone >= 7) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(93, 114.5, "Z4", 7, zone);} + if (zone >= 8) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(116, 137.5, "Z4", 8, zone);} + if (zone >= 9) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(139, 160, "Z4", 9, zone);} + if (zone >= 10) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(161.5, 182.5, "Z5", 10, zone);} + if (zone >= 11) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(184, 205, "Z5", 11, zone);} + if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} } - function drawWaitHR() { - g.setColor(g.theme.fg); - // Waiting for HRM - g.setFontAlign(0,0,0); - g.setFont("Vector",50); - g.drawString("--", Rdiv(x,2)+4, Rdiv(y,2)+4); +function getZoneAlert() { + const HRzonemax = require("graphics_utils"); + let minRadius = 0.40 * R.h; + let startAngle1 = HRzonemax.degreesToRadians(-90); + let endAngle1 = HRzonemax.degreesToRadians(270); + g.setFont("Vector",38);g.setColor("#ff0000"); + HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1); + g.setFontAlign(0,0).drawString("ALERT", centreX, centreY); +} - // Waiting for current Zone - g.setFont("Vector",24); - g.drawString("Z-", Rdiv(x,4.3)-3, Rdiv(y,2)+2); +//Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. +let subZoneLast; +function drawZones() { + if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone alert. + if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 + else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a + else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b + else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c + else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a + else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b + else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c + else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a + else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b + else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c + else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a + else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b + else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c + else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert +} - // waiting for upper and lower limit of current zone - g.setFont("Vector",20); - g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2)); - g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7)); - } - - //These functions call arcs to show different HR zones. - - //To shorten the code, I'll reference some letiables and reuse them. - let centreX = R.x + 0.5 * R.w; - let centreY = R.y + 0.5 * R.h; - let minRadius = 0.38 * R.h; - let maxRadius = 0.50 * R.h; - - //draw background image (dithered green zones)(I should draw different zones in different dithered colors) - const HRzones= require("graphics_utils"); - let minRadiusz = 0.44 * R.h; - let startAngle = HRzones.degreesToRadians(-88.5); - let endAngle = HRzones.degreesToRadians(268.5); +let karvonenInterval; +let hrmstat; - function drawBgArc() { - g.setColor(g.theme.dark==false?0xC618:"#002200"); - HRzones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); - } - - const zones = require("graphics_utils"); - //####### A function to simplify a bit the code ###### - function simplify (sA, eA, Z, currentZone, lastZone) { - let startAngle = zones.degreesToRadians(sA); - let endAngle = zones.degreesToRadians(eA); - if (currentZone == lastZone) zones.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle, endAngle); - else zones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); - g.setFont("Vector",24); - g.clearRect(Rdiv(x,4.3)-12, Rdiv(y,2)+2-12,Rdiv(x,4.3)+12, Rdiv(y,2)+2+12); - g.setFontAlign(0,0,0); - g.drawString(Z, Rdiv(x,4.3), Rdiv(y,2)+2); - } +function init(hrmSettings, exsHrmStats) { + R = Bangle.appRect; + hrmstat = exsHrmStats; + hr = hrmstat.getValue(); + minhr = hrmSettings.min; + maxhr = hrmSettings.max; + calculatehrr(minhr, maxhr); - function zoning (max, min) { // draw values of upper and lower limit of current zone - g.setFont("Vector",20); - g.setColor(g.theme.fg); - g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/2)-10,Rdiv(x,2)+20*2, Rdiv(y,9/2)+10); - g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/7)-10,Rdiv(x,2)+20*2, Rdiv(y,9/7)+10); - g.setFontAlign(0,0,0); - g.drawString(max, Rdiv(x,2), Rdiv(y,9/2)); - g.drawString(min, Rdiv(x,2), Rdiv(y,9/7)); - } + centreX = R.x + 0.5 * R.w; + centreY = R.y + 0.5 * R.h; + minRadius = 0.38 * R.h; + maxRadius = 0.50 * R.h; - function clearCurrentZone() { // Clears the extension of the current zone by painting the extension area in background color - g.setColor(g.theme.bg); - HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); - } - - function getZone(zone) { - drawBgArc(); - clearCurrentZone(); - if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} - if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} - if (zone >= 2) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-20, 1.5, "Z2", 2, zone);} - if (zone >= 3) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(3, 24, "Z2", 3, zone);} - if (zone >= 4) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(25.5, 46.5, "Z3", 4, zone);} - if (zone >= 5) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(48, 69, "Z3", 5, zone);} - if (zone >= 6) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(70.5, 91.5, "Z3", 6, zone);} - if (zone >= 7) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(93, 114.5, "Z4", 7, zone);} - if (zone >= 8) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(116, 137.5, "Z4", 8, zone);} - if (zone >= 9) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(139, 160, "Z4", 9, zone);} - if (zone >= 10) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(161.5, 182.5, "Z5", 10, zone);} - if (zone >= 11) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(184, 205, "Z5", 11, zone);} - if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} - } + minRadiusz = 0.44 * R.h; + startAngle = HRzones.degreesToRadians(-88.5); + endAngle = HRzones.degreesToRadians(268.5); +} - function getZoneAlert() { - const HRzonemax = require("graphics_utils"); - let minRadius = 0.40 * R.h; - let startAngle1 = HRzonemax.degreesToRadians(-90); - let endAngle1 = HRzonemax.degreesToRadians(270); - g.setFont("Vector",38);g.setColor("#ff0000"); - HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1); - g.drawString("ALERT", 26,66); - } - - //Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. - let subZoneLast; - function drawZones() { - if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone alert. - if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 - else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a - else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b - else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c - else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a - else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b - else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c - else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a - else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b - else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c - else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a - else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b - else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c - else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert - } - - function initDraw() { - drawArrows(); - if (hr!=0) updateUI(true); else {drawWaitHR(); drawBgArc();} - //drawZones(); - } +function start(hrmSettings, exsHrmStats) { + wu.hide(); + init(hrmSettings, exsHrmStats); - let hrLast; - //h = 0; // Used to force hr update via web ui console field to trigger draws, together with `if (h!=0) hr = h;` below. - function updateUI(resetHrLast) { // Update UI, only draw if warranted by change in HR. - hrLast = resetHrLast?0:hr; // Handles correct updating on init depending on if we've got HRM readings yet or not. - hr = exsHrmStats.getValue(); - //if (h!=0) hr = h; - if (hr!=hrLast) { - drawHR(); - drawZones(); - } //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. - } - - initDraw(); + g.reset().clearRect(R).setFontAlign(0,0,0); - // check for updates every second. + //draw every second + setTimeout(updateUI,0,true); karvonenInterval = setInterval(function() { - if (!isMenuDisplayed && karvonenActive) updateUI(); + updateUI(false); }, 1000); +} - return karvonenInterval; -}) +let hrLast; +//h = 0; // Used to force hr update via web ui console field to trigger draws, together with `if (h!=0) hr = h;` below. +function updateUI(resetHrLast) { // Update UI, only draw if warranted by change in HR. + if (resetHrLast){ + hrLast = 0; // Handles correct updating on init depending on if we've got HRM readings yet or not. + subZoneLast = undefined; + } + hr = hrmstat.getValue(); + //if (h!=0) hr = h; + drawArrows(); + if (hr && hr!=hrLast) { + drawHR(); + drawZones(); + } else { + drawBgArc(); + drawWaitHR(); + } + + //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. +} + +function stop(){ + if (karvonenInterval) clearInterval(karvonenInterval); + karvonenInterval = undefined; + wu.show(); +} + +exports.start = start; +exports.stop = stop; \ No newline at end of file From 12f55406aabad6b0811234c05dba3468db2599d1 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 21 Apr 2024 17:00:37 +0200 Subject: [PATCH 02/11] runplus - Add lock indicator to karvonen screen --- apps/runplus/karvonen.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index e835a866b..b9c774520 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -4,6 +4,8 @@ let wu = require("widget_utils"); let R; +const ICON_LOCK = atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="); + const x = "x"; const y = "y"; function Rdiv(axis, divisor) { // Used when placing things on the screen return axis=="x" ? (R.x + (R.w-1)/divisor):(R.y + (R.h-1)/divisor); @@ -185,6 +187,13 @@ function drawZones() { let karvonenInterval; let hrmstat; +function drawIndicator() { + if (Bangle.isLocked()) + g.setColor(g.theme.fg).drawImage(ICON_LOCK, 6, 8); + else + g.setColor(g.theme.bg).drawImage(ICON_LOCK, 6, 8); +} + function init(hrmSettings, exsHrmStats) { R = Bangle.appRect; hrmstat = exsHrmStats; @@ -209,8 +218,10 @@ function start(hrmSettings, exsHrmStats) { g.reset().clearRect(R).setFontAlign(0,0,0); + drawIndicator(); //draw every second setTimeout(updateUI,0,true); + Bangle.on("lock", drawIndicator); karvonenInterval = setInterval(function() { updateUI(false); }, 1000); @@ -240,6 +251,7 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change function stop(){ if (karvonenInterval) clearInterval(karvonenInterval); karvonenInterval = undefined; + Bangle.removeListener("lock", drawIndicator); wu.show(); } From 29a6683d53bb03a3db5b539ca0fc73cb787761e9 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 15:29:35 +0200 Subject: [PATCH 03/11] runplus - Add indicators for lock, gps and pulse --- apps/runplus/karvonen.js | 48 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index b9c774520..849e88803 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -5,6 +5,8 @@ let wu = require("widget_utils"); let R; const ICON_LOCK = atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="); +const ICON_HEART = atob("Dw4BODj4+fv3///////f/z/+P/g/4D+APgA4ACAA"); +const ICON_LOCATION = atob("CxABP4/7x/B+D8H8e/5/x/D+D4HwHAEAIA=="); const x = "x"; const y = "y"; function Rdiv(axis, divisor) { // Used when placing things on the screen @@ -187,13 +189,43 @@ function drawZones() { let karvonenInterval; let hrmstat; -function drawIndicator() { +function drawLockIndicator() { if (Bangle.isLocked()) g.setColor(g.theme.fg).drawImage(ICON_LOCK, 6, 8); else g.setColor(g.theme.bg).drawImage(ICON_LOCK, 6, 8); } +let gpsTimeout; +function drawGpsIndicator(e) { + if (e.fix){ + if (gpsTimeout) + clearTimeout(gpsTimeout); + g.setColor(g.theme.fg).drawImage(ICON_LOCATION, 8, R.y2 - 23); + gpsTimeout = setTimeout(()=>{ + g.setColor(g.theme.dark?"#ccc":"#444").drawImage(ICON_LOCATION, 8, R.y2 - 23); + gpsTimeout = setTimeout(()=>{ + g.setColor(g.theme.bg).drawImage(ICON_LOCATION, 8, R.y2 - 23); + }, 3900); + }, 1100); + } +} + +let pulseTimeout; +function drawPulseIndicator() { + if (hr){ + if (pulseTimeout) + clearTimeout(pulseTimeout); + g.setColor(g.theme.fg).drawImage(ICON_HEART, R.x2 - 21, R.y2 - 21); + pulseTimeout = setTimeout(()=>{ + g.setColor(g.theme.dark?"#ccc":"#444").drawImage(ICON_HEART, R.x2 - 21, R.y2 - 21); + pulseTimeout = setTimeout(()=>{ + g.setColor(g.theme.bg).drawImage(ICON_HEART, R.x2 - 21, R.y2 - 21); + }, 3900); + }, 1100); + } +} + function init(hrmSettings, exsHrmStats) { R = Bangle.appRect; hrmstat = exsHrmStats; @@ -218,10 +250,12 @@ function start(hrmSettings, exsHrmStats) { g.reset().clearRect(R).setFontAlign(0,0,0); - drawIndicator(); + drawLockIndicator(); //draw every second setTimeout(updateUI,0,true); - Bangle.on("lock", drawIndicator); + Bangle.on("lock", drawLockIndicator); + Bangle.on("HRM", drawPulseIndicator); + Bangle.on("GPS", drawGpsIndicator); karvonenInterval = setInterval(function() { updateUI(false); }, 1000); @@ -251,7 +285,13 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change function stop(){ if (karvonenInterval) clearInterval(karvonenInterval); karvonenInterval = undefined; - Bangle.removeListener("lock", drawIndicator); + if (pulseTimeout) clearTimeout(pulseTimeout); + pulseTimeout = undefined; + if (gpsTimeout) clearTimeout(gpsTimeout); + gpsTimeout = undefined; + Bangle.removeListener("lock", drawLockIndicator); + Bangle.removeListener("GPS", drawGpsIndicator); + Bangle.removeListener("HRM", drawPulseIndicator); wu.show(); } From 9e33ee0498d80aba6b8f07be6a1c018f3a18b2d7 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 15:30:08 +0200 Subject: [PATCH 04/11] runplus - Update ChangeLog --- apps/runplus/ChangeLog | 1 + apps/runplus/README.md | 1 + apps/runplus/metadata.json | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index 093c0cf9a..3c8cd0f74 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -24,3 +24,4 @@ Write to correct settings file, fixing settings not working. 0.21: Rebase on "Run" app ver. 0.16. 0.22: Ensure screen redraws after "Resume run?" menu (#3044) 0.23: Minor code improvements +0.24: Add indicators for lock,gps and pulse to karvonen screen \ No newline at end of file diff --git a/apps/runplus/README.md b/apps/runplus/README.md index 1776e1186..ddcf34cb7 100644 --- a/apps/runplus/README.md +++ b/apps/runplus/README.md @@ -25,6 +25,7 @@ so if you have no GPS lock you just need to wait. Unlock the screen and navigate between displays by swiping left or right. The upper number is the limit before next heart rate zone. The lower number is the limit before previous heart rate zone. The number in the middle is the heart rate. The Z1 to Z5 number indicates the heart rate zone where you are. The circle provides a quick visualisation of the hr zone in which you are. +Indicator icons for lock, heartrate and location are updated on arrival off internal system events. The heart icon shows if the exstats module decided that the heart rate value is usable. The location icon shows if there was an GPS event with a position fix in it. ## Recording Tracks diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index 109cfbf99..8b3703810 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -1,12 +1,15 @@ { "id": "runplus", "name": "Run+", - "version": "0.23", + "version": "0.24", "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", "supports": ["BANGLEJS2"], - "screenshots": [{"url": "screenshot.png"}], + "screenshots": [ + {"url": "screenshot.png"}, + {"url": "karvonen.png"} + ], "readme": "README.md", "storage": [ {"name": "runplus.app.js", "url": "app.js"}, From 2f6a56ce975aac3261c2a9b391e094b736054015 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 15:36:24 +0200 Subject: [PATCH 05/11] runplus - Add screenshot of karvonen screen --- apps/runplus/karvonen.png | Bin 0 -> 3385 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/runplus/karvonen.png diff --git a/apps/runplus/karvonen.png b/apps/runplus/karvonen.png new file mode 100644 index 0000000000000000000000000000000000000000..deacf7a8aedf9fafc6bd07e1f31d4ee23ce29249 GIT binary patch literal 3385 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 P00000NkvXXu0mjfH>q5) literal 0 HcmV?d00001 From e8194f1de474b4ce10ab7191f2be4e48b689491c Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 15:40:37 +0200 Subject: [PATCH 06/11] runplus - Draw arrows last to prevent right side being cut of --- apps/runplus/karvonen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index 849e88803..275ba83ab 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -270,7 +270,6 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change } hr = hrmstat.getValue(); //if (h!=0) hr = h; - drawArrows(); if (hr && hr!=hrLast) { drawHR(); drawZones(); @@ -279,6 +278,7 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change drawWaitHR(); } + drawArrows(); //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. } From 727802f52d8fe23ce7f681e4334dca37bfc75603 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 16:28:22 +0200 Subject: [PATCH 07/11] runplus - Switch faster to waiting screen if no HRM incoming --- apps/runplus/karvonen.js | 91 ++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index 275ba83ab..9bc6c7b2d 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -72,6 +72,7 @@ function drawHR() { g.setColor(g.theme.fg); g.setFont("Vector",50); g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); + drawArrows(); } function drawWaitHR() { @@ -89,6 +90,8 @@ function drawWaitHR() { g.setFont("Vector",20); g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2)); g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7)); + + drawArrows(); } //These functions call arcs to show different HR zones. @@ -138,8 +141,7 @@ function clearCurrentZone() { // Clears the extension of the current zone by pai HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); } -function getZone(zone) { - drawBgArc(); +function drawZone(zone) { clearCurrentZone(); if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} @@ -156,7 +158,7 @@ function getZone(zone) { if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} } -function getZoneAlert() { +function drawZoneAlert() { const HRzonemax = require("graphics_utils"); let minRadius = 0.40 * R.h; let startAngle1 = HRzonemax.degreesToRadians(-90); @@ -166,24 +168,48 @@ function getZoneAlert() { g.setFontAlign(0,0).drawString("ALERT", centreX, centreY); } +function drawWaitUI(){ + g.clearRect(R); + drawLockIndicator(); + drawPulseIndicator(); + drawBgArc(); + drawWaitHR(); + drawArrows(); +} + +function drawBase() { + g.clearRect(R); + drawLockIndicator(); + drawPulseIndicator(); + drawBgArc(); +} + +function drawZoneUI(full){ + if (full) { + drawBase(); + } + drawHR(); + drawZones(); +} + //Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. let subZoneLast; function drawZones() { - if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone alert. - if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 - else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a - else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b - else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c - else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a - else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b - else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c - else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a - else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b - else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c - else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a - else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b - else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c - else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert + if ((hr < maxhr - 2) && subZoneLast==13) { drawZoneUI(true); } // Reset UI when coming down from zone alert. + if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; drawZone(subZoneLast);}} // Z1 + else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; drawZone(subZoneLast);}} // Z2a + else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; drawZone(subZoneLast);}} // Z2b + else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; drawZone(subZoneLast);}} // Z2c + else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; drawZone(subZoneLast);}} // Z3a + else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; drawZone(subZoneLast);}} // Z3b + else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; drawZone(subZoneLast);}} // Z3c + else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; drawZone(subZoneLast);}} // Z4a + else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; drawZone(subZoneLast);}} // Z4b + else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; drawZone(subZoneLast);}} // Z4c + else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; drawZone(subZoneLast);}} // Z5a + else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; drawZone(subZoneLast);}} // Z5b + else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; drawZone(subZoneLast);}} // Z5c + else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();drawZoneAlert();} // Alert } let karvonenInterval; @@ -250,17 +276,15 @@ function start(hrmSettings, exsHrmStats) { g.reset().clearRect(R).setFontAlign(0,0,0); - drawLockIndicator(); //draw every second - setTimeout(updateUI,0,true); Bangle.on("lock", drawLockIndicator); Bangle.on("HRM", drawPulseIndicator); + Bangle.on("HRM", updateUI); Bangle.on("GPS", drawGpsIndicator); - karvonenInterval = setInterval(function() { - updateUI(false); - }, 1000); -} + setTimeout(drawWaitUI,0); +} +let waitTimeout; let hrLast; //h = 0; // Used to force hr update via web ui console field to trigger draws, together with `if (h!=0) hr = h;` below. function updateUI(resetHrLast) { // Update UI, only draw if warranted by change in HR. @@ -268,17 +292,21 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change hrLast = 0; // Handles correct updating on init depending on if we've got HRM readings yet or not. subZoneLast = undefined; } + + if (waitTimeout) clearTimeout(waitTimeout); + waitTimeout = setTimeout(() => {drawWaitUI(); waitTimeout = undefined;}, 2000); + hr = hrmstat.getValue(); //if (h!=0) hr = h; - if (hr && hr!=hrLast) { - drawHR(); - drawZones(); - } else { - drawBgArc(); - drawWaitHR(); + + if (hrLast != hr){ + if (hr && hrLast != hr){ + drawZoneUI(true); + } else if (!hr) + drawWaitUI(); } - drawArrows(); + hrLast = hr; //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. } @@ -292,6 +320,7 @@ function stop(){ Bangle.removeListener("lock", drawLockIndicator); Bangle.removeListener("GPS", drawGpsIndicator); Bangle.removeListener("HRM", drawPulseIndicator); + Bangle.removeListener("HRM", updateUI); wu.show(); } From 5bd0ed73bc0d8d97a3eab559dff3c58ddd010d5e Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 18:10:16 +0200 Subject: [PATCH 08/11] runplus - Fix flickering of indicators --- apps/runplus/karvonen.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index 9bc6c7b2d..a7b5d0c70 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -158,8 +158,16 @@ function drawZone(zone) { if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} } +function drawIndicators(){ + drawLockIndicator(); + drawPulseIndicator(); + drawGpsIndicator(); +} + function drawZoneAlert() { const HRzonemax = require("graphics_utils"); + drawIndicators(); + g.clearRect(R); let minRadius = 0.40 * R.h; let startAngle1 = HRzonemax.degreesToRadians(-90); let endAngle1 = HRzonemax.degreesToRadians(270); @@ -170,8 +178,7 @@ function drawZoneAlert() { function drawWaitUI(){ g.clearRect(R); - drawLockIndicator(); - drawPulseIndicator(); + drawIndicators(); drawBgArc(); drawWaitHR(); drawArrows(); @@ -179,8 +186,7 @@ function drawWaitUI(){ function drawBase() { g.clearRect(R); - drawLockIndicator(); - drawPulseIndicator(); + drawIndicators(); drawBgArc(); } @@ -209,7 +215,7 @@ function drawZones() { else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; drawZone(subZoneLast);}} // Z5a else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; drawZone(subZoneLast);}} // Z5b else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; drawZone(subZoneLast);}} // Z5c - else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();drawZoneAlert();} // Alert + else if (hr >= maxhr - 2) {subZoneLast=13; drawZoneAlert();} // Alert } let karvonenInterval; @@ -223,17 +229,27 @@ function drawLockIndicator() { } let gpsTimeout; +let lastGps; function drawGpsIndicator(e) { - if (e.fix){ + if (e && e.fix){ if (gpsTimeout) clearTimeout(gpsTimeout); g.setColor(g.theme.fg).drawImage(ICON_LOCATION, 8, R.y2 - 23); + lastGps = 0; gpsTimeout = setTimeout(()=>{ g.setColor(g.theme.dark?"#ccc":"#444").drawImage(ICON_LOCATION, 8, R.y2 - 23); + lastGps = 1; gpsTimeout = setTimeout(()=>{ g.setColor(g.theme.bg).drawImage(ICON_LOCATION, 8, R.y2 - 23); + lastGps = 2; }, 3900); }, 1100); + } else if (lastGps !== undefined){ + switch (lastGps) { + case 0: g.setColor(g.theme.fg).drawImage(ICON_LOCATION, 8, R.y2 - 23); break; + case 1: g.setColor(g.theme.dark?"#ccc":"#444").drawImage(ICON_LOCATION, 8, R.y2 - 23); break; + case 2: g.setColor(g.theme.bg).drawImage(ICON_LOCATION, 8, R.y2 - 23); break; + } } } @@ -299,7 +315,7 @@ function updateUI(resetHrLast) { // Update UI, only draw if warranted by change hr = hrmstat.getValue(); //if (h!=0) hr = h; - if (hrLast != hr){ + if (hrLast != hr || resetHrLast){ if (hr && hrLast != hr){ drawZoneUI(true); } else if (!hr) @@ -317,6 +333,8 @@ function stop(){ pulseTimeout = undefined; if (gpsTimeout) clearTimeout(gpsTimeout); gpsTimeout = undefined; + if (waitTimeout) clearTimeout(waitTimeout); + waitTimeout = undefined; Bangle.removeListener("lock", drawLockIndicator); Bangle.removeListener("GPS", drawGpsIndicator); Bangle.removeListener("HRM", drawPulseIndicator); From 83d56797b22a11457beb8c219704634e23e040c1 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 18:38:16 +0200 Subject: [PATCH 09/11] runplus - Directly draw the correct interface --- apps/runplus/karvonen.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js index a7b5d0c70..afdbdb1cf 100644 --- a/apps/runplus/karvonen.js +++ b/apps/runplus/karvonen.js @@ -168,6 +168,7 @@ function drawZoneAlert() { const HRzonemax = require("graphics_utils"); drawIndicators(); g.clearRect(R); + drawIndicators(); let minRadius = 0.40 * R.h; let startAngle1 = HRzonemax.degreesToRadians(-90); let endAngle1 = HRzonemax.degreesToRadians(270); @@ -292,13 +293,12 @@ function start(hrmSettings, exsHrmStats) { g.reset().clearRect(R).setFontAlign(0,0,0); - //draw every second Bangle.on("lock", drawLockIndicator); Bangle.on("HRM", drawPulseIndicator); Bangle.on("HRM", updateUI); Bangle.on("GPS", drawGpsIndicator); - setTimeout(drawWaitUI,0); + setTimeout(updateUI,0,true); } let waitTimeout; let hrLast; @@ -339,6 +339,7 @@ function stop(){ Bangle.removeListener("GPS", drawGpsIndicator); Bangle.removeListener("HRM", drawPulseIndicator); Bangle.removeListener("HRM", updateUI); + lastGps = undefined; wu.show(); } From b50f9b51830684e7a550b6d3c60c9a2e55a5690d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 20:21:32 +0200 Subject: [PATCH 10/11] runplus - Fix lint warning --- apps/runplus/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 9bae8fcfd..6a9d39346 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -96,7 +96,7 @@ function onStartStop() { } } - promise = promise.then(() => { + promise.then(() => { if (running) { if (shouldResume) exs.resume() From d3b1835ef050146450083f02d5f859b4b4597529 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 22 Apr 2024 20:23:55 +0200 Subject: [PATCH 11/11] runplus - Remove lint exemptions --- apps/lint_exemptions.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/lint_exemptions.js b/apps/lint_exemptions.js index 02ede7bde..63faab837 100644 --- a/apps/lint_exemptions.js +++ b/apps/lint_exemptions.js @@ -275,12 +275,6 @@ module.exports = { "no-unused-vars" ] }, - "runplus/app.js": { - "hash": "fabec449552d17ceb5d510aa058e420757f3caba11999efbe6ebf2ac1a81eb32", - "rules": [ - "no-unused-vars" - ] - }, "powermanager/boot.js": { "hash": "662d9d29a80a4f2c2855097b4073a099604f4f6d444c13a33304346c788cc5cb", "rules": [ @@ -747,12 +741,6 @@ module.exports = { "no-undef" ] }, - "runplus/karvonen.js": { - "hash": "3011bbc5afc3e17bb873f4544d680acc8765105dd825417eadb01047ecadbcb7", - "rules": [ - "no-undef" - ] - }, "rescalc/app.js": { "hash": "925f00a439817fadf92f4e7a7fcd509eb9d9c7e1e4309e315ea92a6881e18b4b", "rules": [