pushups : getting ready for release

pull/3669/head
frederic wagner 2024-09-06 11:00:24 +02:00
parent 509064f08d
commit 47d917c22c
6 changed files with 116 additions and 33 deletions

View File

@ -1,7 +1,30 @@
# Pushups # Pushups
Train for push ups using the accelerometer. It should buzz everytime you go up and down. Pushups is an exercising app with a twist : the accelerometer.
Swipe the screen to set the countdown value.
![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 ## Creator

View File

@ -39,6 +39,14 @@ const IMAGES = [
// number of movements or duration required for each activity // number of movements or duration required for each activity
const DEFAULTS = [7, 10, 10, 30, 15, 30]; 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() { function default_routine() {
let routine = []; let routine = [];
DEFAULTS.forEach((d, i) => { DEFAULTS.forEach((d, i) => {
@ -94,11 +102,8 @@ const DETECTORS = [
]; ];
class FitnessStatus { class FitnessStatus {
constructor(duration) { constructor(config) {
this.routine = require("Storage").readJSON("pushups.cfg", true); this.routine = config.routine;
if (this.routine === undefined) {
this.routine = default_routine();
}
this.routine_step = 0; this.routine_step = 0;
this.current_status = 0; this.current_status = 0;
this.buzzing = false; this.buzzing = false;
@ -108,7 +113,7 @@ class FitnessStatus {
this.remaining = this.routine[this.routine_step][1]; this.remaining = this.routine[this.routine_step][1];
this.activity_start = getTime(); this.activity_start = getTime();
this.starting_time = this.activity_start; this.starting_time = this.activity_start;
this.duration = duration; this.duration = config.duration;
this.completed = false; this.completed = false;
} }
@ -202,6 +207,7 @@ class FitnessStatus {
} }
let activity = this.routine[this.routine_step][0]; let activity = this.routine[this.routine_step][0];
let detector = DETECTORS[activity]; let detector = DETECTORS[activity];
let status = this;
if (detector === null) { if (detector === null) {
// it's time based // it's time based
let activity_duration = getTime() - this.activity_start; let activity_duration = getTime() - this.activity_start;
@ -221,8 +227,7 @@ class FitnessStatus {
if (new_status != this.current_status) { if (new_status != this.current_status) {
this.counts_in_opposite_status += 1; this.counts_in_opposite_status += 1;
// we consider 6 counts to smooth out noise if (this.counts_in_opposite_status == COUNTS[activity]) {
if (this.counts_in_opposite_status == 6) {
this.current_status = 1 - this.current_status; this.current_status = 1 - this.current_status;
this.counts_in_opposite_status = 0; this.counts_in_opposite_status = 0;
if (this.current_status == 0) { if (this.current_status == 0) {
@ -243,11 +248,9 @@ class FitnessStatus {
} }
} }
let status = new FitnessStatus(10 * 60);
// status.display();
function start_routine(config) {
function start_routine() { let status = new FitnessStatus(config);
Bangle.accelWr(0x18,0b01110100); // off, +-8g // NOTE: this code is taken from 'accelrec' app Bangle.accelWr(0x18,0b01110100); // off, +-8g // NOTE: this code is taken from 'accelrec' app
Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter
@ -285,8 +288,8 @@ function start_routine() {
} }
function edit_menu() { function edit_menu(config) {
let routine = status.routine; let routine = config.routine;
E.showScroller({ E.showScroller({
h : 60, h : 60,
@ -308,21 +311,20 @@ function edit_menu() {
select : function(idx) { select : function(idx) {
if (idx == routine.length + 1) { if (idx == routine.length + 1) {
E.showScroller(); E.showScroller();
require("Storage").writeJSON("pushups.cfg", routine); set_duration(config);
start_routine();
} else if (idx == routine.length) { } else if (idx == routine.length) {
E.showScroller(); E.showScroller();
add_activity(); add_activity(config);
} else { } else {
E.showScroller(); E.showScroller();
set_counter(idx); set_counter(config, idx);
} }
} }
}); });
} }
function add_activity() { function add_activity(config) {
E.showScroller({ E.showScroller({
h : 60, h : 60,
c : IMAGES.length, c : IMAGES.length,
@ -332,19 +334,19 @@ function add_activity() {
g.drawImage(img, r.x + r.w / 3, r.y + 10); g.drawImage(img, r.x + r.w / 3, r.y + 10);
}, },
select : function(idx) { select : function(idx) {
let new_index = status.routine.length; let new_index = config.routine.length;
status.routine.push([idx, 10]); config.routine.push([idx, 10]);
E.showScroller(); 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 w = g.getWidth();
let h = g.getHeight(); let h = g.getHeight();
let counter = status.routine[index][1]; let counter = config.routine[index][1];
function display() { function display() {
g.clear(); g.clear();
g.setFont("6x8:2") g.setFont("6x8:2")
@ -378,18 +380,69 @@ function set_counter(index) {
}); });
Bangle.on("touch", function(button, xy) { Bangle.on("touch", function(button, xy) {
if (counter == 0) { if (counter == 0) {
status.routine.splice(index, 1); config.routine.splice(index, 1);
} else { } else {
status.routine[index][1] = counter; config.routine[index][1] = counter;
} }
Bangle.removeAllListeners("touch"); Bangle.removeAllListeners("touch");
Bangle.removeAllListeners("swipe"); 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 w = g.getWidth();
let h = g.getHeight(); let h = g.getHeight();
g.clear(); g.clear();
@ -409,7 +462,7 @@ function main_menu() {
.setFontAlign(0, 0) .setFontAlign(0, 0)
.drawString("Edit", w/2, 3*h/4); .drawString("Edit", w/2, 3*h/4);
Bangle.removeAllListeners("touch"); Bangle.removeAllListeners("touch");
edit_menu(); edit_menu(config);
} else if (xy.y < h/2-10) { } else if (xy.y < h/2-10) {
g.fillRect(10, 10, w-10, h/2-10); g.fillRect(10, 10, w-10, h/2-10);
g.setColor(1, 1, 1) g.setColor(1, 1, 1)
@ -417,10 +470,16 @@ function main_menu() {
.setFontAlign(0, 0) .setFontAlign(0, 0)
.drawString("Start", w/2, h/4); .drawString("Start", w/2, h/4);
Bangle.removeAllListeners("touch"); 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);

View File

@ -9,6 +9,7 @@
"type": "app", "type": "app",
"tags": "health", "tags": "health",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"screenshots": [{"url":"shot_pushups.png"}, {"url":"shot_squats.png"}, {"url":"shot_menu.png"}],
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"pushups.app.js","url":"app.js"}, {"name":"pushups.app.js","url":"app.js"},

BIN
apps/pushups/shot_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB