1
0
Fork 0

Merge pull request #337 from Purple-Tentacle/master

Active Pedometer 0.03 / Chrono Widget 0.03
master
Gordon Williams 2020-04-21 16:22:42 +01:00 committed by GitHub
commit 4744b64a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 330 additions and 116 deletions

View File

@ -1153,22 +1153,22 @@
"name": "Active Pedometer", "name": "Active Pedometer",
"shortName":"Active Pedometer", "shortName":"Active Pedometer",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.03",
"description": "Pedometer that filters out arm movement and displays a step goal progress.", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
"tags": "outdoors,widget", "tags": "outdoors,widget",
"type":"widget",
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"activepedom.wid.js","url":"widget.js"}, {"name":"activepedom.wid.js","url":"widget.js"},
{"name":"activepedom.settings.js","url":"settings.js"}, {"name":"activepedom.settings.js","url":"settings.js"},
{"name":"activepedom.img","url":"app-icon.js","evaluate":true} {"name":"activepedom.img","url":"app-icon.js","evaluate":true},
{"name":"activepedom.app.js","url":"app.js"}
] ]
}, },
{ "id": "chronowid", { "id": "chronowid",
"name": "Chrono Widget", "name": "Chrono Widget",
"shortName":"Chrono Widget", "shortName":"Chrono Widget",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.03",
"description": "Chronometer (timer) which runs as widget.", "description": "Chronometer (timer) which runs as widget.",
"tags": "tools,widget", "tags": "tools,widget",
"readme": "README.md", "readme": "README.md",

View File

@ -1,2 +1,3 @@
0.01: New Widget! 0.01: New Widget!
0.02: Distance calculation and display 0.02: Distance calculation and display
0.03: Data logging and display

View File

@ -1,4 +1,4 @@
# Active Pedometer # Active Pedometer
Pedometer that filters out arm movement and displays a step goal progress. Pedometer that filters out arm movement and displays a step goal progress.
I changed the step counting algorithm completely. I changed the step counting algorithm completely.
@ -6,6 +6,8 @@ Now every step is counted when in status 'active', if the time difference betwee
To get in 'active' mode, you have to reach the step threshold before the active timer runs out. To get in 'active' mode, you have to reach the step threshold before the active timer runs out.
When you reach the step threshold, the steps needed to reach the threshold are counted as well. When you reach the step threshold, the steps needed to reach the threshold are counted as well.
Steps are saved to a datafile every 5 minutes. You can watch a graph using the app.
## Screenshots ## Screenshots
* 600 steps * 600 steps
![](600.png) ![](600.png)
@ -30,6 +32,22 @@ When you reach the step threshold, the steps needed to reach the threshold are c
* Steps are saved to a file and read-in at start (to not lose step progress) * Steps are saved to a file and read-in at start (to not lose step progress)
* Settings can be changed in Settings - App/widget settings - Active Pedometer * Settings can be changed in Settings - App/widget settings - Active Pedometer
## Data storage
* Data is stored to a file
* Format: now,stepsCounted,active,stepsTooShort,stepsTooLong,stepsOutsideTime
* now is UNIX timestamp in ms
* You can chose the app to watch a steps graph
* You can import the file into Excel
* The file does not include a header
* You can convert UNIX timestamp to a date in Excel using this formula: =DATUM(1970;1;1)+(LINKS(A2;10)/86400)
* You have to format the cell with the formula to a date cell. Example: JJJJ-MM-TT-hh-mm-ss
## App
* The app accesses the data stored for the current day
* Timespan is choseable (1h, 4h, 8h, 12h, 16h, 20, 24h), standard is 24h, the whole current day
## Settings ## Settings
* Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100 * Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100

165
apps/activepedom/app.js Normal file
View File

@ -0,0 +1,165 @@
(() => {
//Graph module, as long as modules are not added by the app loader
Modules.addCached("graph",function(){exports.drawAxes=function(b,c,a){function h(a){return e+m*(a-t)/x}function l(a){return f+g-g*(a-n)/u}var k=a.padx||0,d=a.pady||0,t=-k,w=c.length+k-1,n=(void 0!==a.miny?a.miny:a.miny=c.reduce(function(a,b){return Math.min(a,b)},c[0]))-d;c=(void 0!==a.maxy?a.maxy:a.maxy=c.reduce(function(a,b){return Math.max(a,b)},c[0]))+d;a.gridy&&(d=a.gridy,n=d*Math.floor(n/d),c=d*Math.ceil(c/d));var e=a.x||0,f=a.y||0,m=a.width||b.getWidth()-(e+1),g=a.height||b.getHeight()-(f+1);a.axes&&(null!==a.ylabel&&
(e+=6,m-=6),null!==a.xlabel&&(g-=6));a.title&&(f+=6,g-=6);a.axes&&(b.drawLine(e,f,e,f+g),b.drawLine(e,f+g,e+m,f+g));a.title&&(b.setFontAlign(0,-1),b.drawString(a.title,e+m/2,f-6));var x=w-t,u=c-n;u||(u=1);if(a.gridx){b.setFontAlign(0,-1,0);var v=a.gridx;for(d=Math.ceil((t+k)/v)*v;d<=w-k;d+=v){var r=h(d),p=a.xlabel?a.xlabel(d):d;b.setPixel(r,f+g-1);var q=b.stringWidth(p)/2;null!==a.xlabel&&r>q&&b.getWidth()>r+q&&b.drawString(p,r,f+g+2)}}if(a.gridy)for(b.setFontAlign(0,0,1),d=n;d<=c;d+=a.gridy)k=l(d),
p=a.ylabel?a.ylabel(d):d,b.setPixel(e+1,k),q=b.stringWidth(p)/2,null!==a.ylabel&&k>q&&b.getHeight()>k+q&&b.drawString(p,e-5,k+1);b.setFontAlign(-1,-1,0);return{x:e,y:f,w:m,h:g,getx:h,gety:l}};exports.drawLine=function(b,c,a){a=a||{};a=exports.drawAxes(b,c,a);var h=!0,l;for(l in c)h?b.moveTo(a.getx(l),a.gety(c[l])):b.lineTo(a.getx(l),a.gety(c[l])),h=!1;return a};exports.drawBar=function(b,c,a){a=a||{};a.padx=1;a=exports.drawAxes(b,c,a);for(var h in c)b.fillRect(a.getx(h-.5)+1,a.gety(c[h]),a.getx(h+
.5)-1,a.gety(0));return a}});
const storage = require("Storage");
const SETTINGS_FILE = 'activepedom.settings.json';
var history = 86400000; // 28800000=8h 43200000=12h //86400000=24h
//return setting
function setting(key) {
//define default settings
const DEFAULTS = {
'cMaxTime' : 1100,
'cMinTime' : 240,
'stepThreshold' : 30,
'intervalResetActive' : 30000,
'stepSensitivity' : 80,
'stepGoal' : 10000,
'stepLength' : 75,
};
if (!settings) { loadSettings(); }
return (key in settings) ? settings[key] : DEFAULTS[key];
}
//Convert ms to time
function getTime(t) {
date = new Date(t);
offset = date.getTimezoneOffset() / 60;
//var milliseconds = parseInt((t % 1000) / 100),
seconds = Math.floor((t / 1000) % 60);
minutes = Math.floor((t / (1000 * 60)) % 60);
hours = Math.floor((t / (1000 * 60 * 60)) % 24);
hours = hours - offset;
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
}
function getDate(t) {
date = new Date(t*1);
year = date.getFullYear();
month = date.getMonth()+1; //month is zero-based
day = date.getDate();
month = (month < 10) ? "0" + month : month;
day = (day < 10) ? "0" + day : day;
return year + "-" + month + "-" + day;
}
//columns: 0=time, 1=stepsCounted, 2=active, 3=stepsTooShort, 4=stepsTooLong, 5=stepsOutsideTime
function getArrayFromCSV(file, column) {
i = 0;
array = [];
now = new Date();
while ((nextLine = file.readLine())) { //as long as there is a next line
if(nextLine) {
dataSplitted = nextLine.split(','); //split line,
diff = now - dataSplitted[0]; //calculate difference between now and stored time
if (diff <= history) { //only entries from the last x ms
array.push(dataSplitted[column]);
}
}
i++;
}
return array;
}
function drawGraph() {
//times
// actives = getArrayFromCSV(csvFile, 2);
// shorts = getArrayFromCSV(csvFile, 3);
// longs = getArrayFromCSV(csvFile, 4);
// outsides = getArrayFromCSV(csvFile, 5); //array.push(dataSplitted[5].slice(0,-1));
now = new Date();
month = now.getMonth() + 1;
if (month < 10) month = "0" + month;
filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data";
var csvFile = storage.open(filename, "r");
times = getArrayFromCSV(csvFile, 0);
first = getDate(times[0]) + " " + getTime(times[0]); //first entry in datafile
last = getDate (times[times.length-1]) + " " + getTime(times[times.length-1]); //last entry in datafile
//free memory
csvFile = undefined;
times = undefined;
//steps
var csvFile = storage.open(filename, "r");
steps = getArrayFromCSV(csvFile, 1);
first = first + " " + steps[0] + "/" + setting('stepGoal');
last = last + " " + steps[steps.length-1] + "/" + setting('stepGoal');
//define y-axis grid labels
stepsLastEntry = steps[steps.length-1];
if (stepsLastEntry < 1000) gridyValue = 100;
if (stepsLastEntry >= 1000 && stepsLastEntry < 10000) gridyValue = 1000;
if (stepsLastEntry > 10000) gridyValue = 5000;
//draw
drawMenu();
g.drawString("First: " + first, 10, 30);
g.drawString(" Last: " + last, 10, 40);
require("graph").drawLine(g, steps, {
//title: "Steps Counted",
axes : true,
gridy : gridyValue,
y : 60, //offset on screen
x : 5, //offset on screen
});
//free memory from big variables
allData = undefined;
allDataFile = undefined;
csvFile = undefined;
times = undefined;
}
function drawMenu () {
g.clear();
g.setFont("6x8", 1);
g.drawString("BTN1:Timespan | BTN2:Draw", 20, 10);
g.drawString("Timespan: " + history/1000/60/60 + " hours", 20, 20);
}
setWatch(function() { //BTN1
switch(history) {
case 3600000 : //1h
history = 14400000; //4h
break;
case 86400000 : //24
history = 3600000; //1h
break;
default :
history = history + 14400000; //4h
break;
}
drawMenu();
}, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN2
g.setFont("6x8", 2);
g.drawString ("Drawing...",30,60);
drawGraph();
}, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN3
}, BTN3, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN4
}, BTN4, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN5
}, BTN5, {edge:"rising", debounce:50, repeat:true});
//load settings
let settings;
function loadSettings() {
settings = storage.readJSON(SETTINGS_FILE, 1) || {};
}
drawMenu();
})();

View File

@ -3,13 +3,14 @@
var startTimeStep = new Date(); //set start time var startTimeStep = new Date(); //set start time
var stopTimeStep = 0; //Time after one step var stopTimeStep = 0; //Time after one step
var timerResetActive = 0; //timer to reset active var timerResetActive = 0; //timer to reset active
var timerStoreData = 0; //timer to store data
var steps = 0; //steps taken var steps = 0; //steps taken
var stepsCounted = 0; //active steps counted var stepsCounted = 0; //active steps counted
var active = 0; //x steps in y seconds achieved var active = 0; //x steps in y seconds achieved
var stepGoalPercent = 0; //percentage of step goal var stepGoalPercent = 0; //percentage of step goal
var stepGoalBarLength = 0; //length og progress bar var stepGoalBarLength = 0; //length og progress bar
var lastUpdate = new Date(); //used to reset counted steps on new day var lastUpdate = new Date(); //used to reset counted steps on new day
var width = 45; //width of widget var width = 46; //width of widget
//used for statistics and debugging //used for statistics and debugging
var stepsTooShort = 0; var stepsTooShort = 0;
@ -18,13 +19,41 @@
var distance = 0; //distance travelled var distance = 0; //distance travelled
const s = require('Storage');
const SETTINGS_FILE = 'activepedom.settings.json'; const SETTINGS_FILE = 'activepedom.settings.json';
const PEDOMFILE = "activepedom.steps.json"; const PEDOMFILE = "activepedom.steps.json";
var dataFile;
var storeDataInterval = 5*60*1000; //ms
let settings; let settings;
//load settings //load settings
function loadSettings() { function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}; settings = s.readJSON(SETTINGS_FILE, 1) || {};
}
function storeData() {
now = new Date();
month = now.getMonth() + 1;
if (month < 10) month = "0" + month;
filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data";
dataFile = s.open(filename,"a");
if (dataFile) {
if (dataFile.getLength() == 0) {
stepsToWrite = 0;
}
else {
stepsToWrite = stepsCounted;
}
dataFile.write([
now.getTime(),
stepsToWrite,
active,
stepsTooShort,
stepsTooLong,
stepsOutsideTime,
].join(",")+"\n");
}
dataFile = undefined;
} }
//return setting //return setting
@ -77,20 +106,20 @@
//Remove step if time between first and second step is too long //Remove step if time between first and second step is too long
if (stepTimeDiff >= setting('cMaxTime')) { //milliseconds if (stepTimeDiff >= setting('cMaxTime')) { //milliseconds
stepsTooLong++; //count steps which are note counted, because time too long stepsTooLong++; //count steps which are not counted, because time too long
steps--; steps--;
} }
//Remove step if time between first and second step is too short //Remove step if time between first and second step is too short
if (stepTimeDiff <= setting('cMinTime')) { //milliseconds if (stepTimeDiff <= setting('cMinTime')) { //milliseconds
stepsTooShort++; //count steps which are note counted, because time too short stepsTooShort++; //count steps which are not counted, because time too short
steps--; steps--;
} }
//Step threshold reached
if (steps >= setting('stepThreshold')) { if (steps >= setting('stepThreshold')) {
if (active == 0) { if (active == 0) {
stepsCounted = stepsCounted + (setting('stepThreshold') -1) ; //count steps needed to reach active status, last step is counted anyway, so treshold -1 stepsCounted = stepsCounted + (setting('stepThreshold') -1) ; //count steps needed to reach active status, last step is counted anyway, so treshold -1
stepsOutsideTime = stepsOutsideTime - 10; //substract steps needed to reac active status stepsOutsideTime = stepsOutsideTime - 10; //substract steps needed to reach active status
} }
active = 1; active = 1;
clearInterval(timerResetActive); //stop timer which resets active clearInterval(timerResetActive); //stop timer which resets active
@ -109,14 +138,17 @@
function draw() { function draw() {
var height = 23; //width is deined globally var height = 23; //width is deined globally
distance = (stepsCounted * setting('stepLength')) / 100 /1000 //distance in km distance = (stepsCounted * setting('stepLength')) / 100 /1000; //distance in km
//Check if same day //Check if same day
let date = new Date(); let date = new Date();
if (lastUpdate.getDate() == date.getDate()){ //if same day if (lastUpdate.getDate() == date.getDate()){ //if same day
} }
else { else { //different day, set all steps to 0
stepsCounted = 1; //set stepcount to 1 stepsCounted = 0;
stepsTooShort = 0;
stepsTooLong = 0;
stepsOutsideTime = 0;
} }
lastUpdate = date; lastUpdate = date;
@ -166,7 +198,7 @@
stepsTooLong : stepsTooLong, stepsTooLong : stepsTooLong,
stepsOutsideTime : stepsOutsideTime stepsOutsideTime : stepsOutsideTime
}; };
require("Storage").write(PEDOMFILE,d); //write array to file s.write(PEDOMFILE,d); //write array to file
}); });
//When Step is registered by firmware //When Step is registered by firmware
@ -182,8 +214,7 @@
}); });
//Read data from file and set variables //Read data from file and set variables
let pedomData = require("Storage").readJSON(PEDOMFILE,1); let pedomData = s.readJSON(PEDOMFILE,1);
if (pedomData) { if (pedomData) {
if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate); if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate);
stepsCounted = pedomData.stepsToday|0; stepsCounted = pedomData.stepsToday|0;
@ -191,12 +222,10 @@
stepsTooLong = pedomData.stepsTooLong; stepsTooLong = pedomData.stepsTooLong;
stepsOutsideTime = pedomData.stepsOutsideTime; stepsOutsideTime = pedomData.stepsOutsideTime;
} }
pedomdata = 0; //reset pedomdata to save memory pedomdata = 0; //reset pedomdata to save memory
setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive) setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive)
timerStoreData = setInterval(storeData, storeDataInterval); //store data regularly
//Add widget //Add widget
WIDGETS["activepedom"]={area:"tl",width:width,draw:draw}; WIDGETS["activepedom"]={area:"tl",width:width,draw:draw};
})(); })();

View File

@ -1,2 +1,3 @@
0.01: New widget and app! 0.01: New widget and app!
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme) 0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
0.03: Display only minutes:seconds when less than 1 hour left

View File

@ -1,93 +1,93 @@
(() => { (() => {
const storage = require('Storage'); const storage = require('Storage');
settingsChronowid = storage.readJSON("chronowid.json",1)||{}; //read settingsChronowid from file settingsChronowid = storage.readJSON("chronowid.json",1)||{}; //read settingsChronowid from file
var height = 23; var height = 23;
var width = 58; var width = 58;
var interval = 0; //used for the 1 second interval timer var interval = 0; //used for the 1 second interval timer
var now = new Date(); var now = new Date();
var time = 0; var time = 0;
var diff = settingsChronowid.goal - now; var diff = settingsChronowid.goal - now;
//Convert ms to time //Convert ms to time
function getTime(t) { function getTime(t) {
var milliseconds = parseInt((t % 1000) / 100), var milliseconds = parseInt((t % 1000) / 100),
seconds = Math.floor((t / 1000) % 60), seconds = Math.floor((t / 1000) % 60),
minutes = Math.floor((t / (1000 * 60)) % 60), minutes = Math.floor((t / (1000 * 60)) % 60),
hours = Math.floor((t / (1000 * 60 * 60)) % 24); hours = Math.floor((t / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours; hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes; minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds; seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds; return hours + ":" + minutes + ":" + seconds;
} }
function printDebug() { function printDebug() {
print ("Nowtime: " + getTime(now)); print ("Nowtime: " + getTime(now));
print ("Now: " + now); print ("Now: " + now);
print ("Goaltime: " + getTime(settingsChronowid.goal)); print ("Goaltime: " + getTime(settingsChronowid.goal));
print ("Goal: " + settingsChronowid.goal); print ("Goal: " + settingsChronowid.goal);
print("Difftime: " + getTime(diff)); print("Difftime: " + getTime(diff));
print("Diff: " + diff); print("Diff: " + diff);
print ("Started: " + settingsChronowid.started); print ("Started: " + settingsChronowid.started);
print ("----"); print ("----");
} }
//counts down, calculates and displays //counts down, calculates and displays
function countDown() { function countDown() {
now = new Date(); now = new Date();
diff = settingsChronowid.goal - now; //calculate difference diff = settingsChronowid.goal - now; //calculate difference
WIDGETS["chronowid"].draw(); WIDGETS["chronowid"].draw();
//time is up //time is up
if (settingsChronowid.started && diff < 1000) { if (settingsChronowid.started && diff < 1000) {
Bangle.buzz(1500); Bangle.buzz(1500);
//write timer off to file //write timer off to file
settingsChronowid.started = false; settingsChronowid.started = false;
storage.writeJSON('chronowid.json', settingsChronowid); storage.writeJSON('chronowid.json', settingsChronowid);
clearInterval(interval); //stop interval clearInterval(interval); //stop interval
} }
//printDebug(); //printDebug();
} }
// draw your widget // draw your widget
function draw() { function draw() {
if (!settingsChronowid.started) { if (!settingsChronowid.started) {
width = 0; width = 0;
return; //do not draw anything if timer is not started return; //do not draw anything if timer is not started
} }
g.reset(); g.reset();
if (diff >= 0) { if (diff >= 0) {
if (diff < 600000) { //less than 1 hour left if (diff < 3600000) { //less than 1 hour left
width = 58; width = 58;
g.clearRect(this.x,this.y,this.x+width,this.y+height); g.clearRect(this.x,this.y,this.x+width,this.y+height);
g.setFont("6x8", 2); g.setFont("6x8", 2);
g.drawString(getTime(diff).substring(3), this.x+1, this.y+5); //remove hour part 00:00:00 -> 00:00 g.drawString(getTime(diff).substring(3), this.x+1, this.y+5); //remove hour part 00:00:00 -> 00:00
} }
if (diff >= 600000) { //one hour or more left if (diff >= 3600000) { //one hour or more left
width = 48; width = 48;
g.clearRect(this.x,this.y,this.x+width,this.y+height); g.clearRect(this.x,this.y,this.x+width,this.y+height);
g.setFont("6x8", 1); g.setFont("6x8", 1);
g.drawString(getTime(diff), this.x+1, this.y+((height/2)-4)); //display hour 00:00:00 g.drawString(getTime(diff), this.x+1, this.y+((height/2)-4)); //display hour 00:00:00
} }
} }
// not needed anymoe, because we check if diff < 1000 now, so 00:00 is displayed. // not needed anymoe, because we check if diff < 1000 now, so 00:00 is displayed.
// else { // else {
// width = 58; // width = 58;
// g.clearRect(this.x,this.y,this.x+width,this.y+height); // g.clearRect(this.x,this.y,this.x+width,this.y+height);
// g.setFont("6x8", 2); // g.setFont("6x8", 2);
// g.drawString("END", this.x+15, this.y+5); // g.drawString("END", this.x+15, this.y+5);
// } // }
} }
if (settingsChronowid.started) interval = setInterval(countDown, 1000); //start countdown each second if (settingsChronowid.started) interval = setInterval(countDown, 1000); //start countdown each second
// add the widget // add the widget
WIDGETS["chronowid"]={area:"bl",width:width,draw:draw,reload:function() { WIDGETS["chronowid"]={area:"bl",width:width,draw:draw,reload:function() {
reload(); reload();
Bangle.drawWidgets(); // relayout all widgets Bangle.drawWidgets(); // relayout all widgets
}}; }};
//printDebug(); //printDebug();
countDown(); countDown();
})(); })();