var week = 1; // Stock plan: programme week var day = 1; // Stock plan: programe day var run = 1; // Custom plan: running time var walk = 0; // Custom plan: walking time var reps = 1; // Custom plan: repetition count var time; // To store the date var loop; // To store how many times we will have to do a countdown var rep; // The current rep counter var counter; // The seconds counter 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 extraInfoWatch; // Watch for button presses to show additional info var paused = false; // Track pause state var pauseOrResumeWatch; // Watch for button presses to pause/resume countdown var defaultFontSize = (process.env.HWVERSION == 2) ? 7 : 9; // Default font size, Banglejs 2 has smaller var activityBgColour; // Background colour of current activity var currentActivity; // To store the current activity function outOfTime() { buzz(); // Once we're done if (loop == 0) { clearWatch(extraInfoWatch); // Don't watch for button presses anymore if (pauseOrResumeWatch) clearWatch(pauseOrResumeWatch); // Don't watch for button presses anymore g.setBgColor("#75C0E0"); // Blue background for the "Done" text drawText("Done", defaultFontSize); // 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; currentMode = undefined; } } // Buzz 3 times on state transitions function buzz() { 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)); } function drawText(text, size){ g.clear(); g.setFontAlign(0, 0); // center font g.setFont("6x8", size); g.drawString(text, g.getWidth() / 2, g.getHeight() / 2); } function countDown() { if (!paused) { var text = ""; var size = defaultFontSize; if (time) { var total = ("walk" in currentActivity) ? currentActivity.repetition : 1; text += rep + "/" + total + "\n"; // Show the current/total rep count when time is shown size -= 2; // Use smaller font size to fit everything nicely on the screen } text += (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text if (time) text += "\n" + time; drawText(text, size); // 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() { // If something is already running, do nothing if (activityInterval) return; // Switches between the two modes if (!currentMode || currentMode === "walk") { currentMode = "run"; rep++; // Increase the rep counter every time a "run" activity starts counter = currentActivity.run * 60; activityBgColour = "#ff5733"; // Red background for running } else { currentMode = "walk"; counter = currentActivity.walk * 60; activityBgColour = "#4da80a"; // Green background for walking } g.setBgColor(activityBgColour); 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 activePlan menu function getFunc(i, j) { return function() { currentActivity = PLAN[i][j]; startActivity(); }; } function startActivity() { loop = ("walk" in currentActivity) ? currentActivity.repetition * 2 : 1; rep = 0; E.showMenu(); // Hide the main menu extraInfoWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press if (process.env.HWVERSION == 1) pauseOrResumeWatch = setWatch(pauseOrResumeActivity, BTN1, {repeat: true}); // Pause or resume on button press (Bangle.js 1 only) buzz(); mainInterval = setInterval(function() {startTimer();}, 1000); // Check every second if we need to do something } // Pause or resume current activity function pauseOrResumeActivity() { paused = !paused; buzz(); if (paused) { g.setBgColor("#fdd835"); // Yellow background for pause screen drawText("Paused", (process.env.HWVERSION == 2) ? defaultFontSize - 3 : defaultFontSize - 2); // Although the font size is configured here, this feature does not work on Bangle.js 2 as the only physical button is tied to the extra info screen already } else { g.setBgColor(activityBgColour); } } 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}, ], ]; var customRun = {"run": 1}; // 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); }, "Custom run": function() { E.showMenu(custommenu); }, "Start": function() { currentActivity = PLAN[week - 1][day -1]; startActivity(); }, "Exit": function() { load(); }, }; // Plan view var planmenu = { "": { title: "-- Plan --" }, "< Back": function() { E.showMenu(mainmenu);}, }; // Custom view var custommenu = { "": { title : "-- Cust. run --" }, "< Back": function() { E.showMenu(mainmenu);}, "Run (mins)": { value: run, min: 1, max: 150, step: 1, wrap: true, onchange: v => { customRun.run = v; } }, "Walk (mins)": { value: walk, min: 0, max: 10, step: 1, onchange: v => { if (v > 0) { if (reps == 1) { reps = 2; } // Walking only makes sense with multiple reps customRun.repetition = reps; customRun.walk = v; } else { // If no walking, delete both the reps and walk data delete customRun.repetition; delete customRun.walk; } walk = v; } }, "Reps": { value: reps, min: 1, max: 10, step: 1, onchange: v => { if (v > 1) { if (walk == 0) { walk = 1; } // Multiple reps only make sense with walking phases customRun.walk = walk; customRun.repetition = v; } else { // If no multiple reps, delete both the reps and walk data delete customRun.repetition; delete customRun.walk; } reps = v; } }, "Start": function() { currentActivity = customRun; startActivity(); } }; // Populate the activePlan menu view populatePlan(); // Actually display the menu E.showMenu(mainmenu);