First version of the C25K app
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
# C25K
|
||||||
|
|
||||||
|
Unofficial app for the Couch to 5k training plan.
|
||||||
|
From being a couch-potato to running 5k in 8 weeks!
|
||||||
|
|
||||||
|
Each week has 3 training days, ideally with rest days between them.
|
||||||
|
|
||||||
|
Each day's programme consists of running for a certain time with occasional walking/resting phases.
|
||||||
|
When walking is part of the programme, the (run+walk) stages are repeated a number of times.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Show remaining time in seconds for each phase
|
||||||
|
- Vibrates on phase changes
|
||||||
|
- Keeps screen on to allow quickly glancing at the time while running
|
||||||
|
- Shows time on button press
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
If you know the week and day of the programme you'd like to start, set `Week` and `Day` to the appropriate values in the main menu and press `Start`.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
To start the programme of the **second day** of **week 4**:
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Alternatively, you can go to the `View plan` menu to look at all the programmes and select the one you'd like to start.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
Go to the `View plan` menu:
|
||||||
|

|
||||||
|
|
||||||
|
Select the programme to start it:
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The format of the `View menu` is `w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})`.
|
||||||
|
|
||||||
|
For example `w6d1(r:6|w:3|x2)` means:
|
||||||
|
`it's the programme of day 1 on week 6`,
|
||||||
|
`it consists of running for 6 minutes`,
|
||||||
|
`followed by walking for 3`,
|
||||||
|
`done 2 times back to back`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Show the time:
|
||||||
|
|
||||||
|
If you ever need to peek at the time, just press the middle (or only) physical button on the watch:
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This app was hacked together in a day with no JS knowledge.
|
||||||
|
It's probably inefficient and buggy, but it does what I needed it to do: allow me to follow the C25K programme without a phone.
|
||||||
|
|
||||||
|
The app was designed with a Bangle.js 1 in mind, as that's the one I have.
|
||||||
|
It *should* work fine on the Bangle.js 2, but I couldn't test it on real hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Made with <3 by [Erovia](https://github.com/Erovia)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4X/AoPk9G9gsj14lZhWq0AEBgtVqALmhQJBAQMFBIICCBc4ADBQYLnAQQKEBcibETQIABHggLiAEQqEh/wgACCBcpXDBAIKDBcqJDh//BQYLkHwg7GBcY7FU5ALgAEQA="))
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
var week = 1;
|
||||||
|
var day = 1;
|
||||||
|
var time;
|
||||||
|
|
||||||
|
var loop; // To store how many times we will have to do a countdown
|
||||||
|
var counter = 0; // To keep count of how many iterations we have in the current countdown
|
||||||
|
var currentMode; // Either "run" or "walk"
|
||||||
|
var mainInterval; // Ticks every second, checking if a new countdown is needed
|
||||||
|
var activityInterval; // Ticks every second, doing the countdown
|
||||||
|
var buttonWatch; // Watch for button presses
|
||||||
|
|
||||||
|
function outOfTime() {
|
||||||
|
// Buzz 3 times on state transitions
|
||||||
|
Bangle.buzz(500)
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||||
|
.then(() => Bangle.buzz(500))
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||||
|
.then(() => Bangle.buzz(500));
|
||||||
|
|
||||||
|
// Once we're done
|
||||||
|
if (loop == 0) {
|
||||||
|
clearWatch(buttonWatch); // Don't watch for button presses anymore
|
||||||
|
g.setBgColor("#75C0E0"); // Blue background for the "Done" text
|
||||||
|
g.clear();
|
||||||
|
g.drawString("Done", g.getWidth()/2, g.getHeight()/2); // Write "Done" to screen
|
||||||
|
g.reset();
|
||||||
|
setTimeout(E.showMenu, 5000, mainmenu); // Show the main menu again after 5secs
|
||||||
|
clearInterval(mainInterval); // Stop the main interval from starting a new activity
|
||||||
|
mainInterval = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countDown() {
|
||||||
|
text = (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text
|
||||||
|
if (time) text += "\n" + time;
|
||||||
|
g.clear();
|
||||||
|
g.setFontAlign(0,0); // center font
|
||||||
|
if (process.env.HWVERSION == 2) { // To accomodate the Bangle.js 2 screen
|
||||||
|
if (time) {
|
||||||
|
g.setFont("6x8",5); // when time is shown
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setFont("6x8",7); // when time is not shown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setFont("6x8",8); // bitmap font, 8x magnified
|
||||||
|
}
|
||||||
|
g.drawString(text, g.getWidth() / 2, g.getHeight() / 2); // draw the current mode and seconds
|
||||||
|
Bangle.setLCDPower(1); // keep the watch LCD lit up
|
||||||
|
|
||||||
|
counter--; // Reduce the seconds
|
||||||
|
|
||||||
|
// If the current activity is done
|
||||||
|
if (counter < 0) {
|
||||||
|
clearInterval(activityInterval);
|
||||||
|
activityInterval = undefined;
|
||||||
|
outOfTime();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer(w, d) {
|
||||||
|
// If something is already running, do nothing
|
||||||
|
if (activityInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switches between the two modes
|
||||||
|
if (!currentMode || currentMode === "walk") {
|
||||||
|
currentMode = "run";
|
||||||
|
counter = PLAN[w][d].run * 60;
|
||||||
|
g.setBgColor("#ff5733");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentMode = "walk";
|
||||||
|
counter = PLAN[w][d].walk * 60;
|
||||||
|
g.setBgColor("#4da80a");
|
||||||
|
}
|
||||||
|
|
||||||
|
countDown();
|
||||||
|
if (!activityInterval) {
|
||||||
|
loop--; // Reduce the number of iterations
|
||||||
|
activityInterval = setInterval(countDown, 1000); // Start a new activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTime() {
|
||||||
|
if (time) return; // If clock is already shown, don't do anything even if the button was pressed again
|
||||||
|
// Get the time and format it with a leading 0 if necessary
|
||||||
|
var d = new Date();
|
||||||
|
var h = d.getHours();
|
||||||
|
var m = d.getMinutes();
|
||||||
|
time = h + ":" + m.toString().padStart(2, 0);
|
||||||
|
setTimeout(function() { time = undefined; }, 5000); // Hide clock after 5secs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the PLAN menu
|
||||||
|
function populatePlan() {
|
||||||
|
for (var i = 0; i < PLAN.length; i++) {
|
||||||
|
for (var j = 0; j < PLAN[i].length; j++) {
|
||||||
|
// Ever line will have the following format:
|
||||||
|
// w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})
|
||||||
|
var name = "w" + (i + 1) + "d" + (j + 1);
|
||||||
|
if (process.env.HWVERSION == 2) {
|
||||||
|
name += "\n"; // Print in 2 lines to accomodate the Bangle.js 2 screen
|
||||||
|
}
|
||||||
|
name += "(r:" + PLAN[i][j].run;
|
||||||
|
if ("walk" in PLAN[i][j]) name += "|w:" + PLAN[i][j].walk;
|
||||||
|
if ("repetition" in PLAN[i][j]) name += "|x" + PLAN[i][j].repetition;
|
||||||
|
name += ")";
|
||||||
|
// Each menu item will have a function that start the program at the selected day
|
||||||
|
planmenu[name] = getFunc(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to generate functions for the PLAN menu
|
||||||
|
function getFunc(i, j) {
|
||||||
|
return function() {
|
||||||
|
week = i + 1;
|
||||||
|
day = j + 1;
|
||||||
|
startActivity();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startActivity() {
|
||||||
|
var w = week - 1;
|
||||||
|
var d = day - 1;
|
||||||
|
|
||||||
|
if ("walk" in PLAN[w][d]) {
|
||||||
|
loop = PLAN[w][d].repetition * 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loop = 1;
|
||||||
|
}
|
||||||
|
E.showMenu(); // Hide the main menu
|
||||||
|
buttonWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press
|
||||||
|
mainInterval = setInterval(function() {startTimer(w, d);}, 1000); // Check every second if we need to do something
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLAN = [
|
||||||
|
[
|
||||||
|
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||||
|
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||||
|
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||||
|
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||||
|
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 2, "walk": 2, "repetition": 5},
|
||||||
|
{"run": 2.5, "walk": 2.5, "repetition": 4},
|
||||||
|
{"run": 2.5, "walk": 2.5, "repetition": 4},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 3, "walk": 2, "repetition": 5},
|
||||||
|
{"run": 3, "walk": 2, "repetition": 5},
|
||||||
|
{"run": 4, "walk": 2.5, "repetition": 3},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 5, "walk": 2, "repetition": 3},
|
||||||
|
{"run": 8, "walk": 5, "repetition": 2},
|
||||||
|
{"run": 20},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 6, "walk": 3, "repetition": 2},
|
||||||
|
{"run": 10, "walk": 3, "repetition": 2},
|
||||||
|
{"run": 25},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 25},
|
||||||
|
{"run": 25},
|
||||||
|
{"run": 25},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"run": 30},
|
||||||
|
{"run": 30},
|
||||||
|
{"run": 30},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Main menu
|
||||||
|
var mainmenu = {
|
||||||
|
"" : {
|
||||||
|
"title" : "-- C25K --"
|
||||||
|
},
|
||||||
|
"Week" : {
|
||||||
|
value : week,
|
||||||
|
min:1,max:PLAN.length,step:1,
|
||||||
|
onchange : v => { week = v; }
|
||||||
|
},
|
||||||
|
"Day" : {
|
||||||
|
value : day,
|
||||||
|
min:1,max:3,step:1,
|
||||||
|
onchange : v => { day = v; }
|
||||||
|
},
|
||||||
|
"View plan" : function() { E.showMenu(planmenu); },
|
||||||
|
"Start" : function() { startActivity(); },
|
||||||
|
"Exit" : function() { load(); },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plan view
|
||||||
|
var planmenu = {
|
||||||
|
"" : {
|
||||||
|
title : "-- Plan --",
|
||||||
|
back : function() { E.showMenu(mainmenu);},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate the PLAN menu view
|
||||||
|
populatePlan();
|
||||||
|
// Actually display the menu
|
||||||
|
E.showMenu(mainmenu);
|
||||||
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"id": "c25k",
|
||||||
|
"name": "C25K",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Unofficial app for the Couch to 5k training plan",
|
||||||
|
"readme": "README.md",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "running,c25k,tool,outdoors,exercise",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS",
|
||||||
|
"BANGLEJS2"
|
||||||
|
],
|
||||||
|
"storage": [
|
||||||
|
{"name": "c25k.app.js", "url": "app.js"},
|
||||||
|
{"name": "c25k.img", "url": "app-icon.js", "evaluate": true}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{"url": "c25k-scrn1.png"},
|
||||||
|
{"url": "c25k-scrn2.png"},
|
||||||
|
{"url": "c25k-scrn3.png"},
|
||||||
|
{"url": "c25k-scrn4.png"},
|
||||||
|
{"url": "c25k-scrn5.png"},
|
||||||
|
{"url": "c25k-scrn6.png"},
|
||||||
|
{"url": "c25k-scrn7.png"}
|
||||||
|
]
|
||||||
|
}
|
||||||