commit
5850633258
|
|
@ -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
|
||||||
|
|
|
||||||
60
apps.json
60
apps.json
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Animals Game",
|
"name":"Animals Game", "type":"app",
|
||||||
"icon":"*animals",
|
"icon":"*animals",
|
||||||
"src":"-animals"
|
"src":"-animals"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Asteroids!",
|
"name":"Asteroids!","type":"app",
|
||||||
"icon":"*astroid",
|
"icon":"*astroid",
|
||||||
"src":"-astroid"
|
"src":"-astroid"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name":"Morphing Clock","type":"clock",
|
||||||
|
"icon":"*mclock",
|
||||||
|
"src":"-mclock"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgIIFiOAgEgoN4n+AsEw4f8mEAoEwwIaBAok/AoMZwP4AocBwARCjYuBAoUPAodgh0D/kQBYMGgP8IIseF4f8FINgnIFBmOAoEfBAM4AQMPAQIIBoEOAoMHCIILCkIFBjYFBDoUZNAJrCmPAsA4CkCMHn+Y8U/4MAl2M80/O4MEhnmTQQFB8gFDhu0CIcF3gLDg8cAokYAoOAAoQvB/A9H4BBDwPwX4P4nEDnAFBEAMGFIPDhk4g0MAoN8n4FD/ARBAoODuAFBjExwYdBEYMZwYOBgPwh8DhhBHACo"))
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name":"Word Clock","type":"clock",
|
||||||
|
"icon":"*wclock",
|
||||||
|
"src":"-wclock"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 843 B |
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"name":"Clock",
|
|
||||||
"icon":"*clock",
|
|
||||||
"src":"-clock"
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Compass",
|
"name":"Compass","type":"app",
|
||||||
"icon":"*compass",
|
"icon":"*compass",
|
||||||
"src":"-compass"
|
"src":"-compass"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"App Manager",
|
"name":"App Manager","type":"app",
|
||||||
"icon":"*files",
|
"icon":"*files",
|
||||||
"src":"-files"
|
"src":"-files"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"GPS Time",
|
"name":"GPS Time","type":"app",
|
||||||
"icon":"*gpstime",
|
"icon":"*gpstime",
|
||||||
"src":"-gpstime"
|
"src":"-gpstime"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Heart Rate",
|
"name":"Heart Rate","type":"app",
|
||||||
"icon":"*hrm",
|
"icon":"*hrm",
|
||||||
"src":"-hrm"
|
"src":"-hrm"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AH4A/AHeIDC+AC60IC/51ELoIXTCYMIDISLpC/67dxAAJHBYsLhAYKLhgXXHhWAdoQbBApAvhBgYcCAogXkI64XYhDtII/oMUERTyDABJ0FPI4A/AH4A/AGY="))
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Keyboard Control","type":"app",
|
||||||
|
"icon": "*hidkbd",
|
||||||
|
"src": "-hidkbd"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 506 B |
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au"))
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Music Control","type":"app",
|
||||||
|
"icon": "*hidmsic",
|
||||||
|
"src": "-hidmsic"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 632 B |
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"name":"Open Location",
|
"name":"Open Location","type":"app",
|
||||||
"src":"-openloc"
|
"src":"-openloc"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
})();
|
})()
|
||||||
|
|
||||||
|
|
|
||||||
103
apps/settings.js
103
apps/settings.js
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Settings",
|
"name": "Settings","type":"app",
|
||||||
"icon": "*settings",
|
"icon": "*settings",
|
||||||
"src": "-settings"
|
"src": "-settings"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Speedo",
|
"name":"Speedo","type":"app",
|
||||||
"icon":"*speedo",
|
"icon":"*speedo",
|
||||||
"src":"-speedo"
|
"src":"-speedo"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Spirit Level",
|
"name":"Spirit Level","type":"app",
|
||||||
"icon":"*slevel",
|
"icon":"*slevel",
|
||||||
"src":"-slevel"
|
"src":"-slevel"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"Stopwatch",
|
"name":"Stopwatch","type":"app",
|
||||||
"icon":"*swatch",
|
"icon":"*swatch",
|
||||||
"src":"-swatch"
|
"src":"-swatch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name":"T-Rex",
|
"name":"T-Rex","type":"app",
|
||||||
"icon":"*trex",
|
"icon":"*trex",
|
||||||
"src":"-trex"
|
"src":"-trex"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name":"Battery Level","type":"widget",
|
||||||
|
"src":"=sbat"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name":"bluetooth","type":"widget",
|
||||||
|
"src":"=sbt"
|
||||||
|
}
|
||||||
39
comms.js
39
comms.js
|
|
@ -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) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue