pull/309/head
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",
"shortName":"Launcher",
"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.",
"tags": "tool,system,launcher",
"type":"launch",
@ -411,7 +411,7 @@
{ "id": "swatch",
"name": "Stopwatch",
"icon": "stopwatch.png",
"version":"0.05",
"version":"0.06",
"interface": "interface.html",
"description": "Simple stopwatch with Lap Time logging to a JSON file",
"tags": "health",
@ -1050,7 +1050,7 @@
"name": "Touch Launcher",
"shortName":"Menu",
"icon": "app.png",
"version":"0.05",
"version":"0.06",
"description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher",
"type":"launch",
@ -1123,20 +1123,35 @@
{"name":"openstmap.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "activepedom",
{ "id": "activepedom",
"name": "Active Pedometer",
"shortName":"Active Pedometer",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "Pedometer that filters out arm movement and displays a step goal progress.",
"tags": "outdoors,widget",
"type":"widget",
"readme": "README.md",
"storage": [
{"name":"activepedom.wid.js","url":"widget.js"},
{"name":"activepedom.settings.js","url":"settings.js"},
{"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",
"name": "Tabata",
"shortName": "Tabata - Control High-Intensity Interval Training",
@ -1206,7 +1221,7 @@
"name": "Numerals Clock",
"shortName": "Numerals Clock",
"icon": "numerals.png",
"version":"0.02",
"version":"0.03",
"description": "A simple big numerals clock",
"tags": "numerals,clock",
"type":"clock",
@ -1214,7 +1229,8 @@
"storage": [
{"name":"numerals.app.js","url":"numerals.app.js"},
{"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",

View File

@ -1 +1,2 @@
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.
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
* Two line display
* Can display distance (in km) or steps in each line
* 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'
* Progress bar for step goal
* 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)
* 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

View File

@ -4,6 +4,7 @@
*/
(function(back) {
const SETTINGS_FILE = 'activepedom.settings.json';
const LINES = ['Steps', 'Distance'];
// initialize with default settings...
let s = {
@ -13,6 +14,9 @@
'intervalResetActive' : 30000,
'stepSensitivity' : 80,
'stepGoal' : 10000,
'stepLength' : 75,
'lineOne': LINES[0],
'lineTwo': LINES[1],
};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
@ -27,7 +31,7 @@
return function (value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
WIDGETS["activepedom"].draw();
//WIDGETS["activepedom"].draw();
};
}
@ -76,6 +80,33 @@
step: 1000,
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);
});

View File

@ -8,22 +8,16 @@
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();
var width = 45;
var lastUpdate = new Date(); //used to reset counted steps on new day
var width = 45; //width of widget
//used for statistics and debugging
var stepsTooShort = 0;
var stepsTooLong = 0;
var stepsOutsideTime = 0;
//define default settings
const DEFAULTS = {
'cMaxTime' : 1100,
'cMinTime' : 240,
'stepThreshold' : 30,
'intervalResetActive' : 30000,
'stepSensitivity' : 80,
'stepGoal' : 10000,
};
var distance = 0; //distance travelled
const SETTINGS_FILE = 'activepedom.settings.json';
const PEDOMFILE = "activepedom.steps.json";
@ -32,10 +26,21 @@
function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
}
//return setting
function setting(key) {
if (!settings) { loadSettings(); }
return (key in settings) ? settings[key] : DEFAULTS[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];
}
function setStepSensitivity(s) {
@ -46,7 +51,7 @@
}
//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 >= 1000 && num < 10000) { //between 1.000 and 10.000
num = Math.floor(num/100)*100;
@ -99,11 +104,12 @@
else {
stepsOutsideTime++;
}
settings = 0; //reset settings to save memory
}
function draw() {
var height = 23; //width is deined globally
var stepsDisplayLarge = kFormatter(stepsCounted);
distance = (stepsCounted * setting('stepLength')) / 100 /1000 //distance in km
//Check if same day
let date = new Date();
@ -121,10 +127,21 @@
if (active == 1) g.setColor(0x07E0); //green
else g.setColor(0xFFFF); //white
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.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
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+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()
@ -164,6 +183,7 @@
//Read data from file and set variables
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
if (pedomData) {
if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate);
stepsCounted = pedomData.stepsToday|0;
@ -172,6 +192,8 @@
stepsOutsideTime = pedomData.stepsOutsideTime;
}
pedomdata = 0; //reset pedomdata to save memory
setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive)
//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 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)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first

View File

@ -220,7 +220,7 @@ var locales = {
int_curr_symbol: "ILS",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: F",
temperature: C",
ampm: { 0: "am", 1: "pm" },
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

View File

@ -1,2 +1,3 @@
0.01: New App!
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
### color:
### Color:
* rnd - shows numerals in different color combinations every time the watches wakes
* r/g - red/green
* y/w - yellow/white
* o/c - orange/cyan
* b/y - blue/yellow'ish
### draw mode
### Draw mode
* fill - fill 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 = {
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]],
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]],
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]],
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]],
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]],
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]],
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]],
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]],
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]],
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]],
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,92,82,100,73,100,65,92,65,27,59,27,51,19,51,9]],
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,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,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,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,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,20,98,9,98,1,90,1,86,56,22,9,22,1,14,1,9]],
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,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 _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
var _rCol = 0;
var interval = 0;
const REFRESH_RATE = 10E3;
function translate(tx, ty, p) {
function translate(tx, ty, p){
return p.map((x, i)=> x+((i%2)?ty:tx));
}
function fill(poly){
return g.fillPoly(poly);
return g.fillPoly(poly,true);
}
function frame(poly){
return g.drawPoly(poly);
return g.drawPoly(poly,true);
}
let settings = require('Storage').readJSON('numerals.json',1);
if (!settings) {
settings = {
color: 0,
drawMode: "fill"
color:0,
drawMode:"fill",
menuButton:24
};
}
function drawNum(num,col,x,y,func){
g.setColor(col);
let tx = x*100+35;
let ty = y*100+35;
let tx = x*100+25;
let ty = y*104+32;
for (let i=0;i<numerals[num].length;i++){
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){
let d = new Date();
let h1 = Math.floor(d.getHours()/10);
let h2 = d.getHours()%10;
let h1 = Math.floor((_12hour?d.getHours()%12:d.getHours())/10);
let h2 = (_12hour?d.getHours()%12:d.getHours())%10;
let m1 = Math.floor(d.getMinutes()/10);
let m2 = d.getMinutes()%10;
g.clearRect(0,24,240,240);
@ -70,7 +72,7 @@ function draw(drawMode){
Bangle.setLCDMode();
clearWatch();
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
setWatch(Bangle.showLauncher, settings.menuButton, {repeat:false,edge:"falling"});
g.clear();
clearInterval();
@ -78,8 +80,8 @@ if (settings.color>0) _rCol=settings.color-1;
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
draw(settings.drawMode);
Bangle.on('lcdPower', function(on) {
if (on) {
Bangle.on('lcdPower', function(on){
if (on){
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
draw(settings.drawMode);
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);

View File

@ -4,15 +4,17 @@
};
function resetSettings() {
numeralsSettings = {
color: 0,
drawMode: "fill"
color:0,
drawMode:"fill",
menuButton:22
};
updateSettings();
}
let numeralsSettings = storage.readJSON('numerals.json',1);
if (!numeralsSettings) resetSettings();
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={
"" : { "title":"Numerals"},
"Colors": {
@ -27,6 +29,12 @@
format: v=>dm[v],
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
};
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
0.04: Changed save file filename, add interface.html to allow laps to be loaded
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`;
lapData.forEach((lap,lapIndex) => {
lap.date = lap.n.substr(7,16).replace("_"," ");
lap.elapsed = lap.d.shift(); // remove first item
html += `
<div class="column col-12">
<div class="card-header">
<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 class="card-body">
<table class="table table-striped table-hover">

View File

@ -1,8 +1,11 @@
var tTotal = Date.now();
var tStart = Date.now();
var tCurrent = Date.now();
var started = false;
var timeY = 60;
var timeY = 45;
var hsXPos = 0;
var TtimeY = 75;
var ThsXPos = 0;
var lapTimes = [];
var displayInterval;
@ -12,6 +15,7 @@ function timeToText(t) {
var hs = Math.floor(t/10)%100;
return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2);
}
function updateLabels() {
g.reset(1);
g.clearRect(0,23,g.getWidth()-1,g.getHeight()-24);
@ -23,36 +27,54 @@ function updateLabels() {
g.setFont("6x8",1);
g.setFontAlign(-1,-1);
for (var i in lapTimes) {
if (i<16)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);}
else if (i<32)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-16)*8);}
if (i<15)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 40 + i*8);}
else if (i<30)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 40 + (i-15)*8);}
}
drawsecs();
}
function drawsecs() {
var t = tCurrent-tStart;
g.reset(1);
g.setFont("Vector",48);
g.setFontAlign(0,0);
var Tt = tCurrent-tTotal;
var secs = Math.floor(t/1000)%60;
var mins = Math.floor(t/60000);
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;
g.clearRect(0,timeY-26,200,timeY+26);
g.drawString(txt,x,timeY);
var Tx = 125;
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;
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();
}
function drawms() {
var t = tCurrent-tStart;
var hs = Math.floor(t/10)%100;
var Tt = tCurrent-tTotal;
var Ths = Math.floor(Tt/10)%100;
g.setFontAlign(-1,0);
g.setFont("6x8",2);
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() {
lapTimes.push(tCurrent-tTotal);
return lapTimes.map(timeToText).reverse();
}
@ -61,7 +83,8 @@ setWatch(function() { // Start/stop
Bangle.beep();
if (started)
tStart = Date.now()+tStart-tCurrent;
tCurrent = Date.now();
tTotal = Date.now()+tTotal-tCurrent;
tCurrent = Date.now();
if (displayInterval) {
clearInterval(displayInterval);
displayInterval = undefined;
@ -77,29 +100,33 @@ setWatch(function() { // Start/stop
drawms();
}, 20);
}, BTN2, {repeat:true});
setWatch(function() { // Lap
Bangle.beep();
if (started) {
tCurrent = Date.now();
lapTimes.unshift(tCurrent-tStart);
}
tStart = tCurrent;
if (!started) { // save
var timenow= Date();
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
require("Storage").writeJSON(filename, getLapTimesArray());
tStart = tCurrent = tTotal = Date.now();
lapTimes = [];
E.showMessage("Laps Saved","Stopwatch");
setTimeout(updateLabels, 1000);
} else {
tStart = tCurrent;
updateLabels();
}
}, BTN1, {repeat:true});
setWatch(function() { // Reset
if (!started) {
Bangle.beep();
tStart = tCurrent = Date.now();
lapTimes = [];
Bangle.beep();
tStart = tCurrent = tTotal = Date.now();
lapTimes = [];
}
updateLabels();
}, BTN3, {repeat:true});

View File

@ -3,3 +3,4 @@
0.03: Close launcher when lcd turn off
0.04: Complete rewrite to add animation and loop ( issue #210 )
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");
function getApps(){
return Storage.list(/\.info$/).filter(app => app.endsWith('.info')).map(app => Storage.readJSON(app,1) || { name: "DEAD: "+app.substr(1) })
.filter(app=>app.type=="app" || app.type=="clock" || !app.type)
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 && (app.type=="app" || app.type=="clock" || !app.type))
.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first