diff --git a/apps/pushups/README.md b/apps/pushups/README.md index 60c0f315e..df2fe5358 100644 --- a/apps/pushups/README.md +++ b/apps/pushups/README.md @@ -1,7 +1,30 @@ # Pushups -Train for push ups using the accelerometer. It should buzz everytime you go up and down. -Swipe the screen to set the countdown value. +Pushups is an exercising app with a twist : the accelerometer. + +![Screenshot](shot_pushups.png) + +I initially just wanted a pushups counter but i kind of got out of hand. + +The accelerometer will work on the following exercises : + +- pushups +- situps +- squats +- jumping jacks + +For each of them it will try to detect two positions (for example up and down for pushups) +and buzz on each change. You can set up a target counter for each exercise. + +Precision is not 100% but it's good for me and kind of increases my motivation. + +Other activities are time based like + +- plank +- rest + + +Define your training routine, set a duration and you're ready to go. ## Creator diff --git a/apps/pushups/app.js b/apps/pushups/app.js index 3d7ff9b98..1d2d948ca 100644 --- a/apps/pushups/app.js +++ b/apps/pushups/app.js @@ -39,6 +39,14 @@ const IMAGES = [ // number of movements or duration required for each activity const DEFAULTS = [7, 10, 10, 30, 15, 30]; +// detector sensitivity for each activity +// (less is more reactive but more sensitive to noise) +const COUNTS = [6, 10, 6, 6, 6, 5]; + +function default_config() { + return {duration: 10*60, routine: default_routine()}; +} + function default_routine() { let routine = []; DEFAULTS.forEach((d, i) => { @@ -94,11 +102,8 @@ const DETECTORS = [ ]; class FitnessStatus { - constructor(duration) { - this.routine = require("Storage").readJSON("pushups.cfg", true); - if (this.routine === undefined) { - this.routine = default_routine(); - } + constructor(config) { + this.routine = config.routine; this.routine_step = 0; this.current_status = 0; this.buzzing = false; @@ -108,7 +113,7 @@ class FitnessStatus { this.remaining = this.routine[this.routine_step][1]; this.activity_start = getTime(); this.starting_time = this.activity_start; - this.duration = duration; + this.duration = config.duration; this.completed = false; } @@ -202,6 +207,7 @@ class FitnessStatus { } let activity = this.routine[this.routine_step][0]; let detector = DETECTORS[activity]; + let status = this; if (detector === null) { // it's time based let activity_duration = getTime() - this.activity_start; @@ -221,8 +227,7 @@ class FitnessStatus { if (new_status != this.current_status) { this.counts_in_opposite_status += 1; - // we consider 6 counts to smooth out noise - if (this.counts_in_opposite_status == 6) { + if (this.counts_in_opposite_status == COUNTS[activity]) { this.current_status = 1 - this.current_status; this.counts_in_opposite_status = 0; if (this.current_status == 0) { @@ -243,11 +248,9 @@ class FitnessStatus { } } -let status = new FitnessStatus(10 * 60); -// status.display(); - -function start_routine() { +function start_routine(config) { + let status = new FitnessStatus(config); Bangle.accelWr(0x18,0b01110100); // off, +-8g // NOTE: this code is taken from 'accelrec' app Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter @@ -285,8 +288,8 @@ function start_routine() { } -function edit_menu() { - let routine = status.routine; +function edit_menu(config) { + let routine = config.routine; E.showScroller({ h : 60, @@ -308,21 +311,20 @@ function edit_menu() { select : function(idx) { if (idx == routine.length + 1) { E.showScroller(); - require("Storage").writeJSON("pushups.cfg", routine); - start_routine(); + set_duration(config); } else if (idx == routine.length) { E.showScroller(); - add_activity(); + add_activity(config); } else { E.showScroller(); - set_counter(idx); + set_counter(config, idx); } } }); } -function add_activity() { +function add_activity(config) { E.showScroller({ h : 60, c : IMAGES.length, @@ -332,19 +334,19 @@ function add_activity() { g.drawImage(img, r.x + r.w / 3, r.y + 10); }, select : function(idx) { - let new_index = status.routine.length; - status.routine.push([idx, 10]); + let new_index = config.routine.length; + config.routine.push([idx, 10]); E.showScroller(); - set_counter(new_index); + set_counter(config, new_index); } }); } -function set_counter(index) { +function set_counter(config, index) { let w = g.getWidth(); let h = g.getHeight(); - let counter = status.routine[index][1]; + let counter = config.routine[index][1]; function display() { g.clear(); g.setFont("6x8:2") @@ -378,18 +380,69 @@ function set_counter(index) { }); Bangle.on("touch", function(button, xy) { if (counter == 0) { - status.routine.splice(index, 1); + config.routine.splice(index, 1); } else { - status.routine[index][1] = counter; + config.routine[index][1] = counter; } Bangle.removeAllListeners("touch"); Bangle.removeAllListeners("swipe"); - edit_menu(); + edit_menu(config); }); } -function main_menu() { + +//TODO: factorize code with set_counter +function set_duration(config) { + let w = g.getWidth(); + let h = g.getHeight(); + let duration = config.duration; + let minutes = Math.floor(duration / 60); + function display() { + g.clear(); + g.setColor(0); + g.setFont("6x8:2") + .setFontAlign(1, 0) + .drawString("+1", w, h/2); + g.setFontAlign(-1, 0) + .drawString("-1", 0, h/2); + g.setFontAlign(0, -1) + .drawString("+5", w/2, 0); + g.setFontAlign(0, 1) + .drawString("-5", w/2, h); + g.drawString("minutes", w/2, h-40); + g.setFont("Vector:64") + .setFontAlign(0, 0) + .drawString(""+minutes, w/2, h/2); + } + display(); + Bangle.on("swipe", function (directionLR, directionUD) { + if (directionUD == -1) { + minutes += 5; + } else if (directionUD == 1) { + minutes -= 5; + } else if (directionLR == -1) { + minutes -= 1; + } else if (directionLR == 1) { + minutes += 1; + } + if (minutes < 1) { + minutes = 1; + } + display(); + }); + Bangle.on("touch", function(button, xy) { + Bangle.removeAllListeners("touch"); + Bangle.removeAllListeners("swipe"); + config.duration = minutes * 60; + //TODO: don't write if no change + require("Storage").writeJSON("pushups.cfg", config); + start_routine(config); + }); + +} + +function main_menu(config) { let w = g.getWidth(); let h = g.getHeight(); g.clear(); @@ -409,7 +462,7 @@ function main_menu() { .setFontAlign(0, 0) .drawString("Edit", w/2, 3*h/4); Bangle.removeAllListeners("touch"); - edit_menu(); + edit_menu(config); } else if (xy.y < h/2-10) { g.fillRect(10, 10, w-10, h/2-10); g.setColor(1, 1, 1) @@ -417,10 +470,16 @@ function main_menu() { .setFontAlign(0, 0) .drawString("Start", w/2, h/4); Bangle.removeAllListeners("touch"); - start_routine(); + set_duration(config); } }) } -main_menu(); +let config = require("Storage").readJSON("pushups.cfg", true); + +if (config === undefined) { + config = default_config(); +} + +main_menu(config); diff --git a/apps/pushups/metadata.json b/apps/pushups/metadata.json index 64a445067..dc8dd6989 100644 --- a/apps/pushups/metadata.json +++ b/apps/pushups/metadata.json @@ -9,6 +9,7 @@ "type": "app", "tags": "health", "supports": ["BANGLEJS2"], + "screenshots": [{"url":"shot_pushups.png"}, {"url":"shot_squats.png"}, {"url":"shot_menu.png"}], "readme": "README.md", "storage": [ {"name":"pushups.app.js","url":"app.js"}, diff --git a/apps/pushups/shot_menu.png b/apps/pushups/shot_menu.png new file mode 100644 index 000000000..b7f44b6e2 Binary files /dev/null and b/apps/pushups/shot_menu.png differ diff --git a/apps/pushups/shot_pushups.png b/apps/pushups/shot_pushups.png new file mode 100644 index 000000000..3f22ee8d1 Binary files /dev/null and b/apps/pushups/shot_pushups.png differ diff --git a/apps/pushups/shot_squats.png b/apps/pushups/shot_squats.png new file mode 100644 index 000000000..de3ac7bdd Binary files /dev/null and b/apps/pushups/shot_squats.png differ