forked from FOSS/BangleApps
Add 'run' app
parent
4c193b3fd1
commit
12254e06f3
13
apps.json
13
apps.json
|
@ -2277,6 +2277,19 @@
|
|||
{"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "run",
|
||||
"name": "Run",
|
||||
"version":"0.01",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"run.app.js","url":"app.js"},
|
||||
{"name":"run.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "banglerun",
|
||||
"name": "BangleRun",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,33 @@
|
|||
# Run App
|
||||
|
||||
This app allows you to display the status of your run.
|
||||
|
||||
To use it, start the app and press the middle button so that
|
||||
the red `STOP` in the bottom right turns to a green `RUN`.
|
||||
|
||||
## Display
|
||||
|
||||
* `DIST` - the distance travelled based on the GPS (if you have a GPS lock).
|
||||
* NOTE: this is based on the GPS coordinates which are not 100% accurate, especially initially. As
|
||||
the GPS updates your position as it gets more satellites your position changes and the distance
|
||||
shown will increase, even if you are standing still.
|
||||
* `TIME` - the elapsed time for your run
|
||||
* `PACE` - the number of minutes it takes you to run a kilometer **based on your run so far**
|
||||
* `HEART` - Your heart rate
|
||||
* `STEPS` - Steps since you started exercising
|
||||
* `CADENCE` - Steps per second based on your step rate *over the last minute*
|
||||
* `GPS` - this is green if you have a GPS lock. GPS is turned on automatically
|
||||
so if you have no GPS lock you just need to wait.
|
||||
* The current time is displayed right at the bottom of the screen
|
||||
* `RUN/STOP` - whether the distance for your run is being displayed or not
|
||||
|
||||
## Recording Tracks
|
||||
|
||||
`Run` doesn't directly allow you to record your tracks at the moment.
|
||||
However you can just install the `Recorder` app, turn recording on in
|
||||
that, and then start the `Run` app.
|
||||
|
||||
## TODO
|
||||
|
||||
* Allow this app to trigger the `Recorder` app on and off directly.
|
||||
* Keep a log of each run's stats (distance/steps/etc)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///pH9vEFt9TIW0FqALJitUBZNVqoLqgo4BHZAUBtBTHgILB1XAEREV1WsEQ9AgWq1ALHgEO1WtBYxCBhWq0pdInWq2tABY8q1WVBZGq1XFBZS/IKQRvCDIsP9WsBZP60CTCBYs//+wLxALBTQ4AB///+AKHgYLB/gLK/4LHh//AIIwFitVr/8DIIwFLANXBAILIqogBn7DBEYrXBeQRgIBYKmHDgYLLZRBACBZYKJZIILKKRZeWgJGKAFQA=="))
|
|
@ -0,0 +1,159 @@
|
|||
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 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) {
|
||||
var s = Math.round(ms/1000);
|
||||
var min = Math.floor(s/60).toString();
|
||||
s = (s%60).toString();
|
||||
return min.padStart(2,0)+":"+s.padStart(2,0);
|
||||
}
|
||||
|
||||
// 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";
|
||||
layout.gps.bgCol = "#f00";
|
||||
}
|
||||
|
||||
function onStartStop() {
|
||||
running = !running;
|
||||
layout.button.label = running ? "STOP" : "START";
|
||||
layout.status.label = running ? "RUN" : "STOP";
|
||||
layout.status.bgCol = running ? "#0f0" : "#f00";
|
||||
if (running) {
|
||||
clearState();
|
||||
startTime = Date.now();
|
||||
}
|
||||
// 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 },
|
||||
{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;
|
||||
}
|
||||
|
||||
Bangle.on("GPS", function(fix) {
|
||||
layout.gps.bgCol = fix.fix ? "#0f0" : "#f00";
|
||||
lastGPS = thisGPS;
|
||||
thisGPS = fix;
|
||||
if (running && fix.fix && lastGPS.fix) {
|
||||
// work out distance - moving from a to b
|
||||
var a = Bangle.project(lastGPS);
|
||||
var b = Bangle.project(thisGPS);
|
||||
var dx = a.x-b.x, dy = a.y-b.y;
|
||||
var d = Math.sqrt(dx*dx+dy*dy); // this should be the distance in meters
|
||||
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);
|
||||
}
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
// 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. */
|
||||
Bangle.setHRMPower(true,"app");
|
||||
Bangle.setGPSPower(true,"app");
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Loading…
Reference in New Issue