mirror of https://github.com/espruino/BangleApps
179 lines
5.7 KiB
JavaScript
179 lines
5.7 KiB
JavaScript
var B2 = process.env.HWVERSION==2;
|
|
var Layout = require("Layout");
|
|
var locale = require("locale");
|
|
var fontHeading = "6x8:2";
|
|
var fontValue = B2 ? "6x15:2" : "6x8:3";
|
|
var headingCol = "#888";
|
|
var running = false;
|
|
var fixCount = 0;
|
|
var startTime;
|
|
var startSteps;
|
|
// This & previous GPS readings
|
|
var lastGPS, thisGPS;
|
|
var distance = 0; ///< distance in meters
|
|
var startSteps = Bangle.getStepCount(); ///< number of steps when we started
|
|
var lastStepCount = startSteps; // last time 'step' was called
|
|
var stepHistory = new Uint8Array(60); // steps each second for the last minute (0 = current minute)
|
|
|
|
g.clear();
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
// ---------------------------
|
|
|
|
function formatTime(ms) {
|
|
let hrs = Math.floor(ms/3600000).toString();
|
|
let mins = (Math.floor(ms/60000)%60).toString();
|
|
let secs = (Math.floor(ms/1000)%60).toString();
|
|
|
|
if (hrs === '0')
|
|
return mins.padStart(2,0)+":"+secs.padStart(2,0);
|
|
else
|
|
return hrs+":"+mins.padStart(2,0)+":"+secs.padStart(2,0); // dont pad hours
|
|
}
|
|
|
|
// Format speed in meters/second
|
|
function formatPace(speed) {
|
|
if (speed < 0.1667) {
|
|
return `__:__`;
|
|
}
|
|
const pace = Math.round(1000 / speed); // seconds for 1km
|
|
const min = Math.floor(pace / 60); // minutes for 1km
|
|
const sec = pace % 60;
|
|
return ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2);
|
|
}
|
|
|
|
// ---------------------------
|
|
|
|
function clearState() {
|
|
distance = 0;
|
|
startSteps = Bangle.getStepCount();
|
|
stepHistory.fill(0);
|
|
layout.dist.label=locale.distance(distance);
|
|
layout.time.label="00:00";
|
|
layout.pace.label=formatPace(0);
|
|
layout.hrm.label="--";
|
|
layout.steps.label=0;
|
|
layout.cadence.label= "0";
|
|
layout.status.bgCol = "#f00";
|
|
}
|
|
|
|
function onStartStop() {
|
|
running = !running;
|
|
if (running) {
|
|
clearState();
|
|
startTime = Date.now();
|
|
}
|
|
layout.button.label = running ? "STOP" : "START";
|
|
layout.status.label = running ? "RUN" : "STOP";
|
|
layout.status.bgCol = running ? "#0f0" : "#f00";
|
|
// if stopping running, don't clear state
|
|
// so we can at least refer to what we've done
|
|
layout.render();
|
|
}
|
|
|
|
var layout = new Layout( {
|
|
type:"v", c: [
|
|
{ type:"h", filly:1, c:[
|
|
{type:"txt", font:fontHeading, label:"DIST", fillx:1, col:headingCol },
|
|
{type:"txt", font:fontHeading, label:"TIME", fillx:1, col:headingCol }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontValue, label:"0.00", id:"dist", fillx:1 },
|
|
{type:"txt", font:fontValue, label:"00:00", id:"time", fillx:1 }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontHeading, label:"PACE", fillx:1, col:headingCol },
|
|
{type:"txt", font:fontHeading, label:"HEART", fillx:1, col:headingCol }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontValue, label:`__'__"`, id:"pace", fillx:1 },
|
|
{type:"txt", font:fontValue, label:"--", id:"hrm", fillx:1 }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontHeading, label:"STEPS", fillx:1, col:headingCol },
|
|
{type:"txt", font:fontHeading, label:"CADENCE", fillx:1, col:headingCol }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontValue, label:"0", id:"steps", fillx:1 },
|
|
{type:"txt", font:fontValue, label:"0", id:"cadence", fillx:1 }
|
|
]}, { type:"h", filly:1, c:[
|
|
{type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" },
|
|
{type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg },
|
|
{type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 }
|
|
]},
|
|
|
|
]
|
|
},{lazy:true, btns:[{ label:"START", cb: onStartStop, id:"button"}]});
|
|
clearState();
|
|
layout.render();
|
|
|
|
function onTimer() {
|
|
layout.clock.label = locale.time(new Date(),1);
|
|
if (!running) {
|
|
layout.render();
|
|
return;
|
|
}
|
|
// called once a second
|
|
var duration = Date.now() - startTime; // in ms
|
|
// set cadence based on steps over last minute
|
|
var stepsInMinute = E.sum(stepHistory);
|
|
var cadence = 60000 * stepsInMinute / Math.min(duration,60000);
|
|
// update layout
|
|
layout.time.label = formatTime(duration);
|
|
layout.steps.label = Bangle.getStepCount()-startSteps;
|
|
layout.cadence.label = Math.round(cadence);
|
|
layout.render();
|
|
// move step history onwards
|
|
stepHistory.set(stepHistory,1);
|
|
stepHistory[0]=0;
|
|
}
|
|
|
|
function radians(a) {
|
|
return a*Math.PI/180;
|
|
}
|
|
|
|
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
|
|
// https://www.movable-type.co.uk/scripts/latlong.html
|
|
function calcDistance(a,b) {
|
|
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
|
var y = radians(b.lat-a.lat);
|
|
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
|
|
}
|
|
|
|
Bangle.on("GPS", function(fix) {
|
|
layout.gps.bgCol = fix.fix ? "#0f0" : "#f00";
|
|
if (!fix.fix) { return; } // only process actual fixes
|
|
if (fixCount++ == 0) {
|
|
Bangle.buzz(); // first fix, does not need to respect quiet mode
|
|
lastGPS = fix; // initialise on first fix
|
|
}
|
|
|
|
thisGPS = fix;
|
|
|
|
if (running) {
|
|
var d = calcDistance(lastGPS, thisGPS);
|
|
distance += d;
|
|
layout.dist.label=locale.distance(distance);
|
|
var duration = Date.now() - startTime; // in ms
|
|
var speed = distance * 1000 / duration; // meters/sec
|
|
layout.pace.label = formatPace(speed);
|
|
lastGPS = fix;
|
|
}
|
|
});
|
|
Bangle.on("HRM", function(h) {
|
|
layout.hrm.label = h.bpm;
|
|
});
|
|
Bangle.on("step", function(steps) {
|
|
if (running) {
|
|
layout.steps.label = steps-Bangle.getStepCount();
|
|
stepHistory[0] += steps-lastStepCount;
|
|
}
|
|
lastStepCount = steps;
|
|
});
|
|
|
|
let settings = require("Storage").readJSON('run.json',1)||{"use_gps":true,"use_hrm":true};
|
|
|
|
// We always call ourselves once a second, if only to update the time
|
|
setInterval(onTimer, 1000);
|
|
|
|
/* Turn GPS and HRM on right at the start to ensure
|
|
we get the highest chance of a lock. */
|
|
if (settings.use_hrm) Bangle.setHRMPower(true,"app");
|
|
if (settings.use_gps) Bangle.setGPSPower(true,"app");
|