Add sit ups

Add more feedback to the user about the exercises
Clean up code
pull/1296/head
Marco Heiming 2022-01-14 09:47:26 +01:00
parent b96f9bf7e8
commit db0e55b18e
3 changed files with 80 additions and 55 deletions

View File

@ -1 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Add sit ups
Add more feedback to the user about the exercises
Clean up code

View File

@ -2,7 +2,7 @@
Can automatically track exercises while wearing the Bangle.js watch. Can automatically track exercises while wearing the Bangle.js watch.
Currently only push ups and curls are supported. Currently only push ups, curls and sit ups are supported.
## Disclaimer ## Disclaimer
@ -23,7 +23,7 @@ Press stop to end your exercise.
## TODO ## TODO
* Add other exercise types: * Add other exercise types:
* Rope jumps * Rope jumps
* Sit ups * Star jumps
* ... * ...
* Save exercise summaries to file system * Save exercise summaries to file system
* Configure daily goal for exercises * Configure daily goal for exercises

View File

@ -25,22 +25,32 @@ let exerciseType = {
const exerciseTypes = [{ const exerciseTypes = [{
"id": "pushup", "id": "pushup",
"name": "push ups", "name": "push ups",
"useYaxe": true, "useYaxis": true,
"useZaxe": false, "useZaxis": false,
"thresholdY": 2500, "threshold": 2500,
"thresholdMinTime": 1400, // mininmal time between two push ups in ms "thresholdMinTime": 800, // mininmal time between two push ups in ms
"thresholdMaxTime": 5000, // maximal time between two push ups in ms "thresholdMaxTime": 5000, // maximal time between two push ups in ms
"thresholdMinDurationTime": 700, // mininmal duration of half a push ups in ms "thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms
}, },
{ {
"id": "curl", "id": "curl",
"name": "curls", "name": "curls",
"useYaxe": true, "useYaxis": true,
"useZaxe": false, "useZaxis": false,
"thresholdY": 2500, "threshold": 2500,
"thresholdMinTime": 1000, // mininmal time between two curls in ms "thresholdMinTime": 800, // mininmal time between two curls in ms
"thresholdMaxTime": 5000, // maximal time between two curls in ms "thresholdMaxTime": 5000, // maximal time between two curls in ms
"thresholdMinDurationTime": 500, // mininmal duration of half a push ups in ms "thresholdMinDurationTime": 500, // mininmal duration of half a curl in ms
},
{
"id": "situp",
"name": "sit ups",
"useYaxis": false,
"useZaxis": true,
"threshold": 3500,
"thresholdMinTime": 800, // mininmal time between two sit ups in ms
"thresholdMaxTime": 5000, // maximal time between two sit ups in ms
"thresholdMinDurationTime": 500, // mininmal duration of half a sit up in ms
} }
]; ];
let exerciseCounter = 0; let exerciseCounter = 0;
@ -66,7 +76,7 @@ function showMainMenu() {
}; };
exerciseTypes.forEach(function(et) { exerciseTypes.forEach(function(et) {
menu["Do " + et.name] = function() { menu[et.name] = function() {
exerciseType = et; exerciseType = et;
E.showMenu(); E.showMenu();
startTraining(); startTraining();
@ -81,8 +91,8 @@ function showMainMenu() {
value: exerciseCounter + " " + exerciseType.name value: exerciseCounter + " " + exerciseType.name
}; };
} }
menu.Exit = function() { menu.exit = function() {
load(); load();
}; };
E.showMenu(menu); E.showMenu(menu);
@ -91,11 +101,11 @@ function showMainMenu() {
function accelHandler(accel) { function accelHandler(accel) {
if (!exerciseType) return; if (!exerciseType) return;
const t = Math.round(new Date().getTime()); // time in ms const t = Math.round(new Date().getTime()); // time in ms
const y = exerciseType.useYaxe ? accel.y * 8192 : 0; const y = exerciseType.useYaxis ? accel.y * 8192 : 0;
const z = exerciseType.useZaxe ? accel.z * 8192 : 0; const z = exerciseType.useZaxis ? accel.z * 8192 : 0;
//console.log(t, y, z); //console.log(t, y, z);
if (exerciseType.useYaxe) { if (exerciseType.useYaxis) {
while (historyY.length > avgSize) while (historyY.length > avgSize)
historyY.shift(); historyY.shift();
@ -109,7 +119,7 @@ function accelHandler(accel) {
} }
} }
if (exerciseType.useYaxe) { if (exerciseType.useZaxis) {
while (historyZ.length > avgSize) while (historyZ.length > avgSize)
historyZ.shift(); historyZ.shift();
@ -124,72 +134,64 @@ function accelHandler(accel) {
} }
// slope for Y // slope for Y
if (exerciseType.useYaxe) { if (exerciseType.useYaxis) {
let l = historyAvgY.length; let l = historyAvgY.length;
if (l > 1) { if (l > 1) {
const p1 = historyAvgY[l - 2]; const p1 = historyAvgY[l - 2];
const p2 = historyAvgY[l - 1]; const p2 = historyAvgY[l - 1];
const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
// we use this data for exercises which can be detected by using Y axis data // we use this data for exercises which can be detected by using Y axis data
switch (exerciseType.id) { isValidExercise(slopeY, t);
case "pushup":
isValidYAxisExercise(slopeY, t);
break;
case "curl":
isValidYAxisExercise(slopeY, t);
break;
}
} }
} }
// slope for Z // slope for Z
if (exerciseType.useZaxe) { if (exerciseType.useZaxis) {
l = historyAvgZ.length; l = historyAvgZ.length;
if (l > 1) { if (l > 1) {
const p1 = historyAvgZ[l - 2]; const p1 = historyAvgZ[l - 2];
const p2 = historyAvgZ[l - 1]; const p2 = historyAvgZ[l - 1];
const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); const slopeZ = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
historyAvgZ.shift(); // we use this data for some exercises which can be detected by using Z axis data
historySlopeZ.push([p2[0] - p1[0], slopeZ]); isValidExercise(slopeZ, t);
// TODO: we can use this data for some exercises which can be detected by using Z axis data
} }
} }
} }
/* /*
* Check if slope value of Y-axis data looks like an exercise * Check if slope value of Y-axis or Z-axis data (depending on exercise type) looks like an exercise
* *
* In detail we look for slop values which are bigger than the configured Y threshold for the current exercise * In detail we look for slop values which are bigger than the configured threshold for the current exercise type
* Then we look for two consecutive slope values of which one is above 0 and the other is below zero. * Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
* If we find one pair of these values this could be part of one exercise. * If we find one pair of these values this could be part of one exercise.
* Then we look for a pair of values which cross the zero from the otherwise direction * Then we look for a pair of values which cross the zero from the otherwise direction
*/ */
function isValidYAxisExercise(slopeY, t) { function isValidExercise(slope, t) {
if (!exerciseType) return; if (!exerciseType) return;
const thresholdY = exerciseType.thresholdY; const threshold = exerciseType.threshold;
const historySlopeValues = exerciseType.useYaxis ? historySlopeY : historySlopeZ;
const thresholdMinTime = exerciseType.thresholdMinTime; const thresholdMinTime = exerciseType.thresholdMinTime;
const thresholdMaxTime = exerciseType.thresholdMaxTime; const thresholdMaxTime = exerciseType.thresholdMaxTime;
const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime; const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
const exerciseName = exerciseType.name; const exerciseName = exerciseType.name;
if (Math.abs(slopeY) >= thresholdY) {
historyAvgY.shift();
historySlopeY.push([t, slopeY]);
//console.log(t, Math.abs(slopeY));
const lSlopeY = historySlopeY.length; if (Math.abs(slope) >= threshold) {
if (lSlopeY > 1) { historySlopeValues.push([t, slope]);
const p1 = historySlopeY[lSlopeY - 1][1]; //console.log(t, Math.abs(slope));
const p2 = historySlopeY[lSlopeY - 2][1];
const lSlopeHistory = historySlopeValues.length;
if (lSlopeHistory > 1) {
const p1 = historySlopeValues[lSlopeHistory - 1][1];
const p2 = historySlopeValues[lSlopeHistory - 2][1];
if (p1 > 0 && p2 < 0) { if (p1 > 0 && p2 < 0) {
if (lastZeroPassCameFromPositive == false) { if (lastZeroPassCameFromPositive == false) {
lastExerciseHalfCompletionTime = t; lastExerciseHalfCompletionTime = t;
//console.log(t, exerciseName + " half complete..."); console.log(t, exerciseName + " half complete...");
layout.progress.label = "½"; layout.progress.label = "½";
layout.recording.label = "TRAINING";
g.clear(); g.clear();
layout.render(); layout.render();
} }
@ -201,7 +203,7 @@ function isValidYAxisExercise(slopeY, t) {
if (lastZeroPassCameFromPositive == true) { if (lastZeroPassCameFromPositive == true) {
const tDiffLastExercise = t - lastExerciseCompletionTime; const tDiffLastExercise = t - lastExerciseCompletionTime;
const tDiffStart = t - tStart; const tDiffStart = t - tStart;
//console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart)); console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
// check minimal time between exercises: // check minimal time between exercises:
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) { if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
@ -219,22 +221,36 @@ function isValidYAxisExercise(slopeY, t) {
layout.count.label = exerciseCounter; layout.count.label = exerciseCounter;
layout.progress.label = ""; layout.progress.label = "";
layout.recording.label = "Good!";
g.clear(); g.clear();
layout.render(); layout.render();
if (settings.buzz) if (settings.buzz)
Bangle.buzz(100, 0.4); Bangle.buzz(200, 0.5);
} else { } else {
//console.log(t, exerciseName + " to quick for duration time threshold!"); console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime
lastExerciseCompletionTime = t; lastExerciseCompletionTime = t;
layout.recording.label = "Go slower!";
g.clear();
layout.render();
} }
} else { } else {
//console.log(t, exerciseName + " to slow for time threshold!"); console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime
lastExerciseCompletionTime = t; lastExerciseCompletionTime = t;
layout.recording.label = "Go faster!";
g.clear();
layout.render();
} }
} else { } else {
//console.log(t, exerciseName + " to quick for time threshold!"); console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
lastExerciseCompletionTime = t; lastExerciseCompletionTime = t;
layout.recording.label = "Go slower!";
g.clear();
layout.render();
} }
} }
@ -267,6 +283,7 @@ function startTraining() {
if (recordActive) return; if (recordActive) return;
g.clear(1); g.clear(1);
reset(); reset();
Bangle.setLCDTimeout(0); // force LCD on
Bangle.setHRMPower(1, "banglexercise"); Bangle.setHRMPower(1, "banglexercise");
if (!hrtValue) hrtValue = "..."; if (!hrtValue) hrtValue = "...";
@ -285,7 +302,7 @@ function startTraining() {
type: "txt", type: "txt",
id: "count", id: "count",
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8", font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
label: 10, label: exerciseCounter,
pad: 5 pad: 5
}, },
{ {
@ -337,11 +354,16 @@ function startTraining() {
layout.render(); layout.render();
Bangle.setPollInterval(80); // 12.5 Hz Bangle.setPollInterval(80); // 12.5 Hz
Bangle.on('accel', accelHandler);
tStart = new Date().getTime(); tStart = new Date().getTime();
recordActive = true; recordActive = true;
if (settings.buzz) if (settings.buzz)
Bangle.buzz(200, 1); Bangle.buzz(200, 1);
// delay start a little bit
setTimeout(() => {
Bangle.on('accel', accelHandler);
}, 1000);
} }
function stopTraining() { function stopTraining() {