Merge pull request #3373 from halemmerich/runplus
runplus - Add lock, gps, pulse indicators to karvonen screenmaster
commit
e20b815da5
|
|
@ -275,12 +275,6 @@ module.exports = {
|
||||||
"no-unused-vars"
|
"no-unused-vars"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"runplus/app.js": {
|
|
||||||
"hash": "fabec449552d17ceb5d510aa058e420757f3caba11999efbe6ebf2ac1a81eb32",
|
|
||||||
"rules": [
|
|
||||||
"no-unused-vars"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"powermanager/boot.js": {
|
"powermanager/boot.js": {
|
||||||
"hash": "662d9d29a80a4f2c2855097b4073a099604f4f6d444c13a33304346c788cc5cb",
|
"hash": "662d9d29a80a4f2c2855097b4073a099604f4f6d444c13a33304346c788cc5cb",
|
||||||
"rules": [
|
"rules": [
|
||||||
|
|
@ -741,12 +735,6 @@ module.exports = {
|
||||||
"no-undef"
|
"no-undef"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"runplus/karvonen.js": {
|
|
||||||
"hash": "3011bbc5afc3e17bb873f4544d680acc8765105dd825417eadb01047ecadbcb7",
|
|
||||||
"rules": [
|
|
||||||
"no-undef"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rescalc/app.js": {
|
"rescalc/app.js": {
|
||||||
"hash": "925f00a439817fadf92f4e7a7fcd509eb9d9c7e1e4309e315ea92a6881e18b4b",
|
"hash": "925f00a439817fadf92f4e7a7fcd509eb9d9c7e1e4309e315ea92a6881e18b4b",
|
||||||
"rules": [
|
"rules": [
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,4 @@ Write to correct settings file, fixing settings not working.
|
||||||
0.21: Rebase on "Run" app ver. 0.16.
|
0.21: Rebase on "Run" app ver. 0.16.
|
||||||
0.22: Ensure screen redraws after "Resume run?" menu (#3044)
|
0.22: Ensure screen redraws after "Resume run?" menu (#3044)
|
||||||
0.23: Minor code improvements
|
0.23: Minor code improvements
|
||||||
|
0.24: Add indicators for lock,gps and pulse to karvonen screen
|
||||||
|
|
@ -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.
|
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.
|
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
|
## Recording Tracks
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
// Use widget utils to show/hide widgets
|
|
||||||
let wu = require("widget_utils");
|
|
||||||
|
|
||||||
let runInterval;
|
let runInterval;
|
||||||
let karvonenActive = false;
|
let karvonenActive = false;
|
||||||
// Run interface wrapped in a function
|
// Run interface wrapped in a function
|
||||||
let ExStats = require("exstats");
|
const ExStats = require("exstats");
|
||||||
let B2 = process.env.HWVERSION===2;
|
let B2 = process.env.HWVERSION===2;
|
||||||
let Layout = require("Layout");
|
let Layout = require("Layout");
|
||||||
let locale = require("locale");
|
let locale = require("locale");
|
||||||
|
|
@ -13,11 +10,10 @@ let fontValue = B2 ? "6x15:2" : "6x8:3";
|
||||||
let headingCol = "#888";
|
let headingCol = "#888";
|
||||||
let fixCount = 0;
|
let fixCount = 0;
|
||||||
let isMenuDisplayed = false;
|
let isMenuDisplayed = false;
|
||||||
|
const wu = require("widget_utils");
|
||||||
|
|
||||||
g.reset().clear();
|
g.reset().clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
|
||||||
wu.show();
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
let settings = Object.assign({
|
let settings = Object.assign({
|
||||||
|
|
@ -100,7 +96,7 @@ function onStartStop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
promise = promise.then(() => {
|
promise.then(() => {
|
||||||
if (running) {
|
if (running) {
|
||||||
if (shouldResume)
|
if (shouldResume)
|
||||||
exs.resume()
|
exs.resume()
|
||||||
|
|
@ -139,7 +135,7 @@ lc.push({ type:"h", filly:1, c:[
|
||||||
// Now calculate the layout
|
// Now calculate the layout
|
||||||
let layout = new Layout( {
|
let layout = new Layout( {
|
||||||
type:"v", c: lc
|
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;
|
delete lc;
|
||||||
setStatus(exs.state.active);
|
setStatus(exs.state.active);
|
||||||
layout.render();
|
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() {
|
function run() {
|
||||||
|
require("runplus_karvonen").stop();
|
||||||
|
karvonenActive = false;
|
||||||
wu.show();
|
wu.show();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
g.reset().clearRect(Bangle.appRect);
|
||||||
layout.lazy = false;
|
layout.lazy = false;
|
||||||
layout.render();
|
layout.render();
|
||||||
layout.lazy = true;
|
layout.lazy = true;
|
||||||
|
|
@ -179,7 +179,7 @@ function run() {
|
||||||
if (!runInterval){
|
if (!runInterval){
|
||||||
runInterval = setInterval(function() {
|
runInterval = setInterval(function() {
|
||||||
layout.clock.label = locale.time(new Date(),1);
|
layout.clock.label = locale.time(new Date(),1);
|
||||||
if (!isMenuDisplayed && !karvonenActive) layout.render();
|
if (!isMenuDisplayed) layout.render();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,25 +189,23 @@ run();
|
||||||
// Karvonen
|
// Karvonen
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
function stopRunUI() {
|
function karvonen(){
|
||||||
// stop updating and drawing the traditional run app UI
|
// stop updating and drawing the traditional run app UI
|
||||||
clearInterval(runInterval);
|
if (runInterval) clearInterval(runInterval);
|
||||||
runInterval = undefined;
|
runInterval = undefined;
|
||||||
|
g.reset().clearRect(Bangle.appRect);
|
||||||
|
require("runplus_karvonen").start(settings.HRM, exs.stats.bpm);
|
||||||
karvonenActive = true;
|
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
|
// Define the function to go back and forth between the different UI's
|
||||||
function swipeHandler(LR,_) {
|
function swipeHandler(LR,_) {
|
||||||
if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();}
|
if (!isMenuDisplayed){
|
||||||
if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);}
|
if (LR==-1 && karvonenActive)
|
||||||
|
run();
|
||||||
|
if (LR==1 && !karvonenActive)
|
||||||
|
karvonen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Listen for swipes with the swipeHandler
|
// Listen for swipes with the swipeHandler
|
||||||
Bangle.on("swipe", swipeHandler);
|
Bangle.on("swipe", swipeHandler);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
(function karvonen(hrmSettings, exsHrmStats) {
|
|
||||||
//This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+
|
//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.
|
//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.
|
//Other methods are even more approximative.
|
||||||
let wu = require("widget_utils");
|
let wu = require("widget_utils");
|
||||||
wu.hide();
|
let R;
|
||||||
let R = Bangle.appRect;
|
|
||||||
|
|
||||||
|
const ICON_LOCK = atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g==");
|
||||||
g.reset().clearRect(R).setFontAlign(0,0,0);
|
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";
|
const x = "x"; const y = "y";
|
||||||
function Rdiv(axis, divisor) { // Used when placing things on the screen
|
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);
|
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.
|
let linePoints = { //Not lists of points, but used to update points in the drawArrows function.
|
||||||
x: [
|
x: [
|
||||||
175/40,
|
175/40,
|
||||||
|
|
@ -24,10 +24,10 @@
|
||||||
175/52,
|
175/52,
|
||||||
175/110,
|
175/110,
|
||||||
175/122,
|
175/122,
|
||||||
],
|
]
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function drawArrows() {
|
function drawArrows() {
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
// Upper
|
// Upper
|
||||||
|
|
@ -42,27 +42,29 @@
|
||||||
// HRR = maximum HR - Minimum HR. minhr is minimum hr, maxhr is maximum hr.
|
// HRR = maximum HR - Minimum HR. minhr is minimum hr, maxhr is maximum hr.
|
||||||
//get the hrr (heart rate reserve).
|
//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.
|
// 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 minhr;
|
||||||
let maxhr = hrmSettings.max;
|
let maxhr;
|
||||||
|
let hrr;
|
||||||
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.
|
//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();
|
let hr;
|
||||||
// These letiables display next and previous HR zone.
|
// 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
|
//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
|
//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 minzone2;
|
||||||
let maxzone2 = hrr * 0.7 + minhr;
|
let maxzone2;
|
||||||
let maxzone3 = hrr * 0.8 + minhr;
|
let maxzone3;
|
||||||
let maxzone4 = hrr * 0.9 + minhr;
|
let maxzone4;
|
||||||
let maxzone5 = hrr * 0.99 + minhr;
|
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
|
// HR data: large, readable, in the middle of the screen
|
||||||
function drawHR() {
|
function drawHR() {
|
||||||
g.setFontAlign(-1,0,0);
|
g.setFontAlign(-1,0,0);
|
||||||
|
|
@ -70,6 +72,7 @@
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setFont("Vector",50);
|
g.setFont("Vector",50);
|
||||||
g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4);
|
g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4);
|
||||||
|
drawArrows();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawWaitHR() {
|
function drawWaitHR() {
|
||||||
|
|
@ -87,21 +90,23 @@
|
||||||
g.setFont("Vector",20);
|
g.setFont("Vector",20);
|
||||||
g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2));
|
g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2));
|
||||||
g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7));
|
g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7));
|
||||||
|
|
||||||
|
drawArrows();
|
||||||
}
|
}
|
||||||
|
|
||||||
//These functions call arcs to show different HR zones.
|
//These functions call arcs to show different HR zones.
|
||||||
|
|
||||||
//To shorten the code, I'll reference some letiables and reuse them.
|
//To shorten the code, I'll reference some letiables and reuse them.
|
||||||
let centreX = R.x + 0.5 * R.w;
|
let centreX;
|
||||||
let centreY = R.y + 0.5 * R.h;
|
let centreY;
|
||||||
let minRadius = 0.38 * R.h;
|
let minRadius;
|
||||||
let maxRadius = 0.50 * R.h;
|
let maxRadius;
|
||||||
|
|
||||||
//draw background image (dithered green zones)(I should draw different zones in different dithered colors)
|
//draw background image (dithered green zones)(I should draw different zones in different dithered colors)
|
||||||
const HRzones= require("graphics_utils");
|
const HRzones= require("graphics_utils");
|
||||||
let minRadiusz = 0.44 * R.h;
|
let minRadiusz;
|
||||||
let startAngle = HRzones.degreesToRadians(-88.5);
|
let startAngle;
|
||||||
let endAngle = HRzones.degreesToRadians(268.5);
|
let endAngle;
|
||||||
|
|
||||||
function drawBgArc() {
|
function drawBgArc() {
|
||||||
g.setColor(g.theme.dark==false?0xC618:"#002200");
|
g.setColor(g.theme.dark==false?0xC618:"#002200");
|
||||||
|
|
@ -136,8 +141,7 @@
|
||||||
HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle);
|
HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getZone(zone) {
|
function drawZone(zone) {
|
||||||
drawBgArc();
|
|
||||||
clearCurrentZone();
|
clearCurrentZone();
|
||||||
if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);}
|
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 >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);}
|
||||||
|
|
@ -154,60 +158,190 @@
|
||||||
if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);}
|
if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getZoneAlert() {
|
function drawIndicators(){
|
||||||
|
drawLockIndicator();
|
||||||
|
drawPulseIndicator();
|
||||||
|
drawGpsIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawZoneAlert() {
|
||||||
const HRzonemax = require("graphics_utils");
|
const HRzonemax = require("graphics_utils");
|
||||||
|
drawIndicators();
|
||||||
|
g.clearRect(R);
|
||||||
|
drawIndicators();
|
||||||
let minRadius = 0.40 * R.h;
|
let minRadius = 0.40 * R.h;
|
||||||
let startAngle1 = HRzonemax.degreesToRadians(-90);
|
let startAngle1 = HRzonemax.degreesToRadians(-90);
|
||||||
let endAngle1 = HRzonemax.degreesToRadians(270);
|
let endAngle1 = HRzonemax.degreesToRadians(270);
|
||||||
g.setFont("Vector",38);g.setColor("#ff0000");
|
g.setFont("Vector",38);g.setColor("#ff0000");
|
||||||
HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1);
|
HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1);
|
||||||
g.drawString("ALERT", 26,66);
|
g.setFontAlign(0,0).drawString("ALERT", centreX, centreY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawWaitUI(){
|
||||||
|
g.clearRect(R);
|
||||||
|
drawIndicators();
|
||||||
|
drawBgArc();
|
||||||
|
drawWaitHR();
|
||||||
|
drawArrows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBase() {
|
||||||
|
g.clearRect(R);
|
||||||
|
drawIndicators();
|
||||||
|
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.
|
//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;
|
let subZoneLast;
|
||||||
function drawZones() {
|
function drawZones() {
|
||||||
if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone 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; getZone(subZoneLast);}} // Z1
|
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; getZone(subZoneLast);}} // Z2a
|
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; getZone(subZoneLast);}} // Z2b
|
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; getZone(subZoneLast);}} // Z2c
|
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; getZone(subZoneLast);}} // Z3a
|
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; getZone(subZoneLast);}} // Z3b
|
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; getZone(subZoneLast);}} // Z3c
|
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; getZone(subZoneLast);}} // Z4a
|
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; getZone(subZoneLast);}} // Z4b
|
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; getZone(subZoneLast);}} // Z4c
|
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; getZone(subZoneLast);}} // Z5a
|
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; getZone(subZoneLast);}} // Z5b
|
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; getZone(subZoneLast);}} // Z5c
|
else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; drawZone(subZoneLast);}} // Z5c
|
||||||
else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert
|
else if (hr >= maxhr - 2) {subZoneLast=13; drawZoneAlert();} // Alert
|
||||||
}
|
}
|
||||||
|
|
||||||
function initDraw() {
|
let karvonenInterval;
|
||||||
drawArrows();
|
let hrmstat;
|
||||||
if (hr!=0) updateUI(true); else {drawWaitHR(); drawBgArc();}
|
|
||||||
//drawZones();
|
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;
|
||||||
|
let lastGps;
|
||||||
|
function drawGpsIndicator(e) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
hr = hrmstat.getValue();
|
||||||
|
minhr = hrmSettings.min;
|
||||||
|
maxhr = hrmSettings.max;
|
||||||
|
calculatehrr(minhr, maxhr);
|
||||||
|
|
||||||
|
centreX = R.x + 0.5 * R.w;
|
||||||
|
centreY = R.y + 0.5 * R.h;
|
||||||
|
minRadius = 0.38 * R.h;
|
||||||
|
maxRadius = 0.50 * R.h;
|
||||||
|
|
||||||
|
minRadiusz = 0.44 * R.h;
|
||||||
|
startAngle = HRzones.degreesToRadians(-88.5);
|
||||||
|
endAngle = HRzones.degreesToRadians(268.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start(hrmSettings, exsHrmStats) {
|
||||||
|
wu.hide();
|
||||||
|
init(hrmSettings, exsHrmStats);
|
||||||
|
|
||||||
|
g.reset().clearRect(R).setFontAlign(0,0,0);
|
||||||
|
|
||||||
|
Bangle.on("lock", drawLockIndicator);
|
||||||
|
Bangle.on("HRM", drawPulseIndicator);
|
||||||
|
Bangle.on("HRM", updateUI);
|
||||||
|
Bangle.on("GPS", drawGpsIndicator);
|
||||||
|
|
||||||
|
setTimeout(updateUI,0,true);
|
||||||
|
}
|
||||||
|
let waitTimeout;
|
||||||
let hrLast;
|
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.
|
//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.
|
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.
|
if (resetHrLast){
|
||||||
hr = exsHrmStats.getValue();
|
hrLast = 0; // Handles correct updating on init depending on if we've got HRM readings yet or not.
|
||||||
//if (h!=0) hr = h;
|
subZoneLast = undefined;
|
||||||
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();
|
if (waitTimeout) clearTimeout(waitTimeout);
|
||||||
|
waitTimeout = setTimeout(() => {drawWaitUI(); waitTimeout = undefined;}, 2000);
|
||||||
|
|
||||||
// check for updates every second.
|
hr = hrmstat.getValue();
|
||||||
karvonenInterval = setInterval(function() {
|
//if (h!=0) hr = h;
|
||||||
if (!isMenuDisplayed && karvonenActive) updateUI();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return karvonenInterval;
|
if (hrLast != hr || resetHrLast){
|
||||||
})
|
if (hr && hrLast != hr){
|
||||||
|
drawZoneUI(true);
|
||||||
|
} else if (!hr)
|
||||||
|
drawWaitUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop(){
|
||||||
|
if (karvonenInterval) clearInterval(karvonenInterval);
|
||||||
|
karvonenInterval = undefined;
|
||||||
|
if (pulseTimeout) clearTimeout(pulseTimeout);
|
||||||
|
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);
|
||||||
|
Bangle.removeListener("HRM", updateUI);
|
||||||
|
lastGps = undefined;
|
||||||
|
wu.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.start = start;
|
||||||
|
exports.stop = stop;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -1,12 +1,15 @@
|
||||||
{
|
{
|
||||||
"id": "runplus",
|
"id": "runplus",
|
||||||
"name": "Run+",
|
"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.",
|
"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",
|
"icon": "app.png",
|
||||||
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
|
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"screenshots": [{"url": "screenshot.png"}],
|
"screenshots": [
|
||||||
|
{"url": "screenshot.png"},
|
||||||
|
{"url": "karvonen.png"}
|
||||||
|
],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name": "runplus.app.js", "url": "app.js"},
|
{"name": "runplus.app.js", "url": "app.js"},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue