forked from FOSS/BangleApps
add changes to runplus
changes to differentiate runplus from run app fix default settings fallback for HRM fixes to draw correctly and in a timely manner when swiping to the karvonnen ui depending on if we have hrm data or not Update ChangeLog small ui tweak run: Keep run state between runs (allowing you to exit and restart the app) tweak clearRect width to not clear the indicator segment bump version write to correct settings file add tag karvonen change spelling karvonnen -> karvonen in app.js, karvonen.js spelling karvonnen -> karvonen in metadata runplus - Fix typo in variable name preventing starting a run tweak and align HRM min/max defaults, change allowed min/max interval in settings bump version, fix typo follow the preferred metadata.json style Readd contributors to README.md Tweak ChangeLogmaster
parent
6822d8ed70
commit
d5f114762f
|
@ -14,4 +14,4 @@
|
||||||
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
||||||
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
||||||
0.15: Keep run state between runs (allowing you to exit and restart the app)
|
0.15: Keep run state between runs (allowing you to exit and restart the app)
|
||||||
0.16: Added ability to resume a run that was stopped previously (fix #1907)
|
0.16: Added ability to resume a run that was stopped previously (fix #1907)
|
||||||
|
|
|
@ -14,4 +14,11 @@
|
||||||
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
||||||
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
||||||
0.15: Keep run state between runs (allowing you to exit and restart the app)
|
0.15: Keep run state between runs (allowing you to exit and restart the app)
|
||||||
0.16: Added ability to resume a run that was stopped previously (fix #1907)
|
0.16: Added ability to resume a run that was stopped previously (fix #1907)
|
||||||
|
0.17: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher)
|
||||||
|
0.18: Don't clear zone 2b indicator segment when updating HRM reading.
|
||||||
|
Write to correct settings file, fixing settings not working.
|
||||||
|
0.19: Fix typo in variable name preventing starting a run
|
||||||
|
0.20: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix
|
||||||
|
another typo.
|
||||||
|
0.21: Rebase on "Run" app ver. 0.16.
|
||||||
|
|
|
@ -67,3 +67,10 @@ app loader, the module is automatically included in the app's source. However
|
||||||
when developing via the IDE the module won't get pulled in by default.
|
when developing via the IDE the module won't get pulled in by default.
|
||||||
|
|
||||||
There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md)
|
There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md)
|
||||||
|
## Contributors (Run and Run+)
|
||||||
|
gfwilliams
|
||||||
|
hughbarney
|
||||||
|
GrandVizierOlaf
|
||||||
|
BartS23
|
||||||
|
f-teacher
|
||||||
|
thyttan
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
var ExStats = require("exstats");
|
// Use widget utils to show/hide widgets
|
||||||
var B2 = process.env.HWVERSION===2;
|
let wu = require("widget_utils");
|
||||||
var Layout = require("Layout");
|
|
||||||
var locale = require("locale");
|
|
||||||
var fontHeading = "6x8:2";
|
|
||||||
var fontValue = B2 ? "6x15:2" : "6x8:3";
|
|
||||||
var headingCol = "#888";
|
|
||||||
var fixCount = 0;
|
|
||||||
var isMenuDisplayed = false;
|
|
||||||
|
|
||||||
g.clear();
|
let runInterval;
|
||||||
|
let karvonenActive = false;
|
||||||
|
// Run interface wrapped in a function
|
||||||
|
let ExStats = require("exstats");
|
||||||
|
let B2 = process.env.HWVERSION===2;
|
||||||
|
let Layout = require("Layout");
|
||||||
|
let locale = require("locale");
|
||||||
|
let fontHeading = "6x8:2";
|
||||||
|
let fontValue = B2 ? "6x15:2" : "6x8:3";
|
||||||
|
let headingCol = "#888";
|
||||||
|
let fixCount = 0;
|
||||||
|
let isMenuDisplayed = false;
|
||||||
|
|
||||||
|
g.reset().clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
wu.show();
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
let settings = Object.assign({
|
let settings = Object.assign({
|
||||||
|
@ -36,9 +43,13 @@ let settings = Object.assign({
|
||||||
notifications: [],
|
notifications: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, require("Storage").readJSON("run.json", 1) || {});
|
HRM: {
|
||||||
var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!=="");
|
min: 55,
|
||||||
var exs = ExStats.getStats(statIDs, settings);
|
max: 185,
|
||||||
|
},
|
||||||
|
}, require("Storage").readJSON("runplus.json", 1) || {});
|
||||||
|
let statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!=="");
|
||||||
|
let exs = ExStats.getStats(statIDs, settings);
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
|
||||||
function setStatus(running) {
|
function setStatus(running) {
|
||||||
|
@ -100,11 +111,11 @@ function onStartStop() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var lc = [];
|
let lc = [];
|
||||||
// Load stats in pair by pair
|
// Load stats in pair by pair
|
||||||
for (var i=0;i<statIDs.length;i+=2) {
|
for (let i=0;i<statIDs.length;i+=2) {
|
||||||
var sa = exs.stats[statIDs[i+0]];
|
let sa = exs.stats[statIDs[i+0]];
|
||||||
var sb = exs.stats[statIDs[i+1]];
|
let sb = exs.stats[statIDs[i+1]];
|
||||||
lc.push({ type:"h", filly:1, c:[
|
lc.push({ type:"h", filly:1, c:[
|
||||||
sa?{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol }:{},
|
sa?{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol }:{},
|
||||||
sb?{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }:{}
|
sb?{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }:{}
|
||||||
|
@ -122,9 +133,9 @@ lc.push({ type:"h", filly:1, c:[
|
||||||
{type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 }
|
{type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 }
|
||||||
]});
|
]});
|
||||||
// Now calculate the layout
|
// Now calculate the layout
|
||||||
var layout = new Layout( {
|
let layout = new Layout( {
|
||||||
type:"v", c: lc
|
type:"v", c: lc
|
||||||
},{lazy:true, btns:[{ label:"---", cb: onStartStop, id:"button"}]});
|
},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}), id:"button"}]});
|
||||||
delete lc;
|
delete lc;
|
||||||
setStatus(exs.state.active);
|
setStatus(exs.state.active);
|
||||||
layout.render();
|
layout.render();
|
||||||
|
@ -132,16 +143,16 @@ layout.render();
|
||||||
function configureNotification(stat) {
|
function configureNotification(stat) {
|
||||||
stat.on('notify', (e)=>{
|
stat.on('notify', (e)=>{
|
||||||
settings.notify[e.id].notifications.reduce(function (promise, buzzPattern) {
|
settings.notify[e.id].notifications.reduce(function (promise, buzzPattern) {
|
||||||
return promise.then(function () {
|
return promise.then(function () {
|
||||||
return Bangle.buzz(buzzPattern[0], buzzPattern[1]);
|
return Bangle.buzz(buzzPattern[0], buzzPattern[1]);
|
||||||
});
|
});
|
||||||
}, Promise.resolve());
|
}, Promise.resolve());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(settings.notify).forEach((statType) => {
|
Object.keys(settings.notify).forEach((statType) => {
|
||||||
if (settings.notify[statType].increment > 0 && exs.stats[statType]) {
|
if (settings.notify[statType].increment > 0 && exs.stats[statType]) {
|
||||||
configureNotification(exs.stats[statType]);
|
configureNotification(exs.stats[statType]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,8 +164,46 @@ Bangle.on("GPS", function(fix) {
|
||||||
Bangle.buzz(); // first fix, does not need to respect quiet mode
|
Bangle.buzz(); // first fix, does not need to respect quiet mode
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// We always call ourselves once a second to update
|
|
||||||
setInterval(function() {
|
// run() function used to switch between traditional run UI and karvonen UI
|
||||||
layout.clock.label = locale.time(new Date(),1);
|
function run() {
|
||||||
if (!isMenuDisplayed) layout.render();
|
wu.show();
|
||||||
}, 1000);
|
layout.lazy = false;
|
||||||
|
layout.render();
|
||||||
|
layout.lazy = true;
|
||||||
|
// We always call ourselves once a second to update
|
||||||
|
if (!runInterval){
|
||||||
|
runInterval = setInterval(function() {
|
||||||
|
layout.clock.label = locale.time(new Date(),1);
|
||||||
|
if (!isMenuDisplayed && !karvonenActive) layout.render();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run();
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// Karvonen
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
function stopRunUI() {
|
||||||
|
// stop updating and drawing the traditional run app UI
|
||||||
|
clearInterval(runInterval);
|
||||||
|
runInterval = undefined;
|
||||||
|
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);}
|
||||||
|
}
|
||||||
|
// Listen for swipes with the swipeHandler
|
||||||
|
Bangle.on("swipe", swipeHandler);
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
(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;
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
x: [
|
||||||
|
175/40,
|
||||||
|
2,
|
||||||
|
175/135,
|
||||||
|
],
|
||||||
|
y: [
|
||||||
|
175/64,
|
||||||
|
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();
|
||||||
|
let hr1 = 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 = 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 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 = 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);
|
||||||
|
|
||||||
|
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 getZoneAlert() {
|
||||||
|
const HRzonemax = require("graphics_utils");
|
||||||
|
let centreX1,centreY1,maxRadius1 = 1;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// check for updates every second.
|
||||||
|
karvonenInterval = setInterval(function() {
|
||||||
|
if (!isMenuDisplayed && karvonenActive) updateUI();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return karvonenInterval;
|
||||||
|
})
|
|
@ -1,16 +1,20 @@
|
||||||
{ "id": "run",
|
{
|
||||||
"name": "Run",
|
"id": "runplus",
|
||||||
"version":"0.16",
|
"name": "Run+",
|
||||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
"version": "0.21",
|
||||||
|
"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",
|
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
|
||||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url": "screenshot.png"}],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"run.app.js","url":"app.js"},
|
{"name": "runplus.app.js", "url": "app.js"},
|
||||||
{"name":"run.img","url":"app-icon.js","evaluate":true},
|
{"name": "runplus.img", "url": "app-icon.js", "evaluate": true},
|
||||||
{"name":"run.settings.js","url":"settings.js"}
|
{"name": "runplus.settings.js", "url": "settings.js"},
|
||||||
|
{"name": "runplus_karvonen", "url": "karvonen.js"}
|
||||||
],
|
],
|
||||||
"data": [{"name":"run.json"}]
|
"data": [
|
||||||
|
{"name": "runplus.json"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
const SETTINGS_FILE = "run.json";
|
const SETTINGS_FILE = "runplus.json";
|
||||||
var ExStats = require("exstats");
|
var ExStats = require("exstats");
|
||||||
var statsList = ExStats.getList();
|
var statsList = ExStats.getList();
|
||||||
statsList.unshift({name:"-",id:""}); // add blank menu item
|
statsList.unshift({name:"-",id:""}); // add blank menu item
|
||||||
|
@ -31,6 +31,10 @@
|
||||||
notifications: [],
|
notifications: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
HRM: {
|
||||||
|
min: 55,
|
||||||
|
max: 185,
|
||||||
|
},
|
||||||
}, storage.readJSON(SETTINGS_FILE, 1) || {});
|
}, storage.readJSON(SETTINGS_FILE, 1) || {});
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
storage.write(SETTINGS_FILE, settings)
|
storage.write(SETTINGS_FILE, settings)
|
||||||
|
@ -125,5 +129,29 @@
|
||||||
'Box 6': getBoxChooser("B6"),
|
'Box 6': getBoxChooser("B6"),
|
||||||
});
|
});
|
||||||
menu[/*LANG*/"Boxes"] = function() { E.showMenu(boxMenu)};
|
menu[/*LANG*/"Boxes"] = function() { E.showMenu(boxMenu)};
|
||||||
|
|
||||||
|
var hrmMenu = {
|
||||||
|
'< Back': function() { E.showMenu(menu) },
|
||||||
|
}
|
||||||
|
|
||||||
|
menu[/*LANG*/"HRM min/max"] = function() { E.showMenu(hrmMenu)};
|
||||||
|
hrmMenu[/*LANG*/"min"] = {
|
||||||
|
min: 1, max: 100,
|
||||||
|
value: settings.HRM.min,
|
||||||
|
format: w => w,
|
||||||
|
onchange: w => {
|
||||||
|
settings.HRM.min = w;
|
||||||
|
saveSettings();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hrmMenu[/*LANG*/"max"] = {
|
||||||
|
min: 101, max: 220,
|
||||||
|
value: settings.HRM.max,
|
||||||
|
format: v => v,
|
||||||
|
onchange: v => {
|
||||||
|
settings.HRM.max = v;
|
||||||
|
saveSettings();
|
||||||
|
},
|
||||||
|
}
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue