mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into jekyll-apps.json
commit
3f95330025
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,17 @@
|
||||||
|
# AccelaMaze
|
||||||
|
|
||||||
|
Tilt the watch to roll a ball through a maze.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
* Use the menu to select difficulty level (or exit).
|
||||||
|
* Wait until the maze gets generated and a red ball appears.
|
||||||
|
* Tilt the watch to get the ball into the green cell.
|
||||||
|
|
||||||
|
At any time you can click the button to return to the menu.
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
[Nimrod Kerrett](https://zzzen.com)
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwggaXh3M53/AA3yl4IHn//+EM5nMAoIX/C4RfCC4szmcxC4QFBAAUxC4UPAwIOB+YCCiMRkAFCkIGBAAQfBC4IUEAQhHIAAQX/C5EDmcyCgUTAoYXDR4kzC4UBPoKVB+YFFAQSPBiAKBiCnDGoZECABDUCa4YX/C5qPBQwoXGkczmC/FQYSSCVQSSCEwQOCC4hKFX4QXCd5YX/C4qMEmQXITAinDPoIADTwSPFkKMBX47RGI47XIC/4XCgZ9DQYYABmKYBmIXFkczmEBRIK/CQYQIBkECSoiSCA4MQa5pEFd6IX/RgMyC6H/QASVCRIS/EAQrXFJQoX/C6kDRQIXCiYFD+QFBmIUCkYFD+CJBiSPCRwIFFSoQFCiF3u9wI4gAO+wXW+IXygAAW"))
|
|
@ -0,0 +1,276 @@
|
||||||
|
const MARGIN = 25;
|
||||||
|
const WALL_RIGHT = 1, WALL_DOWN = 2;
|
||||||
|
const STATUS_GENERATING = 0, STATUS_PLAYING = 1,
|
||||||
|
STATUS_SOLVED = 2, STATUS_ABORTED = -1;
|
||||||
|
|
||||||
|
function Maze(n) {
|
||||||
|
this.n = n;
|
||||||
|
this.status = STATUS_GENERATING;
|
||||||
|
this.wall_length = Math.floor((g.getHeight()-2*MARGIN)/n);
|
||||||
|
this.total_length = this.wall_length*n;
|
||||||
|
this.margin = Math.floor((g.getHeight()-this.total_length)/2);
|
||||||
|
this.ball_x = 0;
|
||||||
|
this.ball_y = 0;
|
||||||
|
this.clearScreen = function() {
|
||||||
|
g.clearRect(
|
||||||
|
0, this.margin,
|
||||||
|
g.getWidth(), this.margin+this.total_length
|
||||||
|
);
|
||||||
|
};
|
||||||
|
this.clearScreen();
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
for (let i=0; i<=n; i++) {
|
||||||
|
g.drawRect(
|
||||||
|
this.margin, this.margin+i*this.wall_length,
|
||||||
|
g.getWidth()-this.margin, this.margin+i*this.wall_length
|
||||||
|
);
|
||||||
|
g.drawRect(
|
||||||
|
this.margin+i*this.wall_length, this.margin,
|
||||||
|
this.margin+i*this.wall_length, g.getHeight() - this.margin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.walls = new Uint8Array(n*n);
|
||||||
|
this.groups = new Uint8Array(n*n);
|
||||||
|
for (let cell = 0; cell<n*n; cell++) {
|
||||||
|
this.walls[cell] = WALL_RIGHT|WALL_DOWN;
|
||||||
|
this.groups[cell] = cell;
|
||||||
|
}
|
||||||
|
let from_group, to_group;
|
||||||
|
let ngroups = n*n;
|
||||||
|
while (--ngroups) {
|
||||||
|
// Abort if BTN1 pressed [grace period for menu]
|
||||||
|
// (for some reason setWatch() fails inside constructor)
|
||||||
|
if (ngroups<n*n-4 && digitalRead(BTN1)) {
|
||||||
|
aborting = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
from_group = to_group = -1;
|
||||||
|
while (from_group<0) {
|
||||||
|
if (Math.random()<0.5) { // try to break a wall right
|
||||||
|
let r = Math.floor(Math.random()*n);
|
||||||
|
let c = Math.floor(Math.random()*(n-1));
|
||||||
|
let cell = r*n+c;
|
||||||
|
if (this.groups[cell]!=this.groups[cell+1]) {
|
||||||
|
this.walls[cell] &= ~WALL_RIGHT;
|
||||||
|
g.clearRect(
|
||||||
|
this.margin+(c+1)*this.wall_length,
|
||||||
|
this.margin+r*this.wall_length+1,
|
||||||
|
this.margin+(c+1)*this.wall_length,
|
||||||
|
this.margin+(r+1)*this.wall_length-1
|
||||||
|
);
|
||||||
|
g.flip(); // show progress.
|
||||||
|
from_group = this.groups[cell];
|
||||||
|
to_group = this.groups[cell+1];
|
||||||
|
}
|
||||||
|
} else { // try to break a wall down
|
||||||
|
let r = Math.floor(Math.random()*(n-1));
|
||||||
|
let c = Math.floor(Math.random()*n);
|
||||||
|
let cell = r*n+c;
|
||||||
|
if (this.groups[cell]!=this.groups[cell+n]) {
|
||||||
|
this.walls[cell] &= ~WALL_DOWN;
|
||||||
|
g.clearRect(
|
||||||
|
this.margin+c*this.wall_length+1,
|
||||||
|
this.margin+(r+1)*this.wall_length,
|
||||||
|
this.margin+(c+1)*this.wall_length-1,
|
||||||
|
this.margin+(r+1)*this.wall_length
|
||||||
|
);
|
||||||
|
from_group = this.groups[cell];
|
||||||
|
to_group = this.groups[cell+n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let cell = 0; cell<n*n; cell++) {
|
||||||
|
if (this.groups[cell]==from_group) {
|
||||||
|
this.groups[cell] = to_group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.clearScreen = function() {
|
||||||
|
g.clearRect(
|
||||||
|
0, MARGIN, g.getWidth(), g.getHeight()-MARGIN-1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
this.clearCell = function(r, c) {
|
||||||
|
if (!r && !c) {
|
||||||
|
g.setColor("#ffff00");
|
||||||
|
} else if (r==this.n-1 && c==this.n-1) {
|
||||||
|
g.setColor("#00ff00");
|
||||||
|
} else {
|
||||||
|
g.setColor(g.theme.bg);
|
||||||
|
}
|
||||||
|
g.fillRect(
|
||||||
|
this.margin+this.wall_length*c+1,
|
||||||
|
this.margin+this.wall_length*r+1,
|
||||||
|
this.margin+this.wall_length*(c+1),
|
||||||
|
this.margin+this.wall_length*(r+1)
|
||||||
|
);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
if (this.walls[r*n+c]&WALL_RIGHT) {
|
||||||
|
g.fillRect(
|
||||||
|
this.margin+this.wall_length*(c+1),
|
||||||
|
this.margin+this.wall_length*r,
|
||||||
|
this.margin+this.wall_length*(c+1),
|
||||||
|
this.margin+this.wall_length*(r+1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.walls[r*n+c]&WALL_DOWN) {
|
||||||
|
g.fillRect(
|
||||||
|
this.margin+this.wall_length*c,
|
||||||
|
this.margin+this.wall_length*(r+1),
|
||||||
|
this.margin+this.wall_length*(c+1),
|
||||||
|
this.margin+this.wall_length*(r+1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.drawBall = function(x, y) {
|
||||||
|
g.setColor("#ff0000");
|
||||||
|
g.fillEllipse(
|
||||||
|
this.margin+x+1,
|
||||||
|
this.margin+y+1,
|
||||||
|
this.margin+x+this.wall_length-1,
|
||||||
|
this.margin+y+this.wall_length-1
|
||||||
|
);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
};
|
||||||
|
this.move = function(dx, dy) {
|
||||||
|
let next_x = this.ball_x,
|
||||||
|
next_y = this.ball_y,
|
||||||
|
ball_r = Math.floor(this.ball_y/this.wall_length),
|
||||||
|
ball_c = Math.floor(this.ball_x/this.wall_length);
|
||||||
|
if (this.ball_x%this.wall_length) {
|
||||||
|
if (dx) {
|
||||||
|
next_x += dx;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (this.ball_y%this.wall_length) {
|
||||||
|
if (dy) {
|
||||||
|
next_y += dy;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { // exactly in a cell. Check walls
|
||||||
|
if (dy<0 && ball_r>0 && !(this.walls[n*(ball_r-1)+ball_c]&WALL_DOWN)) {
|
||||||
|
next_y--;
|
||||||
|
} else if (dy>0 && ball_r<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_DOWN)) {
|
||||||
|
next_y++;
|
||||||
|
} else if (dx<0 && ball_c>0 && !(this.walls[n*ball_r+ball_c-1]&WALL_RIGHT)) {
|
||||||
|
next_x--;
|
||||||
|
} else if (dx>0 && ball_c<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_RIGHT)) {
|
||||||
|
next_x++;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.clearCell(ball_r, ball_c);
|
||||||
|
if (this.ball_x%this.wall_length) {
|
||||||
|
this.clearCell(ball_r, ball_c+1);
|
||||||
|
}
|
||||||
|
if (this.ball_y%this.wall_length) {
|
||||||
|
this.clearCell(ball_r+1, ball_c);
|
||||||
|
}
|
||||||
|
this.ball_x = next_x;
|
||||||
|
this.ball_y = next_y;
|
||||||
|
this.drawBall(this.ball_x, this.ball_y);
|
||||||
|
if (this.ball_x==(n-1)*this.wall_length && this.ball_y==(n-1)*this.wall_length) {
|
||||||
|
this.status = STATUS_SOLVED;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
this.try_move_horizontally = function(accel_x) {
|
||||||
|
if (accel_x>0.15) {
|
||||||
|
return this.move(-1, 0);
|
||||||
|
} else if (accel_x<-0.15) {
|
||||||
|
return this.move(1, 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
this.try_move_vertically = function(accel_y) {
|
||||||
|
if (accel_y<-0.15) {
|
||||||
|
return this.move(0,1);
|
||||||
|
} else if (accel_y>0.15) {
|
||||||
|
return this.move(0,-1);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
this.tick = function() {
|
||||||
|
accel = Bangle.getAccel();
|
||||||
|
if (this.ball_x%this.wall_length) {
|
||||||
|
this.try_move_horizontally(accel.x);
|
||||||
|
} else if (this.ball_y%this.wall_length) {
|
||||||
|
this.try_move_vertically(accel.y);
|
||||||
|
} else {
|
||||||
|
if (Math.abs(accel.x)>Math.abs(accel.y)) { // prefer horizontally
|
||||||
|
if (!this.try_move_horizontally(accel.x)) {
|
||||||
|
this.try_move_vertically(accel.y);
|
||||||
|
}
|
||||||
|
} else { // prefer vertically
|
||||||
|
if (!this.try_move_vertically(accel.y)) {
|
||||||
|
this.try_move_horizontally(accel.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.clearCell(0,0);
|
||||||
|
this.clearCell(n-1,n-1);
|
||||||
|
this.drawBall(0,0);
|
||||||
|
this.status = STATUS_PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeToText(t) { // Courtesy of stopwatch app
|
||||||
|
let hrs = Math.floor(t/3600000);
|
||||||
|
let mins = Math.floor(t/60000)%60;
|
||||||
|
let secs = Math.floor(t/1000)%60;
|
||||||
|
let tnth = Math.floor(t/100)%10;
|
||||||
|
let text;
|
||||||
|
|
||||||
|
if (hrs === 0)
|
||||||
|
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||||
|
else
|
||||||
|
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
let aborting = false;
|
||||||
|
let start_time = 0;
|
||||||
|
let duration = 0;
|
||||||
|
let maze=null;
|
||||||
|
let mazeMenu = {
|
||||||
|
"": { "title": "Maze size", "selected": 1 },
|
||||||
|
"Easy (8x8)": function() { E.showMenu(); maze = new Maze(8); },
|
||||||
|
"Medium (10x10)": function() { E.showMenu(); maze = new Maze(10); },
|
||||||
|
"Hard (14x14)": function() { E.showMenu(); maze = new Maze(14); },
|
||||||
|
"< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock
|
||||||
|
};
|
||||||
|
|
||||||
|
g.clear(true);
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
Bangle.setLocked(false);
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
|
E.showMenu(mazeMenu);
|
||||||
|
let maze_interval = setInterval(
|
||||||
|
function() {
|
||||||
|
if (maze) {
|
||||||
|
if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) {
|
||||||
|
console.log(`aborting ${start_time}`);
|
||||||
|
maze = null;
|
||||||
|
start_time = duration = 0;
|
||||||
|
aborting = false;
|
||||||
|
setTimeout(function() {E.showMenu(mazeMenu); }, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!start_time) {
|
||||||
|
start_time = Date.now();
|
||||||
|
}
|
||||||
|
if (maze.status==STATUS_PLAYING) {
|
||||||
|
maze.tick();
|
||||||
|
}
|
||||||
|
if (maze.status==STATUS_SOLVED && !duration) {
|
||||||
|
duration = Date.now()-start_time;
|
||||||
|
g.setFontAlign(0,0).setColor(g.theme.fg);
|
||||||
|
g.setFont("Vector",18);
|
||||||
|
g.drawString(`Solved in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 25);
|
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{ "id": "banglexercise",
|
||||||
|
"name": "BanglExercise",
|
||||||
|
"shortName":"BanglExercise",
|
||||||
|
"version":"0.02",
|
||||||
|
"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},
|
||||||
|
{"name":"banglexercise.settings.js","url":"settings.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"banglexercise.json"}
|
||||||
|
]
|
||||||
|
}
|
|
@ -9,4 +9,5 @@
|
||||||
0.09: Tab anywhere to open the launcher.
|
0.09: Tab anywhere to open the launcher.
|
||||||
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
|
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
|
||||||
0.11: Show the gadgetbridge weather temperature (settings).
|
0.11: Show the gadgetbridge weather temperature (settings).
|
||||||
0.12: Added humidity to data.
|
0.12: Added humidity to data.
|
||||||
|
0.13: Improved battery visualization.
|
|
@ -20,7 +20,7 @@ let cOrange = "#FF9900";
|
||||||
let cPurple = "#FF00DC";
|
let cPurple = "#FF00DC";
|
||||||
let cWhite = "#FFFFFF";
|
let cWhite = "#FFFFFF";
|
||||||
let cBlack = "#000000";
|
let cBlack = "#000000";
|
||||||
let cGrey = "#9E9E9E";
|
let cGrey = "#424242";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global lcars variables
|
* Global lcars variables
|
||||||
|
@ -143,7 +143,7 @@ function printData(key, y, c){
|
||||||
} else if (key == "HUMIDITY"){
|
} else if (key == "HUMIDITY"){
|
||||||
text = "HUM";
|
text = "HUM";
|
||||||
var weather = getWeather();
|
var weather = getWeather();
|
||||||
value = parseInt(weather.hum) + "%";
|
value = weather.hum + "%";
|
||||||
|
|
||||||
} else if(key == "CORET"){
|
} else if(key == "CORET"){
|
||||||
value = locale.temp(parseInt(E.getTemperature()));
|
value = locale.temp(parseInt(E.getTemperature()));
|
||||||
|
@ -242,9 +242,14 @@ function drawPosition0(){
|
||||||
|
|
||||||
// The last line is a battery indicator too
|
// The last line is a battery indicator too
|
||||||
var bat = E.getBattery() / 100.0;
|
var bat = E.getBattery() / 100.0;
|
||||||
var batX2 = parseInt((172 - 35) * bat + 35);
|
var batStart = 19;
|
||||||
drawHorizontalBgLine(cOrange, 35, batX2-5, 171, 5);
|
var batWidth = 172 - batStart;
|
||||||
drawHorizontalBgLine(cGrey, batX2+5, 172, 171, 5);
|
var batX2 = parseInt(batWidth * bat + batStart);
|
||||||
|
drawHorizontalBgLine(cOrange, batStart, batX2, 171, 5);
|
||||||
|
drawHorizontalBgLine(cGrey, batX2, 172, 171, 5);
|
||||||
|
for(var i=0; i+batStart<=172; i+=parseInt(batWidth/4)){
|
||||||
|
drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8)
|
||||||
|
}
|
||||||
|
|
||||||
// Draw Infos
|
// Draw Infos
|
||||||
drawInfo();
|
drawInfo();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "LCARS Clock",
|
"name": "LCARS Clock",
|
||||||
"shortName":"LCARS",
|
"shortName":"LCARS",
|
||||||
"icon": "lcars.png",
|
"icon": "lcars.png",
|
||||||
"version":"0.12",
|
"version":"0.13",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.7 KiB |
|
@ -1,3 +1,4 @@
|
||||||
0.01: First commit
|
0.01: First commit
|
||||||
0.02: Handle new firmwares with 'lock' event
|
0.02: Handle new firmwares with 'lock' event
|
||||||
0.03: Don't try to be fancy - just bail out on firmwares without a lock event
|
0.03: Don't try to be fancy - just bail out on firmwares without a lock event
|
||||||
|
0.04: Set sortorder to -1 so that widget always takes up the furthest left position
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"id": "widlock",
|
"id": "widlock",
|
||||||
"name": "Lock Widget",
|
"name": "Lock Widget",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
|
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"tags": "widget,lock",
|
"tags": "widget,lock",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"sortorder": -1,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widlock.wid.js","url":"widget.js"}
|
{"name":"widlock.wid.js","url":"widget.js"}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
|
0.02: Size widget after step count is reset
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"shortName":"Simple Pedometer",
|
"shortName":"Simple Pedometer",
|
||||||
"icon": "screenshot_widpa.png",
|
"icon": "screenshot_widpa.png",
|
||||||
"screenshots": [{"url":"screenshot_widpa.png"}],
|
"screenshots": [{"url":"screenshot_widpa.png"}],
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
|
@ -6,7 +6,7 @@ WIDGETS["widpa"]={area:"tl",width:13,draw:function() {
|
||||||
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
|
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
|
||||||
var steps = Bangle.getHealthStatus("day").steps;
|
var steps = Bangle.getHealthStatus("day").steps;
|
||||||
var w = 1 + (steps.toString().length)*12;
|
var w = 1 + (steps.toString().length)*12;
|
||||||
if (w > this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
|
if (w != this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23);
|
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23);
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
|
0.02: Fixed widget id to wibpb, Size widget after step count is reset
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// on.step version
|
// on.step version
|
||||||
Bangle.on('step', function(s) { WIDGETS["bata"].draw(); });
|
Bangle.on('step', function(s) { WIDGETS["bata"].draw(); });
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) WIDGETS["bata"].draw();
|
if (on) WIDGETS["widpb"].draw();
|
||||||
});
|
});
|
||||||
WIDGETS["bata"]={area:"tl",width:13,draw:function() {
|
WIDGETS["widpb"]={area:"tl",width:13,draw:function() {
|
||||||
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
|
if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off
|
||||||
var steps = Bangle.getHealthStatus("day").steps;
|
var steps = Bangle.getHealthStatus("day").steps;
|
||||||
var w = 1 + (steps.toString().length)*12;
|
var w = 1 + (steps.toString().length)*12;
|
||||||
if (w > this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
|
if (w != this.width) {this.width = w; setTimeout(() => Bangle.drawWidgets(),10); return;}
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
||||||
|
|
Loading…
Reference in New Issue