(() => { var stepTimeDiff = 9999; //Time difference between two steps var startTimeStep = new Date(); //set start time var stopTimeStep = 0; //Time after one step var timerResetActive = 0; //timer to reset active //var timerStoreData = 0; //timer to store data var steps = 0; //steps taken var stepsCounted = 0; //active steps counted var active = 0; //x steps in y seconds achieved var stepGoalPercent = 0; //percentage of step goal var stepGoalBarLength = 0; //length og progress bar var lastUpdate = new Date(); //used to reset counted steps on new day var width = 46; //width of widget //used for statistics and debugging var stepsTooShort = 0; var stepsTooLong = 0; var stepsOutsideTime = 0; var distance = 0; //distance travelled const s = require('Storage'); const SETTINGS_FILE = 'activepedom.settings.json'; const PEDOMFILE = "activepedom.steps.json"; var dataFile; var storeDataInterval = 5*60*1000; //ms let settings; //load settings function loadSettings() { settings = s.readJSON(SETTINGS_FILE, 1) || {}; } function storeData() { let now = new Date(); let month = now.getMonth() + 1; //month is 0-based if (month < 10) month = "0" + month; //leading 0 let filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; //new file for each day dataFile = s.open(filename,"a"); if (dataFile) { //check if filen already exists if (dataFile.getLength() == 0) { //new day, set steps to 0 stepsCounted = 0; stepsTooShort = 0; stepsTooLong = 0; stepsOutsideTime = 0; } dataFile.write([ now.getTime(), stepsCounted, active, stepsTooShort, stepsTooLong, stepsOutsideTime, ].join(",")+"\n"); } dataFile = undefined; //save memory } //return setting function setting(key) { //define default settings const DEFAULTS = { 'cMaxTime' : 1100, 'cMinTime' : 240, 'stepThreshold' : 30, 'intervalResetActive' : 30000, 'stepSensitivity' : 80, 'stepGoal' : 10000, 'stepLength' : 75, 'lineOne' : "Distance", 'lineTwo' : "Steps", }; if (!settings) { loadSettings(); } return (key in settings) ? settings[key] : DEFAULTS[key]; } function setStepSensitivity(s) { function sqr(x) { return x*x; } var X=sqr(8192-s); var Y=sqr(8192+s); Bangle.setOptions({stepCounterThresholdLow:X,stepCounterThresholdHigh:Y}); } //format number to make them shorter function kFormatterSteps(num) { if (num <= 999) return num; //smaller 1.000, return 600 as 600 if (num >= 1000 && num < 10000) { //between 1.000 and 10.000 num = Math.floor(num/100)*100; return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; //return 1600 as 1.6k } if (num >= 10000) { //greater 10.000 num = Math.floor(num/1000)*1000; return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; //return 10.600 as 10k } } //Set Active to 0 function resetActive() { active = 0; steps = 0; if (Bangle.isLCDOn()) WIDGETS["activepedom"].draw(); } function calcSteps() { stopTimeStep = new Date(); //stop time after each step stepTimeDiff = stopTimeStep - startTimeStep; //time between steps in milliseconds startTimeStep = new Date(); //start time again //Remove step if time between first and second step is too long if (stepTimeDiff >= setting('cMaxTime')) { //milliseconds stepsTooLong++; //count steps which are not counted, because time too long steps--; } //Remove step if time between first and second step is too short if (stepTimeDiff <= setting('cMinTime')) { //milliseconds stepsTooShort++; //count steps which are not counted, because time too short steps--; } //Step threshold reached if (steps >= setting('stepThreshold')) { if (active == 0) { 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 reach active status } active = 1; clearInterval(timerResetActive); //stop timer which resets active timerResetActive = setInterval(resetActive, setting('intervalResetActive')); //reset active after timer runs out steps = 0; } if (active == 1) { stepsCounted++; //count steps } else { stepsOutsideTime++; } settings = 0; //reset settings to save memory } function draw() { var height = 23; //width is deined globally distance = (stepsCounted * setting('stepLength')) / 100 /1000; //distance in km //Check if same day let date = new Date(); if (lastUpdate.getDate() == date.getDate()){ //if same day } else { //different day, set all steps to 0 stepsCounted = 0; stepsTooShort = 0; stepsTooLong = 0; stepsOutsideTime = 0; } lastUpdate = date; // not everyone likes a widget, having refreshed lastUpdate we can exit if (setting('lineOne') == 'Hide' && setting('lineTwo') == 'Hide') { settings = 0; //reset settings to save memory return; } g.reset(); g.clearRect(this.x, this.y, this.x+width, this.y+height); //draw numbers if (active == 1) g.setColor(0x07E0); //green else g.setColor(0xFFFF); //white g.setFont("6x8", 2); if (setting('lineOne') == 'Steps') { g.drawString(kFormatterSteps(stepsCounted),this.x+1,this.y); //first line, big number, steps } if (setting('lineOne') == 'Distance') { g.drawString(distance.toFixed(2),this.x+1,this.y); //first line, big number, distance } g.setFont("6x8", 1); g.setColor(0xFFFF); //white if (setting('lineTwo') == 'Steps') { g.drawString(stepsCounted,this.x+1,this.y+14); //second line, small number, steps } if (setting('lineTwo') == 'Distance') { g.drawString(distance.toFixed(3) + "km",this.x+1,this.y+14); //second line, small number, distance } //draw step goal bar stepGoalPercent = (stepsCounted / setting('stepGoal')) * 100; stepGoalBarLength = width / 100 * stepGoalPercent; if (stepGoalBarLength > width) stepGoalBarLength = width; //do not draw across width of widget g.setColor(0x7BEF); //grey g.fillRect(this.x, this.y+height, this.x+width, this.y+height); // draw background bar g.setColor(0xFFFF); //white g.fillRect(this.x, this.y+height, this.x+1, this.y+height-1); //draw start of bar g.fillRect(this.x+width, this.y+height, this.x+width-1, this.y+height-1); //draw end of bar g.fillRect(this.x, this.y+height, this.x+stepGoalBarLength, this.y+height); // draw progress bar settings = 0; //reset settings to save memory } //This event is called just before the device shuts down for commands such as reset(), load(), save(), E.reboot() or Bangle.off() E.on('kill', () => { let d = { //define array to write to file lastUpdate : lastUpdate.toISOString(), stepsToday : stepsCounted, stepsTooShort : stepsTooShort, stepsTooLong : stepsTooLong, stepsOutsideTime : stepsOutsideTime }; s.write(PEDOMFILE,d); //write array to file }); //When Step is registered by firmware Bangle.on('step', (up) => { steps++; //increase step count calcSteps(); if (Bangle.isLCDOn()) WIDGETS["activepedom"].draw(); }); // redraw when the LCD turns on Bangle.on('lcdPower', function(on) { if (on) WIDGETS["activepedom"].draw(); }); // Read data from file and set variables { // new scope ensures pedomData gets freed let pedomData = s.readJSON(PEDOMFILE,1); if (pedomData) { if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate); stepsCounted = pedomData.stepsToday|0; stepsTooShort = pedomData.stepsTooShort; stepsTooLong = pedomData.stepsTooLong; stepsOutsideTime = pedomData.stepsOutsideTime; } } setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive) /*timerStoreData =*/ setInterval(storeData, storeDataInterval); //store data regularly //Add widget, use: WIDGETS.activepedom.getSteps() inside another App to return todays step count WIDGETS["activepedom"]={area:"tl",width:width,draw:draw, getSteps:()=>stepsCounted}; })();