1
0
Fork 0

add other apps

master
Gordon Williams 2019-11-06 22:12:54 +00:00
parent fe2a5e6f28
commit 819c67d442
18 changed files with 494 additions and 0 deletions

View File

@ -1,4 +1,13 @@
[
{ "id": "boot",
"name": "Bootloader",
"icon": "bootloader.png",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"tags": "tool,system",
"storage": [
{"name":".bootcde","url":"bootloader.js"}
]
},
{ "id": "trex",
"name": "T-Rex",
"icon": "trex.png",
@ -75,6 +84,19 @@
{"name":"*slevel","url":"spiritlevel-icon.js","evaluate":true}
]
},
{ "id": "settings",
"name": "Settings",
"icon": "settings.png",
"description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
"tags": "tool,system",
"storage": [
{"name":"+settings","url":"settings.json"},
{"name":"-settings","url":"settings.js"},
{"name":"=settings","url":"settings-init.js"},
{"name":"@settings","url":"settings-default.json","evaluate":true},
{"name":"*settings","url":"settings-icon.js","evaluate":true}
]
},
{ "id": "sbat",
"name": "Battery Level Widget",
"icon": "widget-battery.png",
@ -84,6 +106,28 @@
{"name":"=sbat","url":"widget-battery.js"}
]
},
{ "id": "hrm",
"name": "Heart Rate Monitor",
"icon": "heartrate.png",
"description": "Measure your current heart rate",
"tags": "health",
"storage": [
{"name":"+hrm","url":"heartrate.json"},
{"name":"-hrm","url":"heartrate.js"},
{"name":"*hrm","url":"heartrate-icon.js","evaluate":true}
]
},
{ "id": "swatch",
"name": "Stopwatch",
"icon": "stopwatch.png",
"description": "Simple stopwatch with Lap Time recording",
"tags": "health",
"storage": [
{"name":"+swatch","url":"stopwatch.json"},
{"name":"-swatch","url":"stopwatch.js"},
{"name":"*swatch","url":"stopwatch-icon.js","evaluate":true}
]
},
{ "id": "qrcode",
"name": "Custom QR Code",
"icon": "qrcode.png",

74
apps/bootloader.js Normal file
View File

@ -0,0 +1,74 @@
E.setTimeZone(1);
E.setFlags({pretokenise:1});
setWatch(function() {
Bangle.setLCDMode("direct");
g.clear();
clearInterval();
clearWatch();
Bangle.removeAllListeners();
var s = require("Storage");
var apps = s.list().filter(a=>a[0]=='+').map(app=>s.readJSON(app));
var selected = 0;
var menuScroll = 0;
var menuShowing = false;
function drawMenu() {
g.setFont("6x8",2);
g.setFontAlign(-1,0);
var n = 3;
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
if (selected<menuScroll) menuScroll = selected;
if (menuScroll) g.fillPoly([120,0,100,20,140,20]);
else g.clearRect(100,0,140,20);
if (apps.length>n+menuScroll) g.fillPoly([120,239,100,219,140,219]);
else g.clearRect(100,219,140,239);
for (var i=0;i<n;i++) {
var app = apps[i+menuScroll];
if (!app) break;
var y = 24+i*64;
if (i+menuScroll==selected) {
g.setColor(0.3,0.3,0.3);
g.fillRect(0,y,239,y+63);
g.setColor(1,1,1);
g.drawRect(0,y,239,y+63);
} else
g.clearRect(0,y,239,y+63);
if (app.icon) g.drawImage(s.read(app.icon),8,y+8);
g.drawString(app.name,64,y+32);
}
}
drawMenu();
setWatch(function() {
if (selected>0) {
selected--;
drawMenu();
}
}, BTN1, {repeat:true});
setWatch(function() {
if (selected+1<apps.length) {
selected++;
drawMenu();
}
}, BTN3, {repeat:true});
setWatch(function() { // run
clearWatch();
g.clear();
g.setFont("6x8",2);
g.setFontAlign(0,0);
g.drawString("Loading...",120,120);
if (apps[selected].name=="Clock") load();
else { // load like this so we ensure we've cleared out our RAM
var cmd = 'eval(require("Storage").read("'+apps[selected].src+'"));';
setTimeout(cmd,20);
}
}, BTN2, {repeat:true});
}, BTN2, {repeat:false}); // menu on middle button
var WIDGETPOS={tl:32,tr:g.getWidth()-32,bl:32,br:g.getWidth()-32};
var WIDGETS={};
function drawWidgets() {
Object.keys(WIDGETS).forEach(k=>WIDGETS[k].draw());
}
eval(require("Storage").read("-clock"));
require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget)));
setTimeout(drawWidgets,100);

BIN
apps/bootloader.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

1
apps/heartrate-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA="))

69
apps/heartrate.js Normal file
View File

@ -0,0 +1,69 @@
Bangle.setLCDPower(1);
Bangle.setLCDTimeout(0);
Bangle.ioWr(0x80,0)
x=0;
var min=0,max=0;
var wasHigh = 0, wasLow = 0;
var lastHigh = getTime();
var hrmList = [];
var hrm;
function readHRM() {
var a = analogRead(D29);
var h = getTime();
min=Math.min(min*0.97+a*0.03,a);
max=Math.max(max*0.97+a*0.03,a);
y = E.clip(170 - (a*960*4),100,230);
if (x==0) {
g.clearRect(0,100,239,239);
g.moveTo(-100,0);
}
/*g.setColor(0,1,0);
var z = 170 - (min*960*4); g.fillRect(x,z,x,z);
var z = 170 - (max*960*4); g.fillRect(x,z,x,z);*/
g.setColor(1,1,1);
g.lineTo(x,y);
if ((max-min)>0.005) {
if (4*a > (min+3*max)) { // high
g.setColor(1,0,0);
g.fillRect(x,230,x,239);
g.setColor(1,1,1);
if (!wasHigh && wasLow) {
var currentHrm = 60/(h-lastHigh);
lastHigh = h;
if (currentHrm<250) {
while (hrmList.length>12) hrmList.shift();
hrmList.push(currentHrm);
// median filter
var t = hrmList.slice(); // copy
t.sort();
// average the middle 3
var mid = t.length>>1;
hrm = (t[mid]+t[mid+1]+t[mid+2])/3;
g.setFontVector(40);
g.setFontAlign(0,0);
g.clearRect(0,0,239,100);
var str = Math.round(hrm);
var px = 120;
g.drawString(str,px,40);
px += g.stringWidth(str)/2;
g.setFont("6x8");
g.drawString("BPM",px+20,60);
}
}
wasLow = 0;
wasHigh = 1;
} else if (4*a < (max+3*min)) { // low
wasLow = 1;
} else { // middle
g.setColor(0.5,0,0);
g.fillRect(x,230,x,239);
g.setColor(1,1,1);
wasHigh = 0;
}
}
x++;
if (x>239)x=0;
}
setInterval(readHRM,50);

5
apps/heartrate.json Normal file
View File

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

BIN
apps/heartrate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,8 @@
{
timeout: 10, // Default LCD timeout in seconds
vibrate: true, // Vibration enabled by default. App must support
beep: true, // Beep enabled by default. App must support
timezone: 0, // Set the timezone for the device
HID : false, // BLE HID mode, off by default
debug: false, // Debug mode disabled by default. App must support
}

1
apps/settings-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A/ABEIxGAAgwWO/+IAoIECDB0I////GIxGPAoIXR//854DCC54TCAAYXQCYXIDYYXOOIP4xnMAAJgBPoSMKOIP8xgXDGQJ9CGREIOIXMxmICwITBxh9CC5BEBBoIWBOweMMYZKIWwRdBAgQeDBYYXKFAKnFA4YXIKgJeBFwj0DI5UICgIXDOoIXC5gCBYRIXCN4I+CDgQXCYBJIBBwIXGBQIXX9AXJI4QXHI5Z3K/h3LTYanHX4TvLxhICAAaXCd5gnDd4gLDI5X4xj0CAAPIGwbvJIAeIDAQWBIwXPO5EIRIPP/mM5AFB5HIA4IFBC5DZECAPMDQJdB5AUKJQ3IxnvAgIsLC4ZYCAAgXnCIJxCXgQXPYYJxCAgKMMDAoRCAggA/AH4A/AAoA="))

55
apps/settings-init.js Normal file
View File

@ -0,0 +1,55 @@
(function() {
var s = require('Storage').readJSON('@settings');
if (s.HID) {
Bangle.HID = new Uint8Array([
0x05, 0x01,
0x09, 0x06,
0xA1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x95, 0x05,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x05,
0x91, 0x02,
0x95, 0x01,
0x75, 0x03,
0x91, 0x01,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x25, 0x73,
0x05, 0x07,
0x19, 0x00,
0x29, 0x73,
0x81, 0x00,
0x09, 0x05,
0x15, 0x00,
0x26, 0xFF, 0x00,
0x75, 0x08,
0x95, 0x02,
0xB1, 0x02,
0xC0
]);
NRF.setServices(undefined, {
uart: true, hid: Bangle.HID,
});
}
if (!s.vibrate) Bangle.buzz=()=>Promise.resolve();
if (!s.beep) Bangle.beep=()=>Promise.resolve();
Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone);
})();

135
apps/settings.js Normal file
View File

@ -0,0 +1,135 @@
Bangle.setLCDPower(1);
Bangle.setLCDTimeout(0);
g.clear();
const storage = require('Storage');
let settings;
function debug(msg, arg) {
if (settings.debug)
console.log(msg, arg);
}
function updateSettings() {
debug('updating settings', settings);
storage.erase('@settings');
storage.write('@settings', settings);
}
function resetSettings() {
settings = {
timeout: 10,
vibrate: true,
beep: true,
timezone: 0,
HID : false,
debug: false,
};
setLCDTimeout(settings.timeout);
updateSettings();
}
try {
settings = storage.readJSON('@settings');
} catch (e) {}
if (!settings) resetSettings();
const boolFormat = (v) => v ? "On" : "Off";
function showMainMenu() {
const mainmenu = {
'': { 'title': 'Settings' },
'LCD Timeout': {
value: settings.timeout,
min: 0,
max: 60,
step: 5,
onchange: v => {
settings.timeout = 0 | v;
updateSettings();
Bangle.setLCDTimeout(settings.timeout);
}
},
'Beep': {
value: settings.beep,
format: boolFormat,
onchange: () => {
settings.beep = !settings.beep;
updateSettings();
if (settings.beep) {
Bangle.beep(1);
}
}
},
'Vibration': {
value: settings.vibrate,
format: boolFormat,
onchange: () => {
settings.vibrate = !settings.vibrate;
updateSettings();
if (settings.vibrate) {
VIBRATE.write(1);
setTimeout(()=>VIBRATE.write(0), 10);
}
}
},
'Time Zone': {
value: settings.timezone,
min: -11,
max: 12,
step: 1,
onchange: v => {
settings.timezone = 0 | v;
updateSettings();
}
},
'HID': {
value: settings.HID,
format: boolFormat,
onchange: () => {
settings.HID = !settings.HID;
updateSettings();
}
},
'Debug': {
value: settings.debug,
format: boolFormat,
onchange: () => {
settings.debug = !settings.debug;
updateSettings();
}
},
'Reset': showResetMenu,
'Turn Off': Bangle.off,
'< Back': load
};
return Bangle.menu(mainmenu);
}
function showResetMenu() {
const resetmenu = {
'': { 'title': 'Reset' },
'< Back': showMainMenu,
'Reset Settings': () => {
E.showPrompt('Reset Settings?').then((v) => {
if (v) {
E.showMessage('Resetting');
resetSettings();
}
setTimeout(showMainMenu, 50);
});
},
// this is include for debugging. remove for production
/*'Erase': () => {
storage.erase('=settings');
storage.erase('-settings');
storage.erase('@settings');
storage.erase('*settings');
storage.erase('+settings');
E.reboot();
}*/
};
return Bangle.menu(resetmenu);
}
showMainMenu();

5
apps/settings.json Normal file
View File

@ -0,0 +1,5 @@
{
name: 'Settings',
icon: '*settings',
src: '-settings'
}

BIN
apps/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,6 +1,13 @@
g.clear();
var old = {x:0,y:0};
Bangle.on('accel',function(v) {
var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z));
if (Math.abs(v.y)==max) {
v = {x:v.x,y:v.z,z:v.y};
} else if (Math.abs(v.x)==max) {
v = {x:v.z,y:v.y,z:v.x};
}
var d = Math.sqrt(v.x*v.x+v.y*v.y);
var ang = Math.atan2(d,Math.abs(v.z))*180/Math.PI;

1
apps/stopwatch-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwghC/AFECkQACkAX/C/4AKgMRiMQVCYXFGYIWOxAAEgJLPCwuIwRkCCyQABC5sICIUzn/zmYYEFxs//4AC+ZJCL5QuCCwXzDAuAFxeDCYYyDnALBC5YSD+UvGApGKFwYXFGARIIC4OPCIfxj4FD/AXJRgwXFJBQJBCAYXBiYGEC5ReE/8xC4pgBC50hiQXPOwn/iMRMwgXP+QXBVAiQBC8pHCO6rvFC6IAGC5TXFAAzvLUAgAF+YXJhB4GAAiOBwAXJMBReBC5BILIxQXDGBAuBC5RIBGA4uCIxIwDDAoWCFxQwExEzn/zmYGCFxYwEAAwWMDBIWODA4WQAH4AXA=="))

84
apps/stopwatch.js Normal file
View File

@ -0,0 +1,84 @@
var tStart = Date.now();
var tCurrent = Date.now();
var started = false;
var timeY = 60;
var hsXPos = 0;
var lapTimes = [];
var displayInterval;
function timeToText(t) {
var secs = Math.floor(t/1000)%60;
var mins = Math.floor(t/60000);
var hs = Math.floor(t/10)%100;
return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2);
}
function updateLabels() {
g.clear();
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
g.drawString(started?"STOP":"GO",230,120);
if (!started) g.drawString("RESET",230,50);
g.drawString("LAP",230,190);
g.setFont("6x8",1);
g.setFontAlign(-1,-1);
for (var i in lapTimes) {
g.drawString(i+": "+timeToText(lapTimes[i]),10,timeY + 30 + i*8);
}
drawsecs();
}
function drawsecs() {
var t = tCurrent-tStart;
g.setFont("Vector",48);
g.setFontAlign(0,0);
var secs = Math.floor(t/1000)%60;
var mins = Math.floor(t/60000);
var txt = mins+":"+("0"+secs).substr(-2);
var x = 100;
g.clearRect(0,timeY-26,200,timeY+26);
g.drawString(txt,x,timeY);
hsXPos = 5+x+g.stringWidth(txt)/2;
drawms();
}
function drawms() {
var t = tCurrent-tStart;
var hs = Math.floor(t/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);
}
setWatch(function() { // Start/stop
started = !started;
if (started)
tStart = Date.now()+tStart-tCurrent;
tCurrent = Date.now();
if (displayInterval) {
clearInterval(displayInterval);
displayInterval = undefined;
}
updateLabels();
if (started)
displayInterval = setInterval(function() {
var last = tCurrent;
if (started) tCurrent = Date.now();
if (Math.floor(last/1000)!=Math.floor(tCurrent/1000))
drawsecs();
else
drawms();
}, 20);
}, BTN2, {repeat:true});
setWatch(function() { // Reset
if (!started) {
tStart = tCurrent = Date.now();
}
updateLabels();
}, BTN1, {repeat:true});
setWatch(function() { // Lap
if (started) tCurrent = Date.now();
lapTimes.unshift(tCurrent-tStart);
tStart = tCurrent;
updateLabels();
}, BTN3, {repeat:true});
updateLabels();

5
apps/stopwatch.json Normal file
View File

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

BIN
apps/stopwatch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB