From 47d917c22ce0a97b8442bf815f6858adc4b1261e Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Fri, 6 Sep 2024 11:00:24 +0200 Subject: [PATCH] pushups : getting ready for release --- apps/pushups/README.md | 27 +++++++- apps/pushups/app.js | 121 +++++++++++++++++++++++++--------- apps/pushups/metadata.json | 1 + apps/pushups/shot_menu.png | Bin 0 -> 2199 bytes apps/pushups/shot_pushups.png | Bin 0 -> 2887 bytes apps/pushups/shot_squats.png | Bin 0 -> 3020 bytes 6 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 apps/pushups/shot_menu.png create mode 100644 apps/pushups/shot_pushups.png create mode 100644 apps/pushups/shot_squats.png 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 0000000000000000000000000000000000000000..b7f44b6e207294846f4d9ad43c04d04e9c634a46 GIT binary patch literal 2199 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 G-hTkkB8vk6 literal 0 HcmV?d00001 diff --git a/apps/pushups/shot_squats.png b/apps/pushups/shot_squats.png new file mode 100644 index 0000000000000000000000000000000000000000..de3ac7bdd18453fc0e0755f47f29c2361601702e GIT binary patch literal 3020 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 literal 0 HcmV?d00001