msdeibel 2020-04-16 11:27:36 +02:00
commit 22e8926d95
25 changed files with 443 additions and 86 deletions

View File

@ -41,7 +41,7 @@
"name": "Default Launcher", "name": "Default Launcher",
"shortName":"Launcher", "shortName":"Launcher",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
"tags": "tool,system,launcher", "tags": "tool,system,launcher",
"type":"launch", "type":"launch",
@ -411,7 +411,7 @@
{ "id": "swatch", { "id": "swatch",
"name": "Stopwatch", "name": "Stopwatch",
"icon": "stopwatch.png", "icon": "stopwatch.png",
"version":"0.05", "version":"0.06",
"interface": "interface.html", "interface": "interface.html",
"description": "Simple stopwatch with Lap Time logging to a JSON file", "description": "Simple stopwatch with Lap Time logging to a JSON file",
"tags": "health", "tags": "health",
@ -1050,7 +1050,7 @@
"name": "Touch Launcher", "name": "Touch Launcher",
"shortName":"Menu", "shortName":"Menu",
"icon": "app.png", "icon": "app.png",
"version":"0.05", "version":"0.06",
"description": "Touch enable left to right launcher.", "description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher", "tags": "tool,system,launcher",
"type":"launch", "type":"launch",
@ -1127,16 +1127,31 @@
"name": "Active Pedometer", "name": "Active Pedometer",
"shortName":"Active Pedometer", "shortName":"Active Pedometer",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"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.",
"tags": "outdoors,widget", "tags": "outdoors,widget",
"type":"widget", "type":"widget",
"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}
] ]
}, },
{ "id": "chronowid",
"name": "Chrono Widget",
"shortName":"Chrono Widget",
"icon": "app.png",
"version":"0.01",
"description": "Chronometer (timer) which runs as widget.",
"tags": "tools,widget",
"readme": "README.md",
"storage": [
{"name":"chronowid.wid.js","url":"widget.js"},
{"name":"chronowid.app.js","url":"app.js"},
{"name":"chronowid.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "tabata", { "id": "tabata",
"name": "Tabata", "name": "Tabata",
"shortName": "Tabata - Control High-Intensity Interval Training", "shortName": "Tabata - Control High-Intensity Interval Training",
@ -1206,7 +1221,7 @@
"name": "Numerals Clock", "name": "Numerals Clock",
"shortName": "Numerals Clock", "shortName": "Numerals Clock",
"icon": "numerals.png", "icon": "numerals.png",
"version":"0.02", "version":"0.03",
"description": "A simple big numerals clock", "description": "A simple big numerals clock",
"tags": "numerals,clock", "tags": "numerals,clock",
"type":"clock", "type":"clock",
@ -1214,7 +1229,8 @@
"storage": [ "storage": [
{"name":"numerals.app.js","url":"numerals.app.js"}, {"name":"numerals.app.js","url":"numerals.app.js"},
{"name":"numerals.img","url":"numerals-icon.js","evaluate":true}, {"name":"numerals.img","url":"numerals-icon.js","evaluate":true},
{"name":"numerals.settings.js","url":"numerals.settings.js"} {"name":"numerals.settings.js","url":"numerals.settings.js"},
{"name":"numerals.json","url":"numerals-default.json","evaluate":true}
] ]
}, },
{ "id": "bledetect", { "id": "bledetect",

View File

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

View File

@ -1,4 +1,4 @@
# Improved 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.
@ -19,8 +19,9 @@ When you reach the step threshold, the steps needed to reach the threshold are c
## Features ## Features
* Two line display * Two line display
* Can display distance (in km) or steps in each line
* Large number for good readability * Large number for good readability
* Small number with the exact steps counted * Small number with the exact steps counted or more exact distance
* Large number is displayed in green when status is 'active' * Large number is displayed in green when status is 'active'
* Progress bar for step goal * Progress bar for step goal
* Counts steps only if they are reached in a certain time * Counts steps only if they are reached in a certain time
@ -29,9 +30,23 @@ 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
## Development version ## Settings
* https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/pedometer * Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100
* Min time (ms): Minimum time between two steps in milliseconds, steps will not be counted if fallen below. Standard: 240
* Step threshold: How many steps are needed to reach 'active' mode. If you do not reach the threshold in the 'Active Reset' time, the steps are not counted. Standard: 30
* Act.Res. (ms): Active Reset. After how many miliseconds will the 'active mode' reset. You have to reach the step threshold in this time, otherwise the steps are not counted. Standard: 30000
* Step sens.: Step Sensitivity. How sensitive should the sted detection be? This changes sensitivity in step detection in the firmware. Standard in firmware: 80
* Step goal: This is your daily step goal. Standard: 10000
* Step length: Length of one step in cm. Standard: 75
* Line One: What to display in line one, steps or distance. Standard: steps
* Line Two: What to display in line two, steps or distance. Standard: distance
## Releases
* Offifical app loader: https://github.com/espruino/BangleApps/tree/master/apps/activepedom (https://banglejs.com/apps)
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/activepedom (https://purple-tentacle.github.io/BangleApps/#widget)
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/pedometer
## Requests ## Requests

View File

@ -4,6 +4,7 @@
*/ */
(function(back) { (function(back) {
const SETTINGS_FILE = 'activepedom.settings.json'; const SETTINGS_FILE = 'activepedom.settings.json';
const LINES = ['Steps', 'Distance'];
// initialize with default settings... // initialize with default settings...
let s = { let s = {
@ -13,6 +14,9 @@
'intervalResetActive' : 30000, 'intervalResetActive' : 30000,
'stepSensitivity' : 80, 'stepSensitivity' : 80,
'stepGoal' : 10000, 'stepGoal' : 10000,
'stepLength' : 75,
'lineOne': LINES[0],
'lineTwo': LINES[1],
}; };
// ...and overwrite them with any saved values // ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings // This way saved values are preserved if a new version adds more settings
@ -27,7 +31,7 @@
return function (value) { return function (value) {
s[key] = value; s[key] = value;
storage.write(SETTINGS_FILE, s); storage.write(SETTINGS_FILE, s);
WIDGETS["activepedom"].draw(); //WIDGETS["activepedom"].draw();
}; };
} }
@ -76,6 +80,33 @@
step: 1000, step: 1000,
onchange: save('stepGoal'), onchange: save('stepGoal'),
}, },
'Step length (cm)': {
value: s.stepLength,
min: 1,
max: 150,
step: 1,
onchange: save('stepLength'),
},
'Line One': {
format: () => s.lineOne,
onchange: function () {
// cycles through options
const oldIndex = LINES.indexOf(s.lineOne)
const newIndex = (oldIndex + 1) % LINES.length
s.lineOne = LINES[newIndex]
save('lineOne')(s.lineOne)
},
},
'Line Two': {
format: () => s.lineTwo,
onchange: function () {
// cycles through options
const oldIndex = LINES.indexOf(s.lineTwo)
const newIndex = (oldIndex + 1) % LINES.length
s.lineTwo = LINES[newIndex]
save('lineTwo')(s.lineTwo)
},
},
}; };
E.showMenu(menu); E.showMenu(menu);
}); });

View File

@ -8,22 +8,16 @@
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(); var lastUpdate = new Date(); //used to reset counted steps on new day
var width = 45; var width = 45; //width of widget
//used for statistics and debugging
var stepsTooShort = 0; var stepsTooShort = 0;
var stepsTooLong = 0; var stepsTooLong = 0;
var stepsOutsideTime = 0; var stepsOutsideTime = 0;
//define default settings var distance = 0; //distance travelled
const DEFAULTS = {
'cMaxTime' : 1100,
'cMinTime' : 240,
'stepThreshold' : 30,
'intervalResetActive' : 30000,
'stepSensitivity' : 80,
'stepGoal' : 10000,
};
const SETTINGS_FILE = 'activepedom.settings.json'; const SETTINGS_FILE = 'activepedom.settings.json';
const PEDOMFILE = "activepedom.steps.json"; const PEDOMFILE = "activepedom.steps.json";
@ -32,8 +26,19 @@
function loadSettings() { function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}; settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
} }
//return setting //return setting
function setting(key) { 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(); } if (!settings) { loadSettings(); }
return (key in settings) ? settings[key] : DEFAULTS[key]; return (key in settings) ? settings[key] : DEFAULTS[key];
} }
@ -46,7 +51,7 @@
} }
//format number to make them shorter //format number to make them shorter
function kFormatter(num) { function kFormatterSteps(num) {
if (num <= 999) return num; //smaller 1.000, return 600 as 600 if (num <= 999) return num; //smaller 1.000, return 600 as 600
if (num >= 1000 && num < 10000) { //between 1.000 and 10.000 if (num >= 1000 && num < 10000) { //between 1.000 and 10.000
num = Math.floor(num/100)*100; num = Math.floor(num/100)*100;
@ -99,11 +104,12 @@
else { else {
stepsOutsideTime++; stepsOutsideTime++;
} }
settings = 0; //reset settings to save memory
} }
function draw() { function draw() {
var height = 23; //width is deined globally var height = 23; //width is deined globally
var stepsDisplayLarge = kFormatter(stepsCounted); 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();
@ -121,10 +127,21 @@
if (active == 1) g.setColor(0x07E0); //green if (active == 1) g.setColor(0x07E0); //green
else g.setColor(0xFFFF); //white else g.setColor(0xFFFF); //white
g.setFont("6x8", 2); g.setFont("6x8", 2);
g.drawString(stepsDisplayLarge,this.x+1,this.y); //first line, big number
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.setFont("6x8", 1);
g.setColor(0xFFFF); //white g.setColor(0xFFFF); //white
g.drawString(stepsCounted,this.x+1,this.y+14); //second line, small number 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 //draw step goal bar
stepGoalPercent = (stepsCounted / setting('stepGoal')) * 100; stepGoalPercent = (stepsCounted / setting('stepGoal')) * 100;
@ -136,6 +153,8 @@
g.fillRect(this.x, this.y+height, this.x+1, this.y+height-1); //draw start of bar 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+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 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() //This event is called just before the device shuts down for commands such as reset(), load(), save(), E.reboot() or Bangle.off()
@ -164,6 +183,7 @@
//Read data from file and set variables //Read data from file and set variables
let pedomData = require("Storage").readJSON(PEDOMFILE,1); let pedomData = require("Storage").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;
@ -172,6 +192,8 @@
stepsOutsideTime = pedomData.stepsOutsideTime; stepsOutsideTime = pedomData.stepsOutsideTime;
} }
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)
//Add widget //Add widget

1
apps/chronowid/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New widget and app!

35
apps/chronowid/README.md Normal file
View File

@ -0,0 +1,35 @@
# Chronometer Widget
Chronometer (timer) that runs as a widget.
The advantage is, that you can still see your normal watchface and other widgets when the timer is running.
The widget is always active, but only shown when the timer is on.
Hours, minutes, seconds and timer status can be set with an app.
## Screenshots
TBD
## Features
* Using other apps does not interrupt the timer, no need to keep the widget open (BUT: there will be no buzz when the time is up, for that the widget has to be loaded)
* Target time is saved to a file and timer picks up again when widget is loaded again.
## Settings
There are no settings section in the settings app, timer can be set using an app.
* Hours: Set the hours for the timer
* Minutes: Set the minutes for the timer
* Seconds: Set the seconds for the timer
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app. The widget is always there, but only visible when timer is on.
## Releases
* Offifical app loader: Not yet published.
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
## Requests
If you have any feature requests, please contact me on the Espruino forum: http://forum.espruino.com/profiles/155005/

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwIFCn/8BYYFRABcD4AFFgIFCh/wgeAAoP//8HCYMDAoPD8EAg4FB8PwgEf+EP/H4HQOAgP8uEAvwfBv0ggBFCn4CB/EBwEfgEB+AFBh+AgfgAoI1BIoQJB4AHBAoXgg4uBAIIFCCYQFGh5rDJQJUBK4IFCNYIFVDoopDGoJiBHYYFKVYRZBWIYDBA4IFBNIQzBG4IbBToKkBAQKVFUIYICVoQUCXIQmCYoIsCaITqDAoLvDNYUAA="))

91
apps/chronowid/app.js Normal file
View File

@ -0,0 +1,91 @@
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
const storage = require('Storage');
const boolFormat = v => v ? "On" : "Off";
let settingsChronowid;
function updateSettings() {
var now = new Date();
const goal = new Date(now.getFullYear(), now.getMonth(), now.getDate(),
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
settingsChronowid.goal = goal.getTime();
storage.writeJSON('chronowid.json', settingsChronowid);
}
function resetSettings() {
settingsChronowid = {
hours : 0,
minutes : 0,
seconds : 0,
started : false,
counter : 0,
goal : 0,
};
updateSettings();
}
settingsChronowid = storage.readJSON('chronowid.json',1);
if (!settingsChronowid) resetSettings();
E.on('kill', () => {
print("-KILL-");
updateSettings();
});
function showMenu() {
const timerMenu = {
'': {
'title': 'Set timer',
'predraw': function() {
timerMenu.hours.value = settingsChronowid.hours;
timerMenu.minutes.value = settingsChronowid.minutes;
timerMenu.seconds.value = settingsChronowid.seconds;
timerMenu.started.value = settingsChronowid.started;
}
},
'Hours': {
value: settingsChronowid.hours,
min: 0,
max: 24,
step: 1,
onchange: v => {
settingsChronowid.hours = v;
updateSettings();
}
},
'Minutes': {
value: settingsChronowid.minutes,
min: 0,
max: 59,
step: 1,
onchange: v => {
settingsChronowid.minutes = v;
updateSettings();
}
},
'Seconds': {
value: settingsChronowid.seconds,
min: 0,
max: 59,
step: 1,
onchange: v => {
settingsChronowid.seconds = v;
updateSettings();
}
},
'Timer on': {
value: settingsChronowid.started,
format: boolFormat,
onchange: v => {
settingsChronowid.started = v;
updateSettings();
}
},
};
timerMenu['-Exit-'] = ()=>{load();};
return E.showMenu(timerMenu);
}
showMenu();

BIN
apps/chronowid/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

93
apps/chronowid/widget.js Normal file
View File

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

2
apps/launch/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Only store relevant app data (saves RAM when many apps)

View File

@ -1,5 +1,5 @@
var s = require("Storage"); var s = require("Storage");
var apps = s.list(/\.info$/).map(app=>s.readJSON(app,1)||{name:"DEAD: "+app.substr(1)}).filter(app=>app.type=="app" || app.type=="clock" || !app.type); var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src}}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
apps.sort((a,b)=>{ apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder); var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first if (n) return n; // do sortorder first

View File

@ -220,7 +220,7 @@ var locales = {
int_curr_symbol: "ILS", int_curr_symbol: "ILS",
speed: "kmh", speed: "kmh",
distance: { 0: "m", 1: "km" }, distance: { 0: "m", 1: "km" },
temperature: F", temperature: C",
ampm: { 0: "am", 1: "pm" }, ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020 datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Use BTN2 for settings menu like other clocks 0.02: Use BTN2 for settings menu like other clocks
0.03: maximize numerals, make menu button configurable, change icon to mac palette, add default settings file, respect 12hour setting

View File

@ -5,13 +5,16 @@ Settings can be accessed through the app/widget settings menu of the Bangle.js
## Settings available ## Settings available
### color: ### Color:
* rnd - shows numerals in different color combinations every time the watches wakes * rnd - shows numerals in different color combinations every time the watches wakes
* r/g - red/green * r/g - red/green
* y/w - yellow/white * y/w - yellow/white
* o/c - orange/cyan * o/c - orange/cyan
* b/y - blue/yellow'ish * b/y - blue/yellow'ish
### draw mode ### Draw mode
* fill - fill numerals * fill - fill numerals
* frame - only shows outline of numerals * frame - only shows outline of numerals
### Menu button
* choose button to start launcher menu with

View File

@ -0,0 +1,5 @@
{
color:0,
drawMode:"fill",
menuButton:22
}

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwwhC/ABMBzIADyAJIAAkQBoMZBIoXCBIwADyIkIGAIuKGAQkIBJIwEEKQANC/4XWR58RiIHFWpAXFe4QRFcpAXFewQRFcxAXEFwQwGA4QXKiAXDGAgX/C/4X/C/4X/C7uQCwcBBwYXNBwYuEC54wCFwgXPzMRiIHFC54AHC/4XiCAoXRhIHDyK3GAAwOBJA0QG45VGC4YwCD4YwKFwgABcgIfEAwIAHBwgA/AAgA==")) require("heatshrink").decompress(atob("mEwwhC/ABXdAAfQBJAAEBgUNBJ4mGBKAmFEhAuLEwQhSABoX/C6yPPYw61IB4r3DHxoIFCwQIHC5YuDCIo3HC4oWEBI4X/C/4X/C/4X/C7XQC4gOEC5gwEBA4XLGAYOFC5oPCA44XNAA4X/C8SAGC6q4CCxb4EG5guICAgfIFxQA/ADg"))

View File

@ -7,57 +7,59 @@
*/ */
var numerals = { var numerals = {
0:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[30,21,61,21,69,29,69,61,61,69,30,69,22,61,22,29,30,21]], 0:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,9],[30,25,61,25,69,33,69,67,61,75,30,75,22,67,22,33]],
1:[[59,1,82,1,90,9,90,82,82,90,73,90,65,82,65,27,59,27,51,19,51,9,59,1]], 1:[[59,1,82,1,90,9,90,92,82,100,73,100,65,92,65,27,59,27,51,19,51,9]],
2:[[9,1,82,1,90,9,90,47,82,55,21,55,21,64,82,64,90,72,90,82,82,90,9,90,1,82,1,43,9,35,70,35,70,25,9,25,1,17,1,9,9,1]], 2:[[9,1,82,1,90,9,90,53,82,61,21,61,21,74,82,74,90,82,90,92,82,100,9,100,1,92,1,48,9,40,70,40,70,27,9,27,1,19,1,9]],
3:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,74,9,66,70,66,70,57,9,57,1,49,1,41,9,33,70,33,70,25,9,25,1,17,1,9,9,1]], 3:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,82,9,74,70,74,70,61,9,61,1,53,1,48,9,40,70,40,70,27,9,27,1,19,1,9]],
4:[[9,1,14,1,22,9,22,34,69,34,69,9,77,1,82,1,90,9,90,82,82,90,78,90,70,82,70,55,9,55,1,47,1,9,9,1]], 4:[[9,1,14,1,22,9,22,36,69,36,69,9,77,1,82,1,90,9,90,92,82,100,78,100,70,92,70,61,9,61,1,53,1,9]],
5:[[9,1,82,1,90,9,90,17,82,25,21,25,21,35,82,35,90,43,90,82,82,90,9,90,1,82,1,72,9,64,71,64,71,55,9,55,1,47,1,9,9,1]], 5:[[9,1,82,1,90,9,90,19,82,27,21,27,21,40,82,40,90,48,90,92,82,100,9,100,1,92,1,82,9,74,71,74,71,61,9,61,1,53,1,9]],
6:[[9,1,82,1,90,9,90,14,82,22,22,22,22,36,82,36,90,44,90,82,82,90,9,90,1,82,1,9,9,1],[22,55,69,55,69,69,22,69,22,55]], 6:[[9,1,82,1,90,9,90,19,82,27,22,27,22,40,82,40,90,48,90,92,82,100,9,100,1,92,1,9],[22,60,69,60,69,74,22,74]],
7:[[9,1,82,1,90,9,90,15,15,90,9,90,1,82,1,76,54,23,9,23,1,15,1,9,9,1]], 7:[[9,1,82,1,90,9,90,15,20,98,9,98,1,90,1,86,56,22,9,22,1,14,1,9]],
8:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[22,22,69,22,69,36,22,36,22,22],[22,55,69,55,69,69,22,69,22,55]], 8:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,9],[22,27,69,27,69,43,22,43],[22,58,69,58,69,74,22,74]],
9:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,77,9,69,69,69,69,55,9,55,1,47,1,9,9,1],[22,22,69,22,69,36,22,36,22,22]], 9:[[9,1,82,1,90,9,90,92,82,100,9,100,1,92,1,82,9,74,69,74,69,61,9,61,1,53,1,9],[22,27,69,27,69,41,22,41]],
}; };
var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
var _hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"]; var _hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"];
var _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"]; var _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
var _rCol = 0; var _rCol = 0;
var interval = 0; var interval = 0;
const REFRESH_RATE = 10E3; const REFRESH_RATE = 10E3;
function translate(tx, ty, p) { function translate(tx, ty, p){
return p.map((x, i)=> x+((i%2)?ty:tx)); return p.map((x, i)=> x+((i%2)?ty:tx));
} }
function fill(poly){ function fill(poly){
return g.fillPoly(poly); return g.fillPoly(poly,true);
} }
function frame(poly){ function frame(poly){
return g.drawPoly(poly); return g.drawPoly(poly,true);
} }
let settings = require('Storage').readJSON('numerals.json',1); let settings = require('Storage').readJSON('numerals.json',1);
if (!settings) { if (!settings) {
settings = { settings = {
color: 0, color:0,
drawMode: "fill" drawMode:"fill",
menuButton:24
}; };
} }
function drawNum(num,col,x,y,func){ function drawNum(num,col,x,y,func){
g.setColor(col); g.setColor(col);
let tx = x*100+35; let tx = x*100+25;
let ty = y*100+35; let ty = y*104+32;
for (let i=0;i<numerals[num].length;i++){ for (let i=0;i<numerals[num].length;i++){
if (i>0) g.setColor((func==fill)?"#000000":col); if (i>0) g.setColor((func==fill)?"#000000":col);
func(translate(tx, ty,numerals[num][i])); func(translate(tx,ty,numerals[num][i]));
} }
} }
function draw(drawMode){ function draw(drawMode){
let d = new Date(); let d = new Date();
let h1 = Math.floor(d.getHours()/10); let h1 = Math.floor((_12hour?d.getHours()%12:d.getHours())/10);
let h2 = d.getHours()%10; let h2 = (_12hour?d.getHours()%12:d.getHours())%10;
let m1 = Math.floor(d.getMinutes()/10); let m1 = Math.floor(d.getMinutes()/10);
let m2 = d.getMinutes()%10; let m2 = d.getMinutes()%10;
g.clearRect(0,24,240,240); g.clearRect(0,24,240,240);
@ -70,7 +72,7 @@ function draw(drawMode){
Bangle.setLCDMode(); Bangle.setLCDMode();
clearWatch(); clearWatch();
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); setWatch(Bangle.showLauncher, settings.menuButton, {repeat:false,edge:"falling"});
g.clear(); g.clear();
clearInterval(); clearInterval();
@ -78,8 +80,8 @@ if (settings.color>0) _rCol=settings.color-1;
interval=setInterval(draw, REFRESH_RATE, settings.drawMode); interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
draw(settings.drawMode); draw(settings.drawMode);
Bangle.on('lcdPower', function(on) { Bangle.on('lcdPower', function(on){
if (on) { if (on){
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length); if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
draw(settings.drawMode); draw(settings.drawMode);
interval=setInterval(draw, REFRESH_RATE, settings.drawMode); interval=setInterval(draw, REFRESH_RATE, settings.drawMode);

View File

@ -4,15 +4,17 @@
}; };
function resetSettings() { function resetSettings() {
numeralsSettings = { numeralsSettings = {
color: 0, color:0,
drawMode: "fill" drawMode:"fill",
menuButton:22
}; };
updateSettings(); updateSettings();
} }
let numeralsSettings = storage.readJSON('numerals.json',1); let numeralsSettings = storage.readJSON('numerals.json',1);
if (!numeralsSettings) resetSettings(); if (!numeralsSettings) resetSettings();
let dm = ["fill","frame"]; let dm = ["fill","frame"];
let col = ["rnd","r/g","y/w","o/c","b/y"] let col = ["rnd","r/g","y/w","o/c","b/y"];
let btn = [[24,"BTN1"],[22,"BTN2"],[23,"BTN3"],[11,"BTN4"],[16,"BTN5"]];
var menu={ var menu={
"" : { "title":"Numerals"}, "" : { "title":"Numerals"},
"Colors": { "Colors": {
@ -27,6 +29,12 @@
format: v=>dm[v], format: v=>dm[v],
onchange: v=> { numeralsSettings.drawMode=dm[v]; updateSettings();} onchange: v=> { numeralsSettings.drawMode=dm[v]; updateSettings();}
}, },
"Menu button": {
value: 1|btn[numeralsSettings.menuButton],
min:0,max:4,
format: v=>btn[v][1],
onchange: v=> { numeralsSettings.menuButton=btn[v][0]; updateSettings();}
},
"< back": back "< back": back
}; };
E.showMenu(menu); E.showMenu(menu);

View File

@ -5,3 +5,4 @@
Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running
0.04: Changed save file filename, add interface.html to allow laps to be loaded 0.04: Changed save file filename, add interface.html to allow laps to be loaded
0.05: Added widgets 0.05: Added widgets
0.06: Added total running time, moved lap time to smaller display, total run time now appends as first entry in array, saving now saves last lap as well

View File

@ -17,11 +17,12 @@ function getLapTimes() {
<div class="columns">\n`; <div class="columns">\n`;
lapData.forEach((lap,lapIndex) => { lapData.forEach((lap,lapIndex) => {
lap.date = lap.n.substr(7,16).replace("_"," "); lap.date = lap.n.substr(7,16).replace("_"," ");
lap.elapsed = lap.d.shift(); // remove first item
html += ` html += `
<div class="column col-12"> <div class="column col-12">
<div class="card-header"> <div class="card-header">
<div class="card-title h5">${lap.date}</div> <div class="card-title h5">${lap.date}</div>
<div class="card-subtitle text-gray">${lap.d.length} Laps</div> <div class="card-subtitle text-gray">${lap.d.length} Laps, total time ${lap.elapsed}</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">

View File

@ -1,8 +1,11 @@
var tTotal = Date.now();
var tStart = Date.now(); var tStart = Date.now();
var tCurrent = Date.now(); var tCurrent = Date.now();
var started = false; var started = false;
var timeY = 60; var timeY = 45;
var hsXPos = 0; var hsXPos = 0;
var TtimeY = 75;
var ThsXPos = 0;
var lapTimes = []; var lapTimes = [];
var displayInterval; var displayInterval;
@ -12,6 +15,7 @@ function timeToText(t) {
var hs = Math.floor(t/10)%100; var hs = Math.floor(t/10)%100;
return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2); return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2);
} }
function updateLabels() { function updateLabels() {
g.reset(1); g.reset(1);
g.clearRect(0,23,g.getWidth()-1,g.getHeight()-24); g.clearRect(0,23,g.getWidth()-1,g.getHeight()-24);
@ -23,36 +27,54 @@ function updateLabels() {
g.setFont("6x8",1); g.setFont("6x8",1);
g.setFontAlign(-1,-1); g.setFontAlign(-1,-1);
for (var i in lapTimes) { for (var i in lapTimes) {
if (i<16) if (i<15)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);} {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 40 + i*8);}
else if (i<32) else if (i<30)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-16)*8);} {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 40 + (i-15)*8);}
} }
drawsecs(); drawsecs();
} }
function drawsecs() { function drawsecs() {
var t = tCurrent-tStart; var t = tCurrent-tStart;
g.reset(1); var Tt = tCurrent-tTotal;
g.setFont("Vector",48);
g.setFontAlign(0,0);
var secs = Math.floor(t/1000)%60; var secs = Math.floor(t/1000)%60;
var mins = Math.floor(t/60000); var mins = Math.floor(t/60000);
var txt = mins+":"+("0"+secs).substr(-2); var txt = mins+":"+("0"+secs).substr(-2);
var Tsecs = Math.floor(Tt/1000)%60;
var Tmins = Math.floor(Tt/60000);
var Ttxt = Tmins+":"+("0"+Tsecs).substr(-2);
var x = 100; var x = 100;
g.clearRect(0,timeY-26,200,timeY+26); var Tx = 125;
g.drawString(txt,x,timeY); g.reset(1);
g.setFont("Vector",38);
g.setFontAlign(0,0);
g.clearRect(0,timeY-21,200,timeY+21);
g.drawString(Ttxt,x,timeY);
hsXPos = 5+x+g.stringWidth(txt)/2; hsXPos = 5+x+g.stringWidth(txt)/2;
g.setFont("6x8",2);
g.clearRect(0,TtimeY-7,200,TtimeY+7);
g.drawString(txt,Tx,TtimeY);
ThsXPos = 5+Tx+g.stringWidth(Ttxt)/2;
drawms(); drawms();
} }
function drawms() { function drawms() {
var t = tCurrent-tStart; var t = tCurrent-tStart;
var hs = Math.floor(t/10)%100; var hs = Math.floor(t/10)%100;
var Tt = tCurrent-tTotal;
var Ths = Math.floor(Tt/10)%100;
g.setFontAlign(-1,0); g.setFontAlign(-1,0);
g.setFont("6x8",2); g.setFont("6x8",2);
g.clearRect(hsXPos,timeY,220,timeY+20); g.clearRect(hsXPos,timeY,220,timeY+20);
g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10); g.drawString("."+("0"+Ths).substr(-2),hsXPos-5,timeY+14);
g.setFont("6x8",1);
g.clearRect(ThsXPos,TtimeY,220,TtimeY+5);
g.drawString("."+("0"+hs).substr(-2),ThsXPos-5,TtimeY+3);
} }
function getLapTimesArray() { function getLapTimesArray() {
lapTimes.push(tCurrent-tTotal);
return lapTimes.map(timeToText).reverse(); return lapTimes.map(timeToText).reverse();
} }
@ -61,6 +83,7 @@ setWatch(function() { // Start/stop
Bangle.beep(); Bangle.beep();
if (started) if (started)
tStart = Date.now()+tStart-tCurrent; tStart = Date.now()+tStart-tCurrent;
tTotal = Date.now()+tTotal-tCurrent;
tCurrent = Date.now(); tCurrent = Date.now();
if (displayInterval) { if (displayInterval) {
clearInterval(displayInterval); clearInterval(displayInterval);
@ -77,28 +100,32 @@ setWatch(function() { // Start/stop
drawms(); drawms();
}, 20); }, 20);
}, BTN2, {repeat:true}); }, BTN2, {repeat:true});
setWatch(function() { // Lap setWatch(function() { // Lap
Bangle.beep(); Bangle.beep();
if (started) { if (started) {
tCurrent = Date.now(); tCurrent = Date.now();
lapTimes.unshift(tCurrent-tStart); lapTimes.unshift(tCurrent-tStart);
} }
tStart = tCurrent;
if (!started) { // save if (!started) { // save
var timenow= Date();
var filename = "swatch-"+(new Date()).toISOString().substr(0,16).replace("T","_")+".json"; var filename = "swatch-"+(new Date()).toISOString().substr(0,16).replace("T","_")+".json";
if (tCurrent!=tStart)
lapTimes.unshift(tCurrent-tStart);
// this maxes out the 28 char maximum // this maxes out the 28 char maximum
require("Storage").writeJSON(filename, getLapTimesArray()); require("Storage").writeJSON(filename, getLapTimesArray());
tStart = tCurrent = tTotal = Date.now();
lapTimes = [];
E.showMessage("Laps Saved","Stopwatch"); E.showMessage("Laps Saved","Stopwatch");
setTimeout(updateLabels, 1000); setTimeout(updateLabels, 1000);
} else { } else {
tStart = tCurrent;
updateLabels(); updateLabels();
} }
}, BTN1, {repeat:true}); }, BTN1, {repeat:true});
setWatch(function() { // Reset setWatch(function() { // Reset
if (!started) { if (!started) {
Bangle.beep(); Bangle.beep();
tStart = tCurrent = Date.now(); tStart = tCurrent = tTotal = Date.now();
lapTimes = []; lapTimes = [];
} }
updateLabels(); updateLabels();

View File

@ -3,3 +3,4 @@
0.03: Close launcher when lcd turn off 0.03: Close launcher when lcd turn off
0.04: Complete rewrite to add animation and loop ( issue #210 ) 0.04: Complete rewrite to add animation and loop ( issue #210 )
0.05: Improve perf 0.05: Improve perf
0.06: Only store relevant app data (saves RAM when many apps)

View File

@ -5,8 +5,8 @@ g.flip();
const Storage = require("Storage"); const Storage = require("Storage");
function getApps(){ function getApps(){
return Storage.list(/\.info$/).filter(app => app.endsWith('.info')).map(app => Storage.readJSON(app,1) || { name: "DEAD: "+app.substr(1) }) return Storage.list(/\.info$/).map(app=>{var a=Storage.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src,version:a.version}})
.filter(app=>app.type=="app" || app.type=="clock" || !app.type) .filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type))
.sort((a,b)=>{ .sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder); var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first if (n) return n; // do sortorder first