mirror of https://github.com/espruino/BangleApps
Initial version of BanglExercise
parent
594d944dc4
commit
b711b50f88
17
apps.json
17
apps.json
|
@ -5503,5 +5503,22 @@
|
|||
{"name":"limelight.settings.js","url":"limelight.settings.js"},
|
||||
{"name":"limelight.img","url":"limelight.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version":"0.01",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "sport",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"banglexercise.app.js","url":"app.js"},
|
||||
{"name":"banglexercise.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,35 @@
|
|||
# Banglexercise
|
||||
|
||||
Can automatically track exercises while wearing the Bangle.js watch.
|
||||
|
||||
Currently only push ups are supported.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Select the exercise type you want to practive and go for it!
|
||||
Press stop to end your exercise.
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## TODO
|
||||
* Add other exercise types:
|
||||
* Rope jumps
|
||||
* Curls
|
||||
* Sit ups
|
||||
* ...
|
||||
* Save exercises to file system
|
||||
* Add settings (vibration, beep, ...)
|
||||
* Find a nicer icon
|
||||
|
||||
|
||||
## Contribute
|
||||
Feel free to send in improvements and remarks.
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA=="))
|
|
@ -0,0 +1,259 @@
|
|||
const Layout = require("Layout");
|
||||
|
||||
let historyY = [];
|
||||
let historyZ = [];
|
||||
const avgSize = 10;
|
||||
|
||||
const thresholdY = 2500;
|
||||
const thresholdPushUpTime = 1400; // mininmal time between two push ups
|
||||
let tStart;
|
||||
|
||||
let avgY;
|
||||
let avgZ;
|
||||
let historyAvgY = [];
|
||||
let historyAvgZ = [];
|
||||
let historySlopeY = [];
|
||||
let historySlopeZ = [];
|
||||
|
||||
let lastZeroPassType;
|
||||
let lastZeroPassTime = 0;
|
||||
let lastPushUpCmpltTime = 0;
|
||||
|
||||
let exerciseType = "";
|
||||
let pushUpCounter = 0;
|
||||
|
||||
let layout;
|
||||
|
||||
let recordActive = false;
|
||||
|
||||
|
||||
function showMenu() {
|
||||
let menu;
|
||||
if (pushUpCounter == 0) {
|
||||
menu = {
|
||||
"": {
|
||||
title: "Banglexercise"
|
||||
},
|
||||
"Start push ups": function() {
|
||||
exerciseType = "push ups";
|
||||
E.showMenu();
|
||||
startRecording();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
menu = {
|
||||
"": {
|
||||
title: "Banglexercise"
|
||||
},
|
||||
"Last:": {
|
||||
value: pushUpCounter + " push ups"
|
||||
},
|
||||
"Start push ups": function() {
|
||||
exerciseType = "push ups";
|
||||
E.showMenu();
|
||||
startRecording();
|
||||
}
|
||||
};
|
||||
}
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function accelHandler(accel) {
|
||||
const t = Math.round(new Date().getTime()); // time in ms
|
||||
const y = accel.y * 8192;
|
||||
const z = accel.z * 8192;
|
||||
//console.log(t, y, z);
|
||||
|
||||
while (historyY.length > avgSize)
|
||||
historyY.shift();
|
||||
historyY.push(y);
|
||||
|
||||
if (historyY.length > avgSize / 2)
|
||||
avgY = E.sum(historyY) / historyY.length;
|
||||
|
||||
while (historyZ.length > avgSize)
|
||||
historyZ.shift();
|
||||
historyZ.push(z);
|
||||
|
||||
if (historyZ.length > avgSize / 2)
|
||||
avgZ = E.sum(historyZ) / historyZ.length;
|
||||
|
||||
if (avgY) {
|
||||
//console.log(avgY, avgZ);
|
||||
historyAvgY.push([t, avgY]);
|
||||
historyAvgZ.push([t, avgZ]);
|
||||
}
|
||||
|
||||
let mY;
|
||||
let mZ;
|
||||
// slope for Y
|
||||
let l = historyAvgY.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgY[l - 2];
|
||||
const p2 = historyAvgY[l - 1];
|
||||
mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
|
||||
if (Math.abs(mY) >= thresholdY) {
|
||||
historyAvgY.shift();
|
||||
historySlopeY.push([t, mY]);
|
||||
//console.log(t, Math.abs(mY));
|
||||
|
||||
const lMY = historySlopeY.length;
|
||||
if (lMY > 1) {
|
||||
const pMY1 = historySlopeY[lMY - 2][1];
|
||||
const pMY2 = historySlopeY[lMY - 1][1];
|
||||
isValidPushUp(pMY1, pMY2, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Z
|
||||
l = historyAvgZ.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgZ[l - 2];
|
||||
const p2 = historyAvgZ[l - 1];
|
||||
mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]);
|
||||
historyAvgZ.shift();
|
||||
historySlopeZ.push([p2[0] - p1[0], mZ]);
|
||||
}
|
||||
}
|
||||
|
||||
function isValidPushUp(p1, p2, t) {
|
||||
if (p1 > 0 && p2 < 0) {
|
||||
|
||||
if (lastZeroPassType == "-+") {
|
||||
console.log(t, "Push up half complete...");
|
||||
|
||||
layout.progress.label = "...";
|
||||
layout.render(layout.progress);
|
||||
}
|
||||
|
||||
lastZeroPassType = "+-";
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
if (p2 > 0 && p1 < 0) {
|
||||
|
||||
if (lastZeroPassType == "+-") {
|
||||
// potential complete push up. Let's check the time difference...
|
||||
const tDiffLastPushUp = t - lastPushUpCmpltTime;
|
||||
const tDiffStart = t - tStart;
|
||||
console.log(t, "Push up maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart));
|
||||
|
||||
if ((lastPushUpCmpltTime <= 0 && tDiffStart >= thresholdPushUpTime) || tDiffLastPushUp >= thresholdPushUpTime) {
|
||||
console.log(t, "Push up complete!!!");
|
||||
|
||||
lastPushUpCmpltTime = t;
|
||||
pushUpCounter++;
|
||||
|
||||
layout.count.label = pushUpCounter;
|
||||
layout.render(layout.count);
|
||||
layout.progress.label = "";
|
||||
layout.render(layout.progress);
|
||||
|
||||
Bangle.buzz(100, 0.3); // TODO make configurable
|
||||
} else {
|
||||
console.log(t, "Push up to quick for threshold!");
|
||||
}
|
||||
}
|
||||
|
||||
lastZeroPassType = "-+";
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
function calcPushUps() {
|
||||
const l = historySlopeY.length;
|
||||
for (let i = 1; i < l; i++) {
|
||||
const p1 = historySlopeY[i - 1][1];
|
||||
const p2 = historySlopeY[i][1];
|
||||
const t = historySlopeY[i][0];
|
||||
isValidPushUp(p1, p2, t);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function reset() {
|
||||
historyY = [];
|
||||
historyZ = [];
|
||||
historyAvgY = [];
|
||||
historyAvgZ = [];
|
||||
historySlopeY = [];
|
||||
historySlopeZ = [];
|
||||
|
||||
lastZeroPassType = "";
|
||||
lastZeroPassTime = 0;
|
||||
lastPushUpCmpltTime = 0;
|
||||
pushUpCounter = 0;
|
||||
}
|
||||
|
||||
|
||||
function startRecording() {
|
||||
if (recordActive) return;
|
||||
g.clear(1);
|
||||
reset();
|
||||
layout = new Layout({
|
||||
type: "v",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "type",
|
||||
font: "6x8:2",
|
||||
label: exerciseType,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "count",
|
||||
font: "6x8:9",
|
||||
label: pushUpCounter,
|
||||
pad: 5,
|
||||
bgCol: g.theme.bg
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "progress",
|
||||
font: "6x8:2",
|
||||
label: "",
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "recording",
|
||||
font: "6x8:2",
|
||||
label: "RECORDING",
|
||||
bgCol: "#f00",
|
||||
pad: 5,
|
||||
fillx: 1
|
||||
},
|
||||
]
|
||||
}, {
|
||||
btns: [
|
||||
{
|
||||
label: "STOP",
|
||||
cb: () => {
|
||||
stopRecording();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
layout.render();
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
Bangle.on('accel', accelHandler);
|
||||
Bangle.buzz(200, 1);
|
||||
tStart = new Date().getTime();
|
||||
recordActive = true;
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (!recordActive) return;
|
||||
g.clear(1);
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
console.log("Found " + pushUpCounter + " push ups!");
|
||||
recordActive = false;
|
||||
}
|
||||
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
showMenu();
|
Binary file not shown.
After Width: | Height: | Size: 690 B |
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Loading…
Reference in New Issue