Merge pull request #1 from espruino/master

Grabbing Gordon's latest
master
Conor O'Neill 2019-11-08 14:03:16 +00:00 committed by GitHub
commit 5850633258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 580 additions and 58 deletions

View File

@ -1,6 +1,8 @@
Bangle.js App Loader (and Apps) Bangle.js App Loader (and Apps)
================================ ================================
Try it live at [banglejs.com/apps](https://banglejs.com/apps)
### How does it work? ### How does it work?
* A list of apps is in `apps.json` * A list of apps is in `apps.json`
@ -61,6 +63,7 @@ the *default* of `To RAM`
"name": "Readable name", // readable name "name": "Readable name", // readable name
"icon": "icon.png", // icon in apps/ "icon": "icon.png", // icon in apps/
"description": "...", // long description "description": "...", // long description
"type":"...", // optional(if app) - 'app' or 'widget'
"tags": "", // comma separated tag list for searching "tags": "", // comma separated tag list for searching
"custom": "custom.html", // if supplied, apps/custom.html is loaded in an "custom": "custom.html", // if supplied, apps/custom.html is loaded in an

View File

@ -9,19 +9,31 @@
], ],
"sortorder" : -1 "sortorder" : -1
}, },
{ "id": "clock", { "id": "mclock",
"name": "Morphing Clock", "name": "Morphing Clock",
"icon": "clock-morphing.png", "icon": "clock-morphing.png",
"description": "7 segment clock that morphs between minutes and hours", "description": "7 segment clock that morphs between minutes and hours",
"tags": "clock", "tags": "clock",
"type":"clock",
"storage": [ "storage": [
{"name":"+clock","url":"clock.json"}, {"name":"+mclock","url":"clock-morphing.json"},
{"name":"-clock","url":"clock-morphing.js"}, {"name":"-mclock","url":"clock-morphing.js"},
{"name":"*clock","url":"clock-icon.js","evaluate":true} {"name":"*mclock","url":"clock-morphing-icon.js","evaluate":true}
], ],
"sortorder" : -1 "sortorder" : -1
}, },
{ "id": "wclock",
"name": "Word Clock",
"icon": "clock-word.png",
"description": "Display Time as Text",
"tags": "clock",
"type":"clock",
"storage": [
{"name":"+wclock","url":"clock-word.json"},
{"name":"-wclock","url":"clock-word.js"},
{"name":"*wclock","url":"clock-word-icon.js","evaluate":true}
]
},
{ "id": "trex", { "id": "trex",
"name": "T-Rex", "name": "T-Rex",
"icon": "trex.png", "icon": "trex.png",
@ -98,17 +110,17 @@
{"name":"*slevel","url":"spiritlevel-icon.js","evaluate":true} {"name":"*slevel","url":"spiritlevel-icon.js","evaluate":true}
] ]
}, },
{ "id": "settings", { "id": "setting",
"name": "Settings", "name": "Settings",
"icon": "settings.png", "icon": "settings.png",
"description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
"tags": "tool,system", "tags": "tool,system",
"storage": [ "storage": [
{"name":"+settings","url":"settings.json"}, {"name":"+setting","url":"settings.json"},
{"name":"-settings","url":"settings.js"}, {"name":"-setting","url":"settings.js"},
{"name":"=settings","url":"settings-init.js"}, {"name":"=setting","url":"settings-init.js"},
{"name":"@settings","url":"settings-default.json","evaluate":true}, {"name":"@setting","url":"settings-default.json","evaluate":true},
{"name":"*settings","url":"settings-icon.js","evaluate":true} {"name":"*setting","url":"settings-icon.js","evaluate":true}
] ]
}, },
{ "id": "files", { "id": "files",
@ -127,7 +139,9 @@
"icon": "widget-battery.png", "icon": "widget-battery.png",
"description": "Show the current battery level and charging status in the top right of the clock", "description": "Show the current battery level and charging status in the top right of the clock",
"tags": "widget,battery", "tags": "widget,battery",
"type":"widget",
"storage": [ "storage": [
{"name":"+sbat","url":"widget-battery.json"},
{"name":"=sbat","url":"widget-battery.js"} {"name":"=sbat","url":"widget-battery.js"}
] ]
}, },
@ -136,7 +150,9 @@
"icon": "widget-bluetooth.png", "icon": "widget-bluetooth.png",
"description": "Show the current Bluetooth connection status in the top right of the clock", "description": "Show the current Bluetooth connection status in the top right of the clock",
"tags": "widget,bluetooth", "tags": "widget,bluetooth",
"type":"widget",
"storage": [ "storage": [
{"name":"+sbt","url":"widget-bluetooth.json"},
{"name":"=sbt","url":"widget-bluetooth.js"} {"name":"=sbt","url":"widget-bluetooth.js"}
] ]
}, },
@ -162,6 +178,28 @@
{"name":"*swatch","url":"stopwatch-icon.js","evaluate":true} {"name":"*swatch","url":"stopwatch-icon.js","evaluate":true}
] ]
}, },
{ "id": "hidmsic",
"name": "Bluetooth Music Controls",
"icon": "hid-music.png",
"description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
"tags": "bluetooth",
"storage": [
{"name":"+hidmsic","url":"hid-music.json"},
{"name":"-hidmsic","url":"hid-music.js"},
{"name":"*hidmsic","url":"hid-music-icon.js","evaluate":true}
]
},
{ "id": "hidkbd",
"name": "Bluetooth Keyboard",
"icon": "hid-keyboard.png",
"description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps",
"tags": "bluetooth",
"storage": [
{"name":"+hidkbd","url":"hid-keyboard.json"},
{"name":"-hidkbd","url":"hid-keyboard.js"},
{"name":"*hidkbd","url":"hid-keyboard-icon.js","evaluate":true}
]
},
{ "id": "animals", { "id": "animals",
"name": "Animals Game", "name": "Animals Game",
"icon": "animals.png", "icon": "animals.png",

View File

@ -1,5 +1,5 @@
{ {
"name":"Animals Game", "name":"Animals Game", "type":"app",
"icon":"*animals", "icon":"*animals",
"src":"-animals" "src":"-animals"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"Asteroids!", "name":"Asteroids!","type":"app",
"icon":"*astroid", "icon":"*astroid",
"src":"-astroid" "src":"-astroid"
} }

View File

@ -11,7 +11,7 @@ setWatch(function() {
apps = s.list().filter(a=>a[0]=='+').map(app=>{ apps = s.list().filter(a=>a[0]=='+').map(app=>{
try { return s.readJSON(app); } try { return s.readJSON(app); }
catch (e) { return {name:"DEAD: "+app.substr(1)} } catch (e) { return {name:"DEAD: "+app.substr(1)} }
}); }).filter(app=>app.type=="app" || app.type=="clock" || !app.type);
var selected = 0; var selected = 0;
var menuScroll = 0; var menuScroll = 0;
var menuShowing = false; var menuShowing = false;
@ -75,6 +75,12 @@ var WIDGETS={};
function drawWidgets() { function drawWidgets() {
Object.keys(WIDGETS).forEach(k=>WIDGETS[k].draw()); Object.keys(WIDGETS).forEach(k=>WIDGETS[k].draw());
} }
eval(require("Storage").read("-clock")); var clockApp = require("Storage").list().filter(a=>a[0]=='+').map(app=>{
try { return require("Storage").readJSON(app); }
catch (e) {}
}).find(app=>app.type=="clock");
if (clockApp) eval(require("Storage").read(clockApp.src));
else E.showMessage("No Clock Found");
delete clockApp;
require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget))); require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget)));
setTimeout(drawWidgets,100); setTimeout(drawWidgets,100);

5
apps/clock-morphing.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"Morphing Clock","type":"clock",
"icon":"*mclock",
"src":"-mclock"
}

1
apps/clock-word-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIIFiOAgEgoN4n+AsEw4f8mEAoEwwIaBAok/AoMZwP4AocBwARCjYuBAoUPAodgh0D/kQBYMGgP8IIseF4f8FINgnIFBmOAoEfBAM4AQMPAQIIBoEOAoMHCIILCkIFBjYFBDoUZNAJrCmPAsA4CkCMHn+Y8U/4MAl2M80/O4MEhnmTQQFB8gFDhu0CIcF3gLDg8cAokYAoOAAoQvB/A9H4BBDwPwX4P4nEDnAFBEAMGFIPDhk4g0MAoN8n4FD/ARBAoODuAFBjExwYdBEYMZwYOBgPwh8DhhBHACo"))

137
apps/clock-word.js Normal file
View File

@ -0,0 +1,137 @@
/* jshint esversion: 6 */
(function() {
const allWords = [
"ATWENTYD",
"QUARTERY",
"FIVEHALF",
"DPASTORO",
"FIVEIGHT",
"SIXTHREE",
"TWELEVEN",
"FOURNINE"
];
const hours = {
0: ["", 0, 0],
1: ["ONE", 17, 47, 77],
2: ["TWO", 06, 16, 17],
3: ["THREE", 35, 45, 55, 65, 75],
4: ["FOUR", 07, 17, 27, 37],
5: ["FIVE", 04, 14, 24, 34],
6: ["SIX", 05, 15, 25],
7: ["SEVEN", 05, 46, 56, 66, 67],
8: ["EIGHT", 34, 44, 54, 64, 74],
9: ["NINE", 47, 57, 67, 77],
10: ["TEN", 74, 75, 76],
11: ["ELEVEN", 26, 36, 46, 56, 66, 76],
12: ["TWELVE", 06, 16, 26, 36, 56, 66]
};
const mins = {
0: ["A", 0, 0],
1: ["FIVE", 02, 12, 22, 32],
2: ["TEN", 10, 30, 40],
3: ["QUARTER", 01, 11, 21, 31, 41, 51, 61],
4: ["TWENTY", 10, 20, 30, 40, 50, 60],
5: ["HALF", 42, 52, 62, 72],
6: ["PAST", 13, 23, 33, 43],
7: ["TO", 43, 53]
};
// offsets and incerments
const xs = 30;
const ys = 20;
const dy = 22;
const dx = 25;
// font size and color
const wordFontSize = 20;
const timeFontSize = 30;
const passivColor = 0x3186/*grey*/;
const activeColor = 0xF800/*red*/;
function drawWordClock() {
// get time
var t = new Date();
var h = t.getHours();
var m = t.getMinutes();
var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2);
var hidx;
var midx;
var midxA=[];
g.setFontVector(wordFontSize);
g.setColor(passivColor);
g.setFontAlign(0, -1, 0);
// draw allWords
var c;
var y = ys;
var x = xs;
allWords.forEach((line) => {
x = xs;
for (c in line) {
g.drawString(line[c], x, y);
x += dx;
}
y += dy;
});
// calc indexes
midx = Math.round(m / 5);
hidx = h % 12;
if (hidx === 0) { hidx = 12; }
if (midx > 6) {
if (midx == 12) { midx = 0; }
hidx++;
}
if (midx !== 0) {
if (midx <= 6) {
midxA = [midx, 6];
} else {
midxA = [12 - midx, 7];
}
}
// write hour in active color
g.setColor(activeColor);
g.setFontVector(wordFontSize);
hours[hidx][0].split('').forEach((c, pos) => {
x = xs + (hours[hidx][pos + 1] / 10 | 0) * dx;
y = ys + (hours[hidx][pos + 1] % 10) * dy;
g.drawString(c, x, y);
});
// write min words in active color
midxA.forEach(idx => {
mins[idx][0].split('').forEach((c, pos) => {
x = xs + (mins[idx][pos + 1] / 10 | 0) * dx;
y = ys + (mins[idx][pos + 1] % 10) * dy;
g.drawString(c, x, y);
});
});
// display digital time
g.setColor(activeColor);
g.setFontVector(timeFontSize);
g.clearRect(0,200,240,240);
g.drawString(time, 120, 200);
}
Bangle.on('lcdPower', function(on) {
if (on) {
drawWordClock();
drawWidgets();
}
});
g.clear();
setInterval(drawWordClock, 1E4);
drawWordClock();
})();

5
apps/clock-word.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"Word Clock","type":"clock",
"icon":"*wclock",
"src":"-wclock"
}

BIN
apps/clock-word.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

View File

@ -1,5 +0,0 @@
{
"name":"Clock",
"icon":"*clock",
"src":"-clock"
}

View File

@ -1,5 +1,5 @@
{ {
"name":"Compass", "name":"Compass","type":"app",
"icon":"*compass", "icon":"*compass",
"src":"-compass" "src":"-compass"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"App Manager", "name":"App Manager","type":"app",
"icon":"*files", "icon":"*files",
"src":"-files" "src":"-files"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"GPS Time", "name":"GPS Time","type":"app",
"icon":"*gpstime", "icon":"*gpstime",
"src":"-gpstime" "src":"-gpstime"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"Heart Rate", "name":"Heart Rate","type":"app",
"icon":"*hrm", "icon":"*hrm",
"src":"-hrm" "src":"-hrm"
} }

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A/AHeIDC+AC60IC/51ELoIXTCYMIDISLpC/67dxAAJHBYsLhAYKLhgXXHhWAdoQbBApAvhBgYcCAogXkI64XYhDtII/oMUERTyDABJ0FPI4A/AH4A/AGY="))

86
apps/hid-keyboard.js Normal file
View File

@ -0,0 +1,86 @@
var storage = require('Storage');
const settings = storage.readJSON('@setting') || { HID: false };
var sendHid, next, prev, toggle, up, down, profile;
if (settings.HID) {
profile = 'Keyboard';
sendHid = function (code, cb) {
try {
NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => {
NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => {
if (cb) cb();
});
});
} catch(e) {
print(e);
}
};
next = function (cb) { sendHid(0x4f, cb); };
prev = function (cb) { sendHid(0x50, cb); };
toggle = function (cb) { sendHid(0x2c, cb); };
up = function (cb) {sendHid(0x52, cb); };
down = function (cb) { sendHid(0x51, cb); };
} else {
E.showMessage('HID disabled');
setTimeout(load, 1000);
}
function drawApp() {
g.clear();
g.setFont("6x8",2);
g.setFontAlign(0,0);
g.drawString(profile, 120, 120);
const d = g.getWidth() - 18;
function c(a) {
return {
width: 8,
height: a.length,
bpp: 1,
buffer: (new Uint8Array(a)).buffer
};
}
g.drawImage(c([16,56,124,254,16,16,16,16]),d,40);
g.drawImage(c([16,16,16,16,254,124,56,16]),d,194);
g.drawImage(c([0,8,12,14,255,14,12,8]),d,116);
}
if (next) {
setWatch(function(e) {
var len = e.time - e.lastTime;
if (len > 0.3 && len < 0.9) {
E.showMessage('prev');
setTimeout(drawApp, 1000);
prev(() => {});
} else {
E.showMessage('up');
setTimeout(drawApp, 1000);
up(() => {});
}
}, BTN1, { edge:"falling",repeat:true,debounce:50});
setWatch(function(e) {
var len = e.time - e.lastTime;
if (len > 0.3 && len < 0.9) {
E.showMessage('next');
setTimeout(drawApp, 1000);
next(() => {});
} else {
E.showMessage('down');
setTimeout(drawApp, 1000);
down(() => {});
}
}, BTN3, { edge:"falling",repeat:true,debounce:50});
setWatch(function(e) {
E.showMessage('toggle')
setTimeout(drawApp, 1000);
toggle();
}, BTN2, { edge:"falling",repeat:true,debounce:50});
drawApp();
}

5
apps/hid-keyboard.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Keyboard Control","type":"app",
"icon": "*hidkbd",
"src": "-hidkbd"
}

BIN
apps/hid-keyboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

1
apps/hid-music-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au"))

85
apps/hid-music.js Normal file
View File

@ -0,0 +1,85 @@
var storage = require('Storage');
const settings = storage.readJSON('@setting') || { HID: false };
var sendHid, next, prev, toggle, up, down, profile;
if (settings.HID) {
profile = 'Music';
sendHid = function (code, cb) {
try {
NRF.sendHIDReport([1,code], () => {
NRF.sendHIDReport([1,0], () => {
if (cb) cb();
});
});
} catch(e) {
print(e);
}
};
next = function (cb) { sendHid(0x01, cb); };
prev = function (cb) { sendHid(0x02, cb); };
toggle = function (cb) { sendHid(0x10, cb); };
up = function (cb) {sendHid(0x40, cb); };
down = function (cb) { sendHid(0x80, cb); };
} else {
E.showMessage('HID disabled');
setTimeout(load, 1000);
}
function drawApp() {
g.clear();
g.setFont("6x8",2);
g.setFontAlign(0,0);
g.drawString(profile, 120, 120);
const d = g.getWidth() - 18;
function c(a) {
return {
width: 8,
height: a.length,
bpp: 1,
buffer: (new Uint8Array(a)).buffer
};
}
g.drawImage(c([16,56,124,254,16,16,16,16]),d,40);
g.drawImage(c([16,16,16,16,254,124,56,16]),d,194);
g.drawImage(c([0,8,12,14,255,14,12,8]),d,116);
}
if (next) {
setWatch(function(e) {
var len = e.time - e.lastTime;
if (len > 0.3 && len < 0.9) {
E.showMessage('prev');
setTimeout(drawApp, 1000);
prev(() => {});
} else {
E.showMessage('up');
setTimeout(drawApp, 1000);
up(() => {});
}
}, BTN1, { edge:"falling",repeat:true,debounce:50});
setWatch(function(e) {
var len = e.time - e.lastTime;
if (len > 0.3 && len < 0.9) {
E.showMessage('next');
setTimeout(drawApp, 1000);
next(() => {});
} else {
E.showMessage('down');
setTimeout(drawApp, 1000);
down(() => {});
}
}, BTN3, { edge:"falling",repeat:true,debounce:50});
setWatch(function(e) {
E.showMessage('toggle')
setTimeout(drawApp, 1000);
toggle();
}, BTN2, { edge:"falling",repeat:true,debounce:50});
drawApp();
}

5
apps/hid-music.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Music Control","type":"app",
"icon": "*hidmsic",
"src": "-hidmsic"
}

BIN
apps/hid-music.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

View File

@ -1,4 +1,4 @@
{ {
"name":"Open Location", "name":"Open Location","type":"app",
"src":"-openloc" "src":"-openloc"
} }

View File

@ -50,7 +50,7 @@ function fileLoaded() {
log("No 'coordinates' node found"); log("No 'coordinates' node found");
return; return;
} }
var coordinateLine = coordinateNode[0].textContent; var coordinateLine = coordinateNode[0].textContent.trim();
var coordinateList = coordinateLine.split(/\s+/); var coordinateList = coordinateLine.split(/\s+/);
var coords = []; var coords = [];
var pmin, pmax; var pmin, pmax;

View File

@ -1,10 +1,14 @@
(function() { (function() {
var s = require('Storage').readJSON('@settings'); var s = require('Storage').readJSON('@setting');
var adv = { uart: true };
if (s.HID) { if (s.HID) {
// Report from https://notes.iopush.net/custom-usb-hid-device-descriptor-media-keyboard/
Bangle.HID = new Uint8Array([ Bangle.HID = new Uint8Array([
// Keyboard Controls
0x05, 0x01, 0x05, 0x01,
0x09, 0x06, 0x09, 0x06,
0xA1, 0x01, 0xA1, 0x01,
0x85, 0x02,
0x05, 0x07, 0x05, 0x07,
0x19, 0xe0, 0x19, 0xe0,
0x29, 0xe7, 0x29, 0xe7,
@ -39,17 +43,42 @@
0x75, 0x08, 0x75, 0x08,
0x95, 0x02, 0x95, 0x02,
0xB1, 0x02, 0xB1, 0x02,
0xC0,
// Music Controls
0x05, 0x0C,
0x09, 0x01,
0xA1, 0x01,
0x85, 0x01,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x09, 0xB5,
0x81, 0x02,
0x09, 0xB6,
0x81, 0x02,
0x09, 0xB7,
0x81, 0x02,
0x09, 0xB8,
0x81, 0x02,
0x09, 0xCD,
0x81, 0x02,
0x09, 0xE2,
0x81, 0x02,
0x09, 0xE9,
0x81, 0x02,
0x09, 0xEA,
0x81, 0x02,
0xC0 0xC0
]); ]);
NRF.setServices(undefined, { adv.hid = Bangle.HID;
uart: true, hid: Bangle.HID,
});
} }
NRF.setServices(undefined, adv);
if (!s.vibrate) Bangle.buzz=()=>Promise.resolve(); if (!s.vibrate) Bangle.buzz=()=>Promise.resolve();
if (!s.beep) Bangle.beep=()=>Promise.resolve(); if (!s.beep) Bangle.beep=()=>Promise.resolve();
Bangle.setLCDTimeout(s.timeout); Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1); if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone); E.setTimeZone(s.timezone);
})(); })()

View File

@ -12,8 +12,8 @@ function debug(msg, arg) {
function updateSettings() { function updateSettings() {
debug('updating settings', settings); debug('updating settings', settings);
storage.erase('@settings'); storage.erase('@setting');
storage.write('@settings', settings); storage.write('@setting', settings);
} }
function resetSettings() { function resetSettings() {
@ -30,7 +30,7 @@ function resetSettings() {
} }
try { try {
settings = storage.readJSON('@settings'); settings = storage.readJSON('@setting');
} catch (e) {} } catch (e) {}
if (!settings) resetSettings(); if (!settings) resetSettings();
@ -99,6 +99,7 @@ function showMainMenu() {
updateSettings(); updateSettings();
} }
}, },
'Set Time': showSetTimeMenu,
'Reset': showResetMenu, 'Reset': showResetMenu,
'Turn Off': Bangle.off, 'Turn Off': Bangle.off,
'< Back': load '< Back': load
@ -121,15 +122,101 @@ function showResetMenu() {
}, },
// this is include for debugging. remove for production // this is include for debugging. remove for production
/*'Erase': () => { /*'Erase': () => {
storage.erase('=settings'); storage.erase('=setting');
storage.erase('-settings'); storage.erase('-setting');
storage.erase('@settings'); storage.erase('@setting');
storage.erase('*settings'); storage.erase('*setting');
storage.erase('+settings'); storage.erase('+setting');
E.reboot(); E.reboot();
}*/ }*/
}; };
return Bangle.menu(resetmenu); return Bangle.menu(resetmenu);
} }
function showSetTimeMenu() {
d = new Date();
const timemenu = {
'': {
'title': 'Set Time',
'predraw': function() {
d = new Date();
timemenu.Hour.value = d.getHours();
timemenu.Minute.value = d.getMinutes();
timemenu.Second.value = d.getSeconds();
timemenu.Date.value = d.getDate();
timemenu.Month.value = d.getMonth() + 1;
timemenu.Year.value = d.getFullYear();
}
},
'< Back': showMainMenu,
'Hour': {
value: d.getHours(),
min: 0,
max: 23,
step: 1,
onchange: v => {
d = new Date();
d.setHours(v);
setTime(d.getTime()/1000);
}
},
'Minute': {
value: d.getMinutes(),
min: 0,
max: 59,
step: 1,
onchange: v => {
d = new Date();
d.setMinutes(v);
setTime(d.getTime()/1000);
}
},
'Second': {
value: d.getSeconds(),
min: 0,
max: 59,
step: 1,
onchange: v => {
d = new Date();
d.setSeconds(v);
setTime(d.getTime()/1000);
}
},
'Date': {
value: d.getDate(),
min: 1,
max: 31,
step: 1,
onchange: v => {
d = new Date();
d.setDate(v);
setTime(d.getTime()/1000);
}
},
'Month': {
value: d.getMonth() + 1,
min: 1,
max: 12,
step: 1,
onchange: v => {
d = new Date();
d.setMonth(v - 1);
setTime(d.getTime()/1000);
}
},
'Year': {
value: d.getFullYear(),
min: d.getFullYear() - 10,
max: d.getFullYear() + 10,
step: 1,
onchange: v => {
d = new Date();
d.setFullYear(v);
setTime(d.getTime()/1000);
}
}
};
return Bangle.menu(timemenu);
}
showMainMenu(); showMainMenu();

View File

@ -1,5 +1,5 @@
{ {
"name": "Settings", "name": "Settings","type":"app",
"icon": "*settings", "icon": "*settings",
"src": "-settings" "src": "-settings"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"Speedo", "name":"Speedo","type":"app",
"icon":"*speedo", "icon":"*speedo",
"src":"-speedo" "src":"-speedo"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"Spirit Level", "name":"Spirit Level","type":"app",
"icon":"*slevel", "icon":"*slevel",
"src":"-slevel" "src":"-slevel"
} }

View File

@ -50,18 +50,19 @@ function drawms() {
setWatch(function() { // Start/stop setWatch(function() { // Start/stop
started = !started; started = !started;
if (started) Bangle.beep();
if (started)
tStart = Date.now()+tStart-tCurrent; tStart = Date.now()+tStart-tCurrent;
tCurrent = Date.now(); tCurrent = Date.now();
if (displayInterval) { if (displayInterval) {
clearInterval(displayInterval); clearInterval(displayInterval);
displayInterval = undefined; displayInterval = undefined;
} }
updateLabels(); updateLabels();
if (started) if (started)
displayInterval = setInterval(function() { displayInterval = setInterval(function() {
var last = tCurrent; var last = tCurrent;
if (started) tCurrent = Date.now(); if (started) tCurrent = Date.now();
if (Math.floor(last/1000)!=Math.floor(tCurrent/1000)) if (Math.floor(last/1000)!=Math.floor(tCurrent/1000))
drawsecs(); drawsecs();
else else
@ -69,12 +70,15 @@ setWatch(function() { // Start/stop
}, 20); }, 20);
}, BTN2, {repeat:true}); }, BTN2, {repeat:true});
setWatch(function() { // Reset setWatch(function() { // Reset
Bangle.beep();
if (!started) { if (!started) {
tStart = tCurrent = Date.now(); tStart = tCurrent = Date.now();
} }
lapTimes = [];
updateLabels(); updateLabels();
}, BTN1, {repeat:true}); }, BTN1, {repeat:true});
setWatch(function() { // Lap setWatch(function() { // Lap
Bangle.beep();
if (started) tCurrent = Date.now(); if (started) tCurrent = Date.now();
lapTimes.unshift(tCurrent-tStart); lapTimes.unshift(tCurrent-tStart);
tStart = tCurrent; tStart = tCurrent;

View File

@ -1,5 +1,5 @@
{ {
"name":"Stopwatch", "name":"Stopwatch","type":"app",
"icon":"*swatch", "icon":"*swatch",
"src":"-swatch" "src":"-swatch"
} }

View File

@ -1,5 +1,5 @@
{ {
"name":"T-Rex", "name":"T-Rex","type":"app",
"icon":"*trex", "icon":"*trex",
"src":"-trex" "src":"-trex"
} }

4
apps/widget-battery.json Normal file
View File

@ -0,0 +1,4 @@
{
"name":"Battery Level","type":"widget",
"src":"=sbat"
}

View File

@ -0,0 +1,4 @@
{
"name":"bluetooth","type":"widget",
"src":"=sbt"
}

View File

@ -18,18 +18,39 @@ uploadApp : app => {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
// Load all files // Load all files
Promise.all(app.storage.map(storageFile => { Promise.all(app.storage.map(storageFile => {
var promise;
if (storageFile.content) if (storageFile.content)
promise = Promise.resolve(storageFile.content); return Promise.resolve(storageFile);
else if (storageFile.url) else if (storageFile.url)
promise = httpGet("apps/"+storageFile.url); return httpGet("apps/"+storageFile.url).then(content => {
else promise = Promise.resolve(); return {
name : storageFile.name,
content : content,
evaluate : storageFile.evaluate
}});
else return Promise.resolve();
})).then(fileContents => { // now we just have a list of files + contents...
// filter out empty files
fileContents = fileContents.filter(x=>x!==undefined);
// then map each file to a command to load into storage // then map each file to a command to load into storage
return promise.then(contents => fileContents.forEach(storageFile => {
contents?`\x10require('Storage').write(${toJS(storageFile.name)},${storageFile.evaluate ? contents.trim() : toJS(contents)});`:"") // check if this is the JSON file
})) // now we just have a list of commands... if (storageFile.name[0]=="+") {
.then((fileContents) => { storageFile.evaluate = true;
fileContents = fileContents.join("\n")+"\n"; var json = {};
try {
json = JSON.parse(storageFile.content);
} catch (e) {
reject(storageFile.name+" is not valid JSON");
}
json.files = fileContents.map(storageFile=>storageFile.name).join(",");
storageFile.content = JSON.stringify(json);
}
// format ready for Espruino
var js = storageFile.evaluate ? storageFile.content.trim() : toJS(storageFile.content);
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
});
fileContents = fileContents.map(storageFile=>storageFile.cmd).join("\n")+"\n";
console.log("uploadApp",fileContents); console.log("uploadApp",fileContents);
// reset to ensure we have enough memory to upload what we need to // reset to ensure we have enough memory to upload what we need to
Puck.write("\x03reset();\n", (result) => { Puck.write("\x03reset();\n", (result) => {