Moved exercise threshold values to the exercise type configuration & initial support for curls

master
Marco Heiming 2022-01-11 12:38:19 +01:00
parent c9fa1b8f65
commit 83aa32ee84
2 changed files with 98 additions and 59 deletions

View File

@ -2,7 +2,13 @@
Can automatically track exercises while wearing the Bangle.js watch. Can automatically track exercises while wearing the Bangle.js watch.
Currently only push ups are supported. Currently only push ups and curls are supported.
## Disclaimer
This app is very experimental.
It could be and is likely that the threshold values for detecting exercises do not work for everyone.
Therefore it would be great if we could improve this app together :-)
## Usage ## Usage
@ -17,7 +23,6 @@ Press stop to end your exercise.
## TODO ## TODO
* Add other exercise types: * Add other exercise types:
* Rope jumps * Rope jumps
* Curls
* Sit ups * Sit ups
* ... * ...
* Save exercises to file system * Save exercises to file system

View File

@ -15,26 +15,35 @@ let lastZeroPassTime = 0;
let lastExerciseCmpltTime = 0; let lastExerciseCmpltTime = 0;
let exerciseType = { let exerciseType = {
"id": "",
"name": "" "name": ""
}; };
// add new exercises here:
const exerciseTypes = [{ const exerciseTypes = [{
"id": "pushup", "id": "pushup",
"name": "Push ups", "name": "Push ups",
"useYaxe": true, "useYaxe": true,
"useZaxe": false "useZaxe": false,
} // add other exercises here "thresholdY": 2500,
"thresholdTime": 1400 // mininmal time between two push ups
},
{
"id": "curl",
"name": "Curls",
"useYaxe": true,
"useZaxe": false,
"thresholdY": 2500,
"thresholdTime": 1000 // mininmal time between two curls
}
]; ];
let exerciseCounter = 0; let exerciseCounter = 0;
let layout; let layout;
let recordActive = false; let recordActive = false;
/** // Size of average window for data analysis
* Thresholds
*/
const avgSize = 6; const avgSize = 6;
const pushUpThresholdY = 2500;
const pushUpThresholdTime = 1400; // mininmal time between two push ups
let hrtValue; let hrtValue;
@ -55,7 +64,9 @@ function showMainMenu() {
}); });
if (exerciseCounter > 0) { if (exerciseCounter > 0) {
menu["----"] = {}; menu["----"] = {
value: ""
};
menu["Last:"] = { menu["Last:"] = {
value: exerciseCounter + " " + exerciseType.name value: exerciseCounter + " " + exerciseType.name
}; };
@ -101,19 +112,18 @@ function accelHandler(accel) {
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 mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
if (Math.abs(mY) >= pushUpThresholdY) {
historyAvgY.shift();
historySlopeY.push([t, mY]);
//console.log(t, Math.abs(mY));
const lMY = historySlopeY.length; // we use this data for exercises which can be detected by using Y axis data
if (lMY > 1) { switch (exerciseType.id) {
const pMY1 = historySlopeY[lMY - 2][1]; case "pushup":
const pMY2 = historySlopeY[lMY - 1][1]; isValidYAxisExercise(slopeY, t);
isValidPushUp(pMY1, pMY2, t); break;
} case "curl":
isValidYAxisExercise(slopeY, t);
break;
} }
} }
} }
@ -123,18 +133,42 @@ function accelHandler(accel) {
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 mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]);
historyAvgZ.shift(); historyAvgZ.shift();
historySlopeZ.push([p2[0] - p1[0], mZ]); historySlopeZ.push([p2[0] - p1[0], slopeZ]);
// TODO: we can use this data for some exercises which can be detected by using Z axis data
} }
} }
} }
function isValidPushUp(p1, p2, t) { /*
if (p1 > 0 && p2 < 0) { * Check if slope value of Y-axis data looks like an exercise
*
* In detail we look for slop values which are bigger than the configured Y threshold for the current exercise
* 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.
* Then we look for a pair of values which cross the zero from the otherwise direction
*/
function isValidYAxisExercise(slopeY, t) {
if (!exerciseType) return;
const thresholdY = exerciseType.thresholdY;
const thresholdTime = exerciseType.thresholdTime;
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 (lSlopeY > 1) {
const p1 = historySlopeY[lSlopeY - 2][1];
const p2 = historySlopeY[lSlopeY - 1][1];
if (p1 > 0 && p2 < 0) {
if (lastZeroPassType == "-+") { if (lastZeroPassType == "-+") {
console.log(t, "Push up half complete..."); console.log(t, exerciseName + " half complete...");
layout.progress.label = "*"; layout.progress.label = "*";
layout.render(); layout.render();
@ -144,15 +178,14 @@ function isValidPushUp(p1, p2, t) {
lastZeroPassTime = t; lastZeroPassTime = t;
} }
if (p2 > 0 && p1 < 0) { if (p2 > 0 && p1 < 0) {
if (lastZeroPassType == "+-") { if (lastZeroPassType == "+-") {
// potential complete push up. Let's check the time difference... // potential complete exercise. Let's check the time difference...
const tDiffLastPushUp = t - lastExerciseCmpltTime; const tDiffLastPushUp = t - lastExerciseCmpltTime;
const tDiffStart = t - tStart; const tDiffStart = t - tStart;
console.log(t, "Push up maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart));
if ((lastExerciseCmpltTime <= 0 && tDiffStart >= pushUpThresholdTime) || tDiffLastPushUp >= pushUpThresholdTime) { if ((lastExerciseCmpltTime <= 0 && tDiffStart >= thresholdTime) || tDiffLastPushUp >= thresholdTime) {
console.log(t, "Push up complete!!!"); console.log(t, exerciseName + " complete!!!");
lastExerciseCmpltTime = t; lastExerciseCmpltTime = t;
exerciseCounter++; exerciseCounter++;
@ -161,17 +194,20 @@ function isValidPushUp(p1, p2, t) {
layout.progress.label = ""; layout.progress.label = "";
layout.render(); layout.render();
Bangle.buzz(100, 0.3); // TODO make configurable Bangle.buzz(100, 0.4); // TODO make configurable
} else { } else {
console.log(t, "Push up to quick for threshold!"); console.log(t, exerciseName + " to quick for time threshold!");
} }
} }
lastZeroPassType = "-+"; lastZeroPassType = "-+";
lastZeroPassTime = t; lastZeroPassTime = t;
} }
}
}
} }
function reset() { function reset() {
historyY = []; historyY = [];
historyZ = []; historyZ = [];
@ -252,14 +288,12 @@ function startRecording() {
}, },
] ]
}, { }, {
btns: [ btns: [{
{
label: "STOP", label: "STOP",
cb: () => { cb: () => {
stopRecording(); stopRecording();
} }
} }],
],
lazy: true lazy: true
}); });
layout.render(); layout.render();