Merge branch 'master' of github.com:ff2005/BangleApps into feature/nifty-clock-b
|
|
@ -217,8 +217,9 @@ and which gives information about the app for the Launcher.
|
|||
{ "id": "appid", // 7 character app id
|
||||
"name": "Readable name", // readable name
|
||||
"shortName": "Short name", // short name for launcher
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"version": "0v01", // the version of this app
|
||||
"description": "...", // long description (can contain markdown)
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"type":"...", // optional(if app) -
|
||||
// 'app' - an application
|
||||
// 'widget' - a widget
|
||||
|
|
@ -226,6 +227,7 @@ and which gives information about the app for the Launcher.
|
|||
// 'bootloader' - code that runs at startup only
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
"tags": "", // comma separated tag list for searching
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great app",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My widget's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great widget",
|
||||
"tags": "widget",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.wid.js","url":"widget.js"}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
0.01: First version
|
||||
0.02: Moved arrow image load to global scope
|
||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||
0.04: removed LCD1.write() as it was keeping LCD on
|
||||
0.04: removed LED1.write() as it was keeping LCD on
|
||||
0.05: Turn compass off when screen off
|
||||
Calibrate at start if no info
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
|
||||
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
|
||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
var intervalRef;
|
||||
|
|
@ -7,6 +7,7 @@ var bearing=0; // always point north
|
|||
var heading = 0;
|
||||
var oldHeading = 0;
|
||||
var candraw = false;
|
||||
var isCalibrating = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function flip1(x,y) {
|
||||
|
|
@ -76,7 +77,7 @@ function tiltfixread(O,S){
|
|||
return psi;
|
||||
}
|
||||
|
||||
function reading() {
|
||||
function reading(m) {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
var dir = bearing - heading;
|
||||
|
|
@ -97,18 +98,19 @@ function reading() {
|
|||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
function onMag(m) {
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
}
|
||||
Bangle.on('mag', onMag);
|
||||
Bangle.setCompassPower(1, "app");
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
Bangle.removeListener('mag', onMag);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
|
|
@ -132,6 +134,7 @@ function docalibrate(e,first){
|
|||
flip1(56,56);
|
||||
|
||||
calibrate().then((r)=>{
|
||||
isCalibrating = false;
|
||||
require("Storage").write("magnav.json",r);
|
||||
Bangle.buzz();
|
||||
CALIBDATA = r;
|
||||
|
|
@ -143,9 +146,13 @@ function docalibrate(e,first){
|
|||
setTimeout(setButtons,1000);
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
|
||||
if (first === undefined) first = false;
|
||||
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
isCalibrating = true;
|
||||
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
|
|
@ -153,16 +160,24 @@ function docalibrate(e,first){
|
|||
}
|
||||
|
||||
function startdraw(){
|
||||
Bangle.setCompassPower(1, "app");
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
intervalRef = setInterval(reading,500);
|
||||
if (intervalRef) clearInterval(intervalRef);
|
||||
intervalRef = setInterval(reading,200);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
|
||||
Bangle.setCompassPower(0, "app");
|
||||
if (intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
|
|
@ -172,6 +187,7 @@ function setButtons(){
|
|||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (isCalibrating) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
|
|
@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) {
|
|||
}
|
||||
});
|
||||
|
||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
startdraw();
|
||||
setButtons();
|
||||
|
||||
Bangle.setLCDPower(1);
|
||||
if (CALIBDATA) startdraw(); else docalibrate({},true);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
||||
0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal.
|
||||
0.04: Update to use Bangle.setUI instead of setWatch
|
||||
0.05: Update *on* the minute rather than every 15 secs
|
||||
Now show widgets
|
||||
Make compatible with themes, and Bangle.js 2
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
||||
// https://github.com/eska-muc/BangleApps
|
||||
const fields = [4, 4, 11, 4];
|
||||
const offset = 20;
|
||||
const offset = 24;
|
||||
const width = g.getWidth() - 2 * offset;
|
||||
const height = g.getHeight() - 2 * offset;
|
||||
const rowHeight = height / 4;
|
||||
|
|
@ -10,11 +10,23 @@ var show_date = false;
|
|||
var show_time = false;
|
||||
var yy = 0;
|
||||
|
||||
rowlights = [];
|
||||
time_digit = [];
|
||||
var rowlights = [];
|
||||
var time_digit = [];
|
||||
|
||||
function drawBerlinClock() {
|
||||
g.clear();
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
var now = new Date();
|
||||
|
||||
// show date below the clock
|
||||
|
|
@ -24,8 +36,7 @@ function drawBerlinClock() {
|
|||
var day = now.getDate();
|
||||
var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
|
||||
var strWidth = g.stringWidth(dateString);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1,-1);
|
||||
g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4);
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +61,7 @@ function drawBerlinClock() {
|
|||
x2 = (col + 1) * boxWidth + offset;
|
||||
y2 = (row + 1) * rowHeight + offset;
|
||||
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawRect(x1, y1, x2, y2);
|
||||
g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2);
|
||||
if (col < rowlights[row]) {
|
||||
if (row === 2) {
|
||||
if (((col + 1) % 3) === 0) {
|
||||
|
|
@ -65,46 +75,42 @@ function drawBerlinClock() {
|
|||
g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
|
||||
}
|
||||
if (row == 3 && show_time) {
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg).setFontAlign(0,0);
|
||||
g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function toggleDate() {
|
||||
show_date = ! show_date;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
function toggleTime() {
|
||||
show_time = ! show_time;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
g.clear();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
Bangle.drawWidgets();
|
||||
// call your app function here
|
||||
drawBerlinClock();
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawBerlinClock, 15E3);
|
||||
// Show launcher when button pressed, handle up/down
|
||||
Bangle.setUI("clockupdown", dir=> {
|
||||
if (dir<0) toggleTime();
|
||||
if (dir>0) toggleDate();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawBerlinClock();
|
||||
if (BTN3) {
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"});
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"});
|
||||
}
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -31,3 +31,6 @@
|
|||
Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js
|
||||
0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now
|
||||
0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics
|
||||
0.32: Fix single quote error in g.wrapString polyfill
|
||||
improve g.stringMetrics polyfill
|
||||
Fix issue where re-running bootupdate could disable existing polyfills
|
||||
|
|
|
|||
|
|
@ -81,9 +81,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
|||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.theme) {
|
||||
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
|
||||
}
|
||||
delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it
|
||||
boot += `Bangle.setUI=function(mode, cb) {
|
||||
if (Bangle.btnWatches) {
|
||||
|
|
@ -131,6 +133,7 @@ else if (mode=="updown") {
|
|||
throw new Error("Unknown UI mode");
|
||||
};\n`;
|
||||
}
|
||||
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.imageMetrics=function(src) {
|
||||
if (src[0]) return {width:src[0],height:src[1]};
|
||||
|
|
@ -141,15 +144,18 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi
|
|||
return {width:im.charCodeAt(0), height:im.charCodeAt(1)};
|
||||
};\n`;
|
||||
}
|
||||
delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.stringMetrics=function(txt) {
|
||||
return {width:this.stringWidth(txt), height:this.getFontHeight()};
|
||||
txt = txt.toString().split("\\n");
|
||||
return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length};
|
||||
};\n`;
|
||||
}
|
||||
delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.wrapString=function(str, maxWidth) {
|
||||
var lines = [];
|
||||
for (var unwrappedLine of str.split("\n")) {
|
||||
for (var unwrappedLine of str.split("\\n")) {
|
||||
var words = unwrappedLine.split(" ");
|
||||
var line = words.shift();
|
||||
for (var word of words) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Bluetooth Heart Rate Monitor
|
||||
|
||||
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
|
||||
|
||||
This means it's compatible with many Bangle.js apps including:
|
||||
|
||||
* [Heart Rate Widget](https://banglejs.com/apps/#widhrt)
|
||||
* [Heart Rate Recorder](https://banglejs.com/apps/#heart)
|
||||
|
||||
It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm)
|
||||
as that requires live sensor data (rather than just BPM readings).
|
||||
|
||||
## Usage
|
||||
|
||||
Just install the app, then install an app that uses the heart rate monitor.
|
||||
|
||||
Once installed it'll automatically try and connect to the first bluetooth
|
||||
heart rate monitor it finds.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
This works with any heart rate monitor providing the standard Bluetooth
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`).
|
||||
|
||||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
|
||||
## Internals
|
||||
|
||||
This replaces `Bangle.setHRMPower` with its own implementation.
|
||||
|
||||
## TODO
|
||||
|
||||
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
|
||||
* A widget to show connection state?
|
||||
* Specify a specific device by address?
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA=="))
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -0,0 +1,79 @@
|
|||
(function() {
|
||||
var log = function() {};//print
|
||||
var gatt;
|
||||
var status;
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||
}
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.HRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setHRMPower on", app);
|
||||
if (!Bangle.isHRMOn()) {
|
||||
log("HRM not already on");
|
||||
status = "searching";
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
status = "connecting";
|
||||
device.on('gattserverdisconnected', function(reason) {
|
||||
gatt = undefined;
|
||||
});
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
log("Connected");
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0x180D);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0x2A37);
|
||||
}).then(function(characteristic) {
|
||||
log("Got characteristic");
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
Bangle.emit('HRM',{
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
log("Ready");
|
||||
status = "ok";
|
||||
}).catch(function(err) {
|
||||
log("Error",err);
|
||||
gatt = undefined;
|
||||
status = "error";
|
||||
});
|
||||
}
|
||||
} else { // not on
|
||||
log("setHRMPower off", app);
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
status = undefined;
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
@ -5,3 +5,5 @@
|
|||
0.11: added Heart Rate Monitor status and ability to turn on/off
|
||||
0.12: added support for different locales
|
||||
0.13: Use setUI, work with smaller screens and themes
|
||||
0.14: Fix BTN1 (fix #853)
|
||||
Add light/dark theme support
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ const HRT_FN_MODE = "fn_hrt";
|
|||
let infoMode = NONE_MODE;
|
||||
let functionMode = NONE_FN_MODE;
|
||||
|
||||
let textCol = g.theme.dark ? "#0f0" : "#080";
|
||||
|
||||
function drawAll(){
|
||||
updateTime();
|
||||
updateRest(new Date());
|
||||
|
|
@ -45,9 +47,7 @@ function writeLineStart(line){
|
|||
function writeLine(str,line){
|
||||
var y = marginTop+line*fontheight;
|
||||
g.setFont("6x8",fontsize);
|
||||
//g.setColor(0,1,0);
|
||||
g.setColor("#0f0");
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setColor(textCol).setFontAlign(-1,-1);
|
||||
g.clearRect(0,y,((str.length+1)*20),y+fontheight-1);
|
||||
writeLineStart(line);
|
||||
g.drawString(str,25,y);
|
||||
|
|
@ -56,7 +56,7 @@ function writeLine(str,line){
|
|||
function drawInfo(line) {
|
||||
let val;
|
||||
let str = "";
|
||||
let col = "#0f0"; // green
|
||||
let col = textCol; // green
|
||||
|
||||
//console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode);
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ function drawInfo(line) {
|
|||
case NONE_FN_MODE:
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
col = "#0ff"; // cyan
|
||||
col = g.theme.dark ? "#0ff": "#088"; // cyan
|
||||
str = "HRM: " + (hrtOn ? "ON" : "OFF");
|
||||
drawModeLine(line,str,col);
|
||||
return;
|
||||
|
|
@ -72,7 +72,7 @@ function drawInfo(line) {
|
|||
|
||||
switch(infoMode) {
|
||||
case NONE_MODE:
|
||||
col = "#fff";
|
||||
col = g.theme.bg;
|
||||
str = "";
|
||||
break;
|
||||
case HRT_MODE:
|
||||
|
|
@ -104,9 +104,8 @@ function drawModeLine(line, str, col) {
|
|||
g.setColor(col);
|
||||
var y = marginTop+line*fontheight;
|
||||
g.fillRect(0, y, 239, y+fontheight-1);
|
||||
g.setColor(0);
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(str, g.getWidth()/2, y);
|
||||
g.setColor(g.theme.bg).setFontAlign(0, 0);
|
||||
g.drawString(str, g.getWidth()/2, y+fontheight/2);
|
||||
}
|
||||
|
||||
function changeInfoMode() {
|
||||
|
|
@ -193,7 +192,7 @@ Bangle.on('lcdPower',function(on) {
|
|||
var click = setInterval(updateTime, 1000);
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clockupdown", btn=>{
|
||||
if (btn==0) changeInfoMode();
|
||||
if (btn==1) changeFunctionMode();
|
||||
if (btn<0) changeInfoMode();
|
||||
if (btn>0) changeFunctionMode();
|
||||
drawAll();
|
||||
});
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Show text if uncalibrated
|
||||
0.03: Eliminate flickering
|
||||
0.04: Fix for Bangle.js 2 and themes
|
||||
|
|
|
|||
|
|
@ -1,60 +1,72 @@
|
|||
var tg = Graphics.createArrayBuffer(120,20,1,{msb:true});
|
||||
var timg = {
|
||||
width:tg.getWidth(),
|
||||
height:tg.getHeight(),
|
||||
bpp:1,
|
||||
buffer:tg.buffer
|
||||
};
|
||||
|
||||
var ag = Graphics.createArrayBuffer(160,160,2,{msb:true});
|
||||
var W = g.getWidth();
|
||||
var M = W/2; // middle of screen
|
||||
// Angle buffer
|
||||
var AGS = W > 200 ? 160 : 120; // buffer size
|
||||
var AGM = AGS/2; // midpoint/radius
|
||||
var AGH = AGM-10; // hand size
|
||||
var ag = Graphics.createArrayBuffer(AGS,AGS,2,{msb:true});
|
||||
var aimg = {
|
||||
width:ag.getWidth(),
|
||||
height:ag.getHeight(),
|
||||
bpp:2,
|
||||
buffer:ag.buffer,
|
||||
palette:new Uint16Array([0,0x03FF,0xF800,0x001F])
|
||||
palette:new Uint16Array([
|
||||
g.theme.bg,
|
||||
g.toColor("#07f"),
|
||||
g.toColor("#f00"),
|
||||
g.toColor("#00f")])
|
||||
};
|
||||
ag.setColor(1);
|
||||
ag.fillCircle(80,80,79,79);
|
||||
ag.setColor(0);
|
||||
ag.fillCircle(80,80,69,69);
|
||||
ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1);
|
||||
ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11);
|
||||
|
||||
function arrow(r,c) {
|
||||
r=r*Math.PI/180;
|
||||
var p = Math.PI/2;
|
||||
ag.setColor(c);
|
||||
ag.fillPoly([
|
||||
80+60*Math.sin(r), 80-60*Math.cos(r),
|
||||
80+10*Math.sin(r+p), 80-10*Math.cos(r+p),
|
||||
80+10*Math.sin(r-p), 80-10*Math.cos(r-p),
|
||||
ag.setColor(c).fillPoly([
|
||||
AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r),
|
||||
AGM+10*Math.sin(r+p), AGM-10*Math.cos(r+p),
|
||||
AGM+10*Math.sin(r-p), AGM-10*Math.cos(r-p),
|
||||
]);
|
||||
}
|
||||
|
||||
var wasUncalibrated = false;
|
||||
var oldHeading = 0;
|
||||
Bangle.on('mag', function(m) {
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
tg.clear();
|
||||
tg.setFont("6x8",1);
|
||||
tg.setColor(1);
|
||||
g.reset();
|
||||
if (isNaN(m.heading)) {
|
||||
tg.setFontAlign(0,-1);
|
||||
tg.setFont("6x8",1);
|
||||
tg.drawString("Uncalibrated",60,4);
|
||||
tg.drawString("turn 360° around",60,12);
|
||||
if (!wasUncalibrated) {
|
||||
g.clearRect(0,24,W,48);
|
||||
g.setFontAlign(0,-1).setFont("6x8");
|
||||
g.drawString("Uncalibrated\nturn 360° around",M,24+4);
|
||||
wasUncalibrated = true;
|
||||
}
|
||||
else {
|
||||
tg.setFontAlign(0,0);
|
||||
tg.setFont("6x8",2);
|
||||
tg.drawString(Math.round(m.heading),60,12);
|
||||
} else {
|
||||
if (wasUncalibrated) {
|
||||
g.clearRect(0,24,W,48);
|
||||
wasUncalibrated = false;
|
||||
}
|
||||
g.drawImage(timg,0,0,{scale:2});
|
||||
g.setFontAlign(0,0).setFont("6x8",3);
|
||||
var y = 36;
|
||||
g.clearRect(M-40,y,M+40,y+24);
|
||||
g.drawString(Math.round(m.heading),M,y,true);
|
||||
}
|
||||
|
||||
|
||||
ag.setColor(0);
|
||||
arrow(oldHeading,0);
|
||||
arrow(oldHeading+180,0);
|
||||
arrow(m.heading,2);
|
||||
arrow(m.heading+180,3);
|
||||
g.drawImage(aimg,40,50);
|
||||
g.drawImage(aimg,
|
||||
(W-ag.getWidth())/2,
|
||||
g.getHeight()-(ag.getHeight()+4));
|
||||
oldHeading = m.heading;
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1,284 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="fw-unknown">
|
||||
<p>Firmware updates using the App Loader are only possible on
|
||||
Bangle.js 2. For firmware updates on Bangle.js 1 please
|
||||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></p>
|
||||
</div>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p>Please upload a hex file here. This file should be the <code>.app_hex</code>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
|
||||
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex"/><br>
|
||||
<p><button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
var hex;
|
||||
var hexJS; // JS to upload hex
|
||||
var HEADER_LEN = 16; // size of app flash header
|
||||
var MAX_ADDRESS = 0x1000000; // discount anything in hex file above this
|
||||
var VERSION = 0x12345678; // VERSION! Use this to test firmware in JS land
|
||||
var DEBUG = false;
|
||||
|
||||
function log(t) {
|
||||
document.getElementById('log').innerText += t+"\n";
|
||||
console.log(t);
|
||||
}
|
||||
|
||||
function onInit(device) {
|
||||
console.log(device);
|
||||
if (device && device.id=="BANGLEJS2") {
|
||||
document.getElementById("fw-unknown").style = "display:none";
|
||||
document.getElementById("fw-ok").style = "";
|
||||
}
|
||||
}
|
||||
|
||||
function checkForFileOnServer() {
|
||||
/*function getURL(url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = callback;
|
||||
baseURL = url;
|
||||
xhr.open("GET", baseURL);
|
||||
xhr.responseType = "document";
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function getFilesFromURL(url, regex, callback) {
|
||||
getURL(url, function() {
|
||||
var files = [];
|
||||
var elements = this.responseXML.getElementsByTagName("a");
|
||||
for (var i=0;i<elements.length;i++) {
|
||||
var href = elements[i].href;
|
||||
if (regex.exec(href)) {
|
||||
files.push(href);
|
||||
}
|
||||
}
|
||||
callback(files);
|
||||
});
|
||||
}
|
||||
|
||||
var regex = new RegExp("_bangle2");
|
||||
|
||||
var domFirmware = document.getElementById("latest-firmware");
|
||||
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
|
||||
releaseFiles.sort().reverse().forEach(function(f) {
|
||||
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
|
||||
domFirmware.innerHTML += 'Release: <a href="'+f+'">'+name+'</a><br/>';
|
||||
});
|
||||
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
|
||||
travisFiles.forEach(function(f) {
|
||||
var name = f.substr(f.lastIndexOf('/')+1);
|
||||
domFirmware.innerHTML += 'Cutting Edge build: <a href="'+f+'">'+name+'</a><br/>';
|
||||
});
|
||||
document.getElementById("checking-server").style = "display:none";
|
||||
document.getElementById("main-ui").style = "";
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
function downloadFile() {
|
||||
/*response = await fetch(APP_HEX_PATH+"readlink.php?link="+APP_HEX_FILE, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (response.ok) {
|
||||
blob = await response.blob();
|
||||
data = await blob.text();
|
||||
document.getElementById("latest-firmware").innerHTML="(<b>"+data.toString()+"</b>)";
|
||||
}*/
|
||||
}
|
||||
|
||||
function handleFileSelect(event) {
|
||||
if (event.target.files.length!=1) {
|
||||
log("More than one file selected!");
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
hex = event.target.result.split("\n");
|
||||
document.getElementById("upload").style = ""; // show upload
|
||||
fileLoaded();
|
||||
};
|
||||
reader.readAsText(event.target.files[0]);
|
||||
};
|
||||
|
||||
|
||||
function parseLines(dataCallback) {
|
||||
var addrHi = 0;
|
||||
hex.forEach(function(hexline) {
|
||||
if (DEBUG) console.log(hexline);
|
||||
var bytes = hexline.substr(1,2);
|
||||
var addrLo = parseInt(hexline.substr(3,4),16);
|
||||
var cmd = hexline.substr(7,2);
|
||||
if (cmd=="02") addrHi = parseInt(hexline.substr(9,4),16) << 4; // Extended Segment Address
|
||||
else if (cmd=="04") addrHi = parseInt(hexline.substr(9,4),16) << 16; // Extended Linear Address
|
||||
else if (cmd=="00") {
|
||||
var addr = addrHi + addrLo;
|
||||
var data = [];
|
||||
for (var i=0;i<16;i++) data.push(parseInt(hexline.substr(9+(i*2),2),16));
|
||||
dataCallback(addr,data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function CRC32(data) {
|
||||
var crc = 0xFFFFFFFF;
|
||||
data.forEach(function(d) {
|
||||
crc^=d;
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
|
||||
});
|
||||
return (~crc)>>>0; // >>>0 converts to unsigned 32-bit integer
|
||||
}
|
||||
|
||||
function btoa(input) {
|
||||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
var out = "";
|
||||
var i=0;
|
||||
while (i<input.length) {
|
||||
var octet_a = 0|input[i++];
|
||||
var octet_b = 0;
|
||||
var octet_c = 0;
|
||||
var padding = 0;
|
||||
if (i<input.length) {
|
||||
octet_b = 0|input[i++];
|
||||
if (i<input.length) {
|
||||
octet_c = 0|input[i++];
|
||||
padding = 0;
|
||||
} else
|
||||
padding = 1;
|
||||
} else
|
||||
padding = 2;
|
||||
var triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
out += b64[(triple >> 18) & 63] +
|
||||
b64[(triple >> 12) & 63] +
|
||||
((padding>1)?'=':b64[(triple >> 6) & 63]) +
|
||||
((padding>0)?'=':b64[triple & 63]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// To upload the app, we write to external flash
|
||||
function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
|
||||
/* typedef struct {
|
||||
uint32_t address;
|
||||
uint32_t size;
|
||||
uint32_t CRC;
|
||||
uint32_t version;
|
||||
} FlashHeader; */
|
||||
bin32[0] = startAddress;
|
||||
bin32[1] = endAddress - startAddress;
|
||||
bin32[2] = CRC32(new Uint8Array(binary.buffer, HEADER_LEN));
|
||||
bin32[3] = VERSION; // VERSION! Use this to test ourselves
|
||||
console.log("CRC 0x"+bin32[2].toString(16));
|
||||
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
||||
hexJS += '\x10var s = require("Storage");\n';
|
||||
var CHUNKSIZE = 1024;
|
||||
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
|
||||
var l = binary.length-i;
|
||||
if (l>CHUNKSIZE) l=CHUNKSIZE;
|
||||
var chunk = btoa(new Uint8Array(binary.buffer, i, l));
|
||||
hexJS += `\x10s.write('.firmware', atob("${chunk}"), 0x${i.toString(16)}, ${binary.length});\n`;
|
||||
}
|
||||
hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
|
||||
hexJS += '\x10setTimeout(()=>E.reboot(), 1000);\n';
|
||||
}
|
||||
|
||||
|
||||
// To upload the bootloader, we write to internal flash, right over bootloader
|
||||
function createJS_bootloader(binary, startAddress, endAddress) {
|
||||
var crc = CRC32(binary);
|
||||
console.log("CRC 0x"+crc.toString(16));
|
||||
hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("BOOTLOADER UP TO DATE!"); load();}\n`;
|
||||
hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`;
|
||||
var CHUNKSIZE = 1024;
|
||||
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
|
||||
var l = binary.length-i;
|
||||
if (l>CHUNKSIZE) l=CHUNKSIZE;
|
||||
var chunk = btoa(new Uint8Array(binary.buffer, binary.byteOffset+i, l));
|
||||
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
|
||||
}
|
||||
// hexJS += `\x10(function() {
|
||||
// if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC!";
|
||||
// var f = require("Flash");
|
||||
// for (var i=${startAddress};i<${endAddress};i+=4096) f.erasePage(i);
|
||||
// f.write(_fw,${startAddress});
|
||||
// E.reboot();
|
||||
// })();\n`;
|
||||
hexJS += `\x10if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
||||
hexJS += '\x10var f = require("Flash");\n';
|
||||
for (var i=startAddress;i<endAddress;i+=4096)
|
||||
hexJS += '\x10f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `\x10f.write(_fw,${startAddress});\n`;
|
||||
// hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
|
||||
// hexJS += '\x10setTimeout(()=>E.reboot(), 2000);\n';
|
||||
}
|
||||
|
||||
function fileLoaded() {
|
||||
// Work out addresses
|
||||
var startAddress, endAddress = 0;
|
||||
parseLines(function(addr, data) {
|
||||
if (addr>MAX_ADDRESS) return; // ignore data out of range
|
||||
if (startAddress === undefined || addr<startAddress)
|
||||
startAddress = addr;
|
||||
var end = addr + data.length;
|
||||
if (end > endAddress)
|
||||
endAddress = end;
|
||||
});
|
||||
console.log(`// Data from 0x${startAddress.toString(16)} to 0x${endAddress.toString(16)} (${endAddress-startAddress} bytes)`);
|
||||
// Work out data
|
||||
var HEADER_LEN = 16;
|
||||
var binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
|
||||
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
|
||||
var bin32 = new Uint32Array(binary.buffer);
|
||||
parseLines(function(addr, data) {
|
||||
if (addr>MAX_ADDRESS) return; // ignore data out of range
|
||||
var binAddr = HEADER_LEN + addr - startAddress;
|
||||
binary.set(data, binAddr);
|
||||
if (DEBUG) console.log("i",addr.toString(16).padStart(8,0), data.map(x=>x.toString(16).padStart(2,0)).join(" "));
|
||||
//console.log("o",new Uint8Array(binary.buffer, binAddr, data.length));
|
||||
});
|
||||
|
||||
if (startAddress == 0xf7000) {
|
||||
console.log("Bootloader - Writing to internal flash");
|
||||
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
|
||||
} else {
|
||||
console.log("App - Writing to external flash");
|
||||
createJS_app(binary, bin32, startAddress, endAddress);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function handleUpload() {
|
||||
if (!hexJS) {
|
||||
log("Hex file not loaded!");
|
||||
return;
|
||||
}
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"RAM", content:hexJS},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
document.getElementById("upload").addEventListener("click", handleUpload);
|
||||
checkForFileOnServer();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
0.03: Fix time output on new firmwares when no GPS time set (fix #104)
|
||||
0.04: Fix shown UTC time zone sign
|
||||
0.05: Use new 'layout library for Bangle2, fix #764 by adding a back button
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="))
|
||||
require("heatshrink").decompress(atob("mEw4UA////G161hyd8Jf4ALlQLK1WABREC1WgBZEK32oFxPW1QuJ7QwIFwOqvQLHhW31NaBY8qy2rtUFoAuG3W61EVqALF1+qr2gqtUHQu11dawNVqo6F22q9XFBYIwEhWqz2r6oLBGAheBqwuBBYx2CFwQLGlWqgoLCMAsKLoILChR6EgQuDqkqYYsBFweqYYoLDoWnYYoLD/WVYYv8FwXqPoIwEn52BqGrPoILEh/1FwOl9SsBBYcD/pdB2uq/QvEh/8LoOu1xHFh8/gGp9WWL4oMBgWltXeO4owBgWt1ReFYYh2GYYmXEQzDD3wiHegYKIGAJRGAAguJAH4AC"))
|
||||
|
|
|
|||
|
|
@ -1,68 +1,75 @@
|
|||
var img = require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="));
|
||||
function satelliteImage() {
|
||||
return require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AGnE4F1wvsF34wgFldcLdyMYsoACF1WJF4YxPFzOtF4wxNFzAvKSiIvU1ovIGAkJAAQucF5QxCFwYwbF4QwLrwvjYIVfrwABrtdq9Wqwvkq4oCAAtXmYvi1teE4NXrphCrxoCGAbvdSIoAHNQNeFzQvGeRQvCsowrYYNfF8YwHZQQFCF8QwGF4owjeYovBroHEMERhEF8IwNrtWryYFF8YwCq4vhGBeJF5AwaxIwKwVXFwwvandfMJeJF8M6nZiLGQIvdstfGAVlGBZkCxJeZJQIwCGIRjMFzYACGIc6r/+FsIvGGIYABEzYvPGQYvusovkAH4A/AH4A/ACo="));
|
||||
}
|
||||
|
||||
var fix;
|
||||
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
var Layout = require("Layout");
|
||||
Bangle.setGPSPower(1, "app");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showMessage("Loading..."); // avoid showing rubbish on screen
|
||||
|
||||
g.clear();
|
||||
|
||||
var fix;
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.on('GPS',function(f) {
|
||||
fix = f;
|
||||
g.reset(1);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(90,30,239,90);
|
||||
if (fix.fix) {
|
||||
g.drawString("GPS",170,40);
|
||||
g.drawString("Acquired",170,60);
|
||||
function setGPSTime() {
|
||||
if (fix.time!==undefined) {
|
||||
setTime(fix.time.getTime()/1000);
|
||||
E.showMessage("System time set", {img:require("heatshrink").decompress(atob("lEo4UBvvv///vEFBYNVAAWq1QFDBAgKGrQJD0oJDtQJD1IICqwGBFoIDByocDwAJBgQeDtWoJwcqDwWq0EAgfAgEKHoQcCBIQeBGAQaBBIQzBytaEwQJDlWlrQmBBIkK0tqBI+ptRNCBIcCBKhECBIh6CAgUL8AJHl/4BI8+3gJRl/8GJH/BI8Ah6MDLIZQB+BjGAAIoBBI84BIaVCAAaVBVIYJEWYLkEXobRDAAbRBcoYACcoT5DEwYJCtQoElWpBINaDwYcB0oJBGQIzCAYIwBDwQGBAAIcCDwYACDgQACBIYIEBQYFDA="))});
|
||||
} else {
|
||||
g.drawString("Waiting for",170,40);
|
||||
g.drawString("GPS Fix",170,60);
|
||||
E.showMessage("No GPS time to set");
|
||||
}
|
||||
g.setFont("6x8");
|
||||
g.drawString(fix.satellites+" satellites",170,80);
|
||||
|
||||
g.clearRect(0,100,239,239);
|
||||
var t = ["","","","---",""];
|
||||
if (fix.time!==undefined)
|
||||
Bangle.removeListener('GPS',onGPS);
|
||||
setTimeout(function() {
|
||||
fix = undefined;
|
||||
layout.forgetLazyState(); // redraw all next time
|
||||
Bangle.on('GPS',onGPS);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
var layout = new Layout( {
|
||||
type:"v", c: [
|
||||
{type:"h", c:[
|
||||
{type:"img", src:satelliteImage },
|
||||
{ type:"v", fillx:1, c: [
|
||||
{type:"txt", font:"6x8:2", label:"Waiting\nfor GPS", id:"status" },
|
||||
{type:"txt", font:"6x8", label:"---", id:"sat" },
|
||||
]},
|
||||
]},
|
||||
{type:"txt", fillx:1, filly:1, font:"6x8:2", label:"---", id:"gpstime" }
|
||||
]},{lazy:true, btns: [
|
||||
{ label : "Set", cb : setGPSTime},
|
||||
{ label : "Back", cb : ()=>load() }
|
||||
]});
|
||||
|
||||
|
||||
function onGPS(f) {
|
||||
if (fix===undefined) {
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
fix = f;
|
||||
if (fix.fix) {
|
||||
layout.status.label = "GPS\nAcquired";
|
||||
} else {
|
||||
layout.status.label = "Waiting\nfor GPS";
|
||||
}
|
||||
layout.sat.label = fix.satellites+" satellites";
|
||||
|
||||
var t = ["","---",""];
|
||||
if (fix.time!==undefined) {
|
||||
t = fix.time.toString().split(" ");
|
||||
/*
|
||||
[
|
||||
"Sun",
|
||||
"Nov",
|
||||
"10",
|
||||
"2019",
|
||||
"15:55:35",
|
||||
"GMT+0100"
|
||||
]
|
||||
*/
|
||||
//g.setFont("6x8",2);
|
||||
//g.drawString(t[0],120,110); // day
|
||||
g.setFont("6x8",3);
|
||||
g.drawString(t[1]+" "+t[2],120,135); // date
|
||||
g.setFont("6x8",2);
|
||||
g.drawString(t[3],120,160); // year
|
||||
g.setFont("6x8",3);
|
||||
g.drawString(t[4],120,185); // time
|
||||
if (fix.time) {
|
||||
// timezone
|
||||
var tz = (new Date()).getTimezoneOffset()/-60;
|
||||
if (tz==0) tz="UTC";
|
||||
else if (tz>0) tz="UTC+"+tz;
|
||||
else tz="UTC"+tz;
|
||||
g.setFont("6x8",2);
|
||||
g.drawString(tz,120,210); // gmt
|
||||
g.setFontAlign(0,0,3);
|
||||
g.drawString("Set",230,120);
|
||||
g.setFontAlign(0,0);
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(function() {
|
||||
g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});
|
||||
},100);
|
||||
setWatch(function() {
|
||||
if (fix.time!==undefined)
|
||||
setTime(fix.time.getTime()/1000);
|
||||
}, BTN2, {repeat:true});
|
||||
t = [t[1]+" "+t[2],t[3],t[4],t[5],tz];
|
||||
}
|
||||
|
||||
layout.gpstime.label = t.join("\n");
|
||||
layout.render();
|
||||
}
|
||||
|
||||
Bangle.on('GPS',onGPS);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
0.01: base code
|
||||
|
||||
0.02: saved settings when switching color scheme
|
||||
|
|
@ -180,13 +180,46 @@ function fmtDate(day,month,year,hour)
|
|||
return months[month] + ". " + day + " " + year;
|
||||
}
|
||||
|
||||
// Handles Flipping colors, then refreshes the UI
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// HANDLE COLORS + SETTINGS
|
||||
//
|
||||
|
||||
function getColorScheme()
|
||||
{
|
||||
let settings = require('Storage').readJSON("hcclock.json", true) || {};
|
||||
if (!("scheme" in settings)) {
|
||||
settings.scheme = 0;
|
||||
}
|
||||
return settings.scheme;
|
||||
}
|
||||
|
||||
function setColorScheme(value)
|
||||
{
|
||||
let settings = require('Storage').readJSON("hcclock.json", true) || {};
|
||||
settings.scheme = value;
|
||||
require('Storage').writeJSON('hcclock.json', settings);
|
||||
|
||||
if(value == 0) // White
|
||||
{
|
||||
bg = 255;
|
||||
fg = 0;
|
||||
}
|
||||
else // Black
|
||||
{
|
||||
bg = 0;
|
||||
fg = 255;
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
function flipColors()
|
||||
{
|
||||
let t = bg;
|
||||
bg = fg;
|
||||
fg = t;
|
||||
redraw();
|
||||
if(getColorScheme() == 0)
|
||||
setColorScheme(1);
|
||||
else
|
||||
setColorScheme(0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
|
@ -197,7 +230,7 @@ function flipColors()
|
|||
// Initialize
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
redraw();
|
||||
setColorScheme(getColorScheme());
|
||||
|
||||
// Define Refresh Interval
|
||||
setInterval(updateTime, interval);
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@
|
|||
Generate scale based on defined minimum and maximum measurement
|
||||
Added background line on 50% to ease estimation of drawn values
|
||||
0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||
0.07: theme support
|
||||
|
|
|
|||
|
|
@ -221,9 +221,9 @@ function graphRecord(n) {
|
|||
if (tempCount == startLine) {
|
||||
// generating rgaph in loop when reaching startLine to keep loading
|
||||
// message on screen until graph can be drawn
|
||||
g.clear().
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()).
|
||||
// Home for Btn2
|
||||
setColor(1, 1, 1).
|
||||
setColor(g.theme.fg).
|
||||
drawLine(220, 118, 227, 110).
|
||||
drawLine(227, 110, 234, 118).
|
||||
drawPoly([222,117,222,125,232,125,232,117], false).
|
||||
|
|
@ -245,7 +245,7 @@ function graphRecord(n) {
|
|||
// scale indicator line for 50%
|
||||
drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
|
||||
// background line for 50%
|
||||
setColor(1, 1, 1).
|
||||
setColor(g.theme.fg).
|
||||
drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
|
||||
setFontAlign(1, -1, 0).
|
||||
setFont("Vector", 10);
|
||||
|
|
@ -303,7 +303,7 @@ function graphRecord(n) {
|
|||
log("Finished rendering data");
|
||||
Bangle.buzz(200, 0.3);
|
||||
g.flip();
|
||||
setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
|
||||
setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Fix timing issues, and use 1/2 scale to keep graph on screen
|
||||
0.04: Update for new firmwares that have a 'HRM-raw' event
|
||||
0.05: Tweaks for 'HRM-raw' handling
|
||||
0.06: Add widgets
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA="))
|
||||
require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4="))
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ Bangle.setHRMPower(1);
|
|||
var hrmInfo, hrmOffset = 0;
|
||||
var hrmInterval;
|
||||
var btm = g.getHeight()-1;
|
||||
var lastHrmPt = []; // last xy coords we draw a line to
|
||||
|
||||
function onHRM(h) {
|
||||
if (counter!==undefined) {
|
||||
// the first time we're called remove
|
||||
// the countdown
|
||||
counter = undefined;
|
||||
g.clear();
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
}
|
||||
hrmInfo = h;
|
||||
/* On 2v09 and earlier firmwares the only solution for realtime
|
||||
|
|
@ -28,7 +29,7 @@ function onHRM(h) {
|
|||
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,239,80);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75);
|
||||
var str = hrmInfo.bpm;
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
|
|
@ -43,17 +44,18 @@ Bangle.on('HRM-raw', function(v) {
|
|||
hrmOffset++;
|
||||
if (hrmOffset>g.getWidth()) {
|
||||
hrmOffset=0;
|
||||
g.clearRect(0,80,239,239);
|
||||
g.moveTo(-100,0);
|
||||
g.clearRect(0,80,g.getWidth(),g.getHeight());
|
||||
lastHrmPt = [-100,0];
|
||||
}
|
||||
|
||||
y = E.clip(btm-v.filt/4,btm-10,btm);
|
||||
g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y);
|
||||
y = E.clip(170 - (v.raw/2),80,btm);
|
||||
g.setColor(g.theme.fg).lineTo(hrmOffset, y);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
lastHrmPt = [hrmOffset, y];
|
||||
if (counter !==undefined) {
|
||||
counter = undefined;
|
||||
g.clear();
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -65,7 +67,10 @@ function countDown() {
|
|||
setTimeout(countDown, 1000);
|
||||
}
|
||||
}
|
||||
g.clear().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
countDown();
|
||||
|
||||
|
|
@ -79,13 +84,14 @@ function readHRM() {
|
|||
if (!hrmInfo) return;
|
||||
|
||||
if (hrmOffset==0) {
|
||||
g.clearRect(0,100,239,239);
|
||||
g.moveTo(-100,0);
|
||||
g.clearRect(0,100,g.getWidth(),g.getHeight());
|
||||
lastHrmPt = [-100,0];
|
||||
}
|
||||
for (var i=0;i<2;i++) {
|
||||
var a = hrmInfo.raw[hrmOffset];
|
||||
hrmOffset++;
|
||||
y = E.clip(170 - (a*2),100,230);
|
||||
g.setColor(g.theme.fg).lineTo(hrmOffset, y);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
lastHrmPt = [hrmOffset, y];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function drawMenu() {
|
|||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var m = w/2;
|
||||
var n = (h-48)/64;
|
||||
var n = Math.floor((h-48)/64);
|
||||
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
|
||||
if (selected<menuScroll) menuScroll = selected;
|
||||
// arrows
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# Messages app
|
||||
|
||||
**THIS APP IS CURRENTLY BETA**
|
||||
|
||||
This app handles the display of messages and message notifications. It stores
|
||||
a list of currently received messages and allows them to be listed, viewed,
|
||||
and responded to.
|
||||
|
||||
It is a replacement for the old `notify`/`gadgetbridge` apps.
|
||||
|
||||
## Usage
|
||||
|
||||
...
|
||||
|
||||
## Requests
|
||||
|
||||
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA="))
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
/* MESSAGES is a list of:
|
||||
{id:int,
|
||||
src,
|
||||
title,
|
||||
subject,
|
||||
body,
|
||||
sender,
|
||||
tel:string,
|
||||
new:true // not read yet
|
||||
}
|
||||
*/
|
||||
|
||||
/* For example for maps:
|
||||
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."})
|
||||
GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."})
|
||||
GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"})
|
||||
GB({"t":"notify-","id":1})
|
||||
|
||||
GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"})
|
||||
GB({"t":"notify~","id":1,"title":"High St"})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"})
|
||||
GB({"t":"notify~","id":1,"title":"0 yd - High St"})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"})
|
||||
|
||||
*/
|
||||
|
||||
var Layout = require("Layout");
|
||||
|
||||
var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
|
||||
if (!Array.isArray(MESSAGES)) MESSAGES=[];
|
||||
var onMessagesModified = function(msg) {
|
||||
// TODO: if new, show this new one
|
||||
if (msg.new) Bangle.buzz();
|
||||
showMessage(msg.id);
|
||||
};
|
||||
function saveMessages() {
|
||||
require("Storage").writeJSON("messages.json",MESSAGES)
|
||||
}
|
||||
|
||||
function showMapMessage(msg) {
|
||||
var m;
|
||||
var distance, street, target, eta;
|
||||
m=msg.title.match(/(.*) - (.*)/);
|
||||
if (m) {
|
||||
distance = m[1];
|
||||
street = m[2];
|
||||
} else street=msg.title;
|
||||
m=msg.body.match(/(.*) - (.*)/);
|
||||
if (m) {
|
||||
target = m[1];
|
||||
eta = m[2];
|
||||
} else target=msg.body;
|
||||
layout = new Layout({
|
||||
type:"v", c: [
|
||||
{type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 },
|
||||
{type:"h", bgCol:"#0f0", fillx:1, c: [
|
||||
{type:"txt", font:"6x8", label:"Towards" },
|
||||
{type:"txt", font:"6x15:2", label:street }
|
||||
]},
|
||||
{type:"h",fillx:1, filly:1, c: [
|
||||
{type:"img",src:atob(msg.img)},
|
||||
{type:"v", fillx:1, c: [
|
||||
{type:"txt", font:"6x15:2", label:distance||"" }
|
||||
]},
|
||||
]},
|
||||
|
||||
{type:"txt", font:"6x8:2", label:eta }
|
||||
]
|
||||
});
|
||||
g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1);
|
||||
layout.render();
|
||||
Bangle.setUI("updown",function() {
|
||||
// any input to mark as not new and return to menu
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
checkMessages();
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(msgid) {
|
||||
var msg = MESSAGES.find(m=>m.id==msgid);
|
||||
if (!msg) return checkMessages(); // go home if no message found
|
||||
if (msg.src=="Maps") return showMapMessage(msg);
|
||||
var m = msg.title+"\n"+msg.body;
|
||||
E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => {
|
||||
if (chosen=="read") {
|
||||
// any input to mark as not new and return to menu
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
checkMessages();
|
||||
} else {
|
||||
checkMessages(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show a single menu item for the message
|
||||
function showMessageMenuItem(y, idx) {
|
||||
var msg = MESSAGES[idx];
|
||||
var W = g.getWidth(), H=48;
|
||||
if (msg.new) g.setBgColor("#4F4");
|
||||
else g.setBgColor("#CFC");
|
||||
g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg);
|
||||
var m = msg.title+"\n"+msg.body;
|
||||
if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2);
|
||||
if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2);
|
||||
if (msg.body) {
|
||||
g.setFontAlign(-1,-1).setFont("6x8");
|
||||
var l = g.wrapString(msg.body, W-14);
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
g.drawString(l.join("\n"), 12,y+20);
|
||||
}
|
||||
}
|
||||
//test
|
||||
//g.clear(1); showMessageMenuItem(MESSAGES[0],24)
|
||||
|
||||
if (process.env.HWVERSION==1) { // Bangle.js 1
|
||||
showBigMenu = function(options) {
|
||||
/* options = {
|
||||
h = height
|
||||
items = # of items
|
||||
draw = function(y, idx)
|
||||
onSelect = function(idx)
|
||||
}*/
|
||||
var selected = 0;
|
||||
var menuScroll = 0;
|
||||
var menuShowing = false;
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var m = w/2;
|
||||
var n = Math.floor((h-48)/options.h);
|
||||
|
||||
function drawMenu() {
|
||||
g.reset();
|
||||
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
|
||||
if (selected<menuScroll) menuScroll = selected;
|
||||
// draw
|
||||
g.setColor(g.theme.fg);
|
||||
for (var i=0;i<n;i++) {
|
||||
var idx = i+menuScroll;
|
||||
if (idx<0 || idx>=options.items) break;
|
||||
var y = 24+i*options.h;
|
||||
options.draw(y, idx);
|
||||
// border for selected
|
||||
if (i+menuScroll==selected) {
|
||||
g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2);
|
||||
}
|
||||
}
|
||||
// arrows
|
||||
g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,6,m-14,20,m+14,20]);
|
||||
g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
|
||||
}
|
||||
g.clearRect(0,24,w-1,h-1);
|
||||
drawMenu();
|
||||
Bangle.setUI("updown",dir=>{
|
||||
if (dir) {
|
||||
selected += dir;
|
||||
if (selected<0) selected = options.items-1;
|
||||
if (selected>=options.items) selected = 0;
|
||||
drawMenu();
|
||||
} else {
|
||||
options.onSelect(selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else { // Bangle.js 2
|
||||
showBigMenu = function(options) {
|
||||
/* options = {
|
||||
h = height
|
||||
items = # of items
|
||||
draw = function(y, idx)
|
||||
onSelect = function(idx)
|
||||
}*/
|
||||
var menuScroll = 0;
|
||||
var menuShowing = false;
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var n = Math.ceil((h-24)/options.h);
|
||||
var menuScrollMax = options.h*options.items - (h-24);
|
||||
|
||||
function drawItem(i) {
|
||||
var y = 24+i*options.h-menuScroll;
|
||||
if (i<0 || i>=options.items || y<-options.h || y>=h) return;
|
||||
options.draw(y, i);
|
||||
}
|
||||
|
||||
function drawMenu() {
|
||||
g.reset().clearRect(0,24,w-1,h-1);
|
||||
g.setClipRect(0,24,w-1,h-1);
|
||||
for (var i=0;i<n;i++) drawItem(i);
|
||||
g.setClipRect(0,0,w-1,h-1);
|
||||
}
|
||||
drawMenu();
|
||||
g.flip(); // force an update now to make this snappier
|
||||
|
||||
Bangle.dragHandler = e=>{
|
||||
var dy = e.dy;
|
||||
if (menuScroll - dy < 0)
|
||||
dy = menuScroll;
|
||||
if (menuScroll - dy > menuScrollMax)
|
||||
dy = menuScroll - menuScrollMax;
|
||||
if (!dy) return;
|
||||
g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
|
||||
g.scroll(0,dy);
|
||||
menuScroll -= dy;
|
||||
if (e.dy < 0) {
|
||||
drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1);
|
||||
if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2);
|
||||
} else {
|
||||
drawItem(Math.floor((menuScroll+24)/options.h));
|
||||
if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1);
|
||||
}
|
||||
g.setClipRect(0,0,w-1,h-1);
|
||||
};
|
||||
Bangle.on('drag',Bangle.dragHandler);
|
||||
Bangle.touchHandler = (_,e)=>{
|
||||
if (e.y<20) return;
|
||||
var i = Math.floor((e.y+menuScroll-24) / options.h);
|
||||
if (i>=0 && i<options.items)
|
||||
options.onSelect(i);
|
||||
};
|
||||
Bangle.on("touch", Bangle.touchHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function checkMessages(forceShowMenu) {
|
||||
// If no messages, just show 'no messages' and return
|
||||
if (!MESSAGES.length)
|
||||
return E.showPrompt("No Messages",{
|
||||
title:"Messages",
|
||||
img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
|
||||
buttons : {"Ok":1}
|
||||
}).then(() => { load() });
|
||||
// we have >0 messages
|
||||
// TODO: IF A NEW MESSAGE, SHOW IT
|
||||
if (!forceShowMenu) {
|
||||
var newMessages = MESSAGES.filter(m=>m.new);
|
||||
if (newMessages.length)
|
||||
return showMessage(newMessages[0].id);
|
||||
}
|
||||
// Otherwise show a menu
|
||||
var m = {
|
||||
"":{title:"Messages"},
|
||||
"< Back": ()=>load()
|
||||
};
|
||||
/*g.setFont("6x8");
|
||||
MESSAGES.forEach(msg=>{
|
||||
// "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"
|
||||
var title = g.wrapString(msg.title, g.getWidth())[0];
|
||||
m[title] = function() {
|
||||
showMessage(msg.id);
|
||||
}
|
||||
});
|
||||
E.showMenu(m);*/
|
||||
showBigMenu({
|
||||
h : 48,
|
||||
items : MESSAGES.length,
|
||||
draw : showMessageMenuItem,
|
||||
onSelect : idx => showMessage(MESSAGES[idx].id)
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
checkMessages();
|
||||
|
After Width: | Height: | Size: 917 B |
|
|
@ -0,0 +1,36 @@
|
|||
(function() {
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
if (_GB) setTimeout(_GB,0,event);
|
||||
// call handling?
|
||||
if (!event.t.startsWith("notify")) return;
|
||||
/* event is:
|
||||
{t:"notify",id:int, src,title,subject,body,sender,tel:string}
|
||||
{t:"notify~",id:int, title:string} // modified
|
||||
{t:"notify-",id:int} // remove
|
||||
*/
|
||||
var messages, inApp = "undefined"!=typeof MESSAGES;
|
||||
if (inApp)
|
||||
messages = MESSAGES; // we're in an app that has already loaded messages
|
||||
else // no app - load messages
|
||||
messages = require("Storage").readJSON("messages.json",1)||[];
|
||||
// now modify/delete as appropriate
|
||||
var mIdx = messages.findIndex(m=>m.id==event.id);
|
||||
if (event.t=="notify-") {
|
||||
if (mIdx>=0) messages.splice(mIdx, 1); // remove item
|
||||
mIdx=-1;
|
||||
} else { // add/modify
|
||||
if (event.t=="notify") event.new=true; // new message
|
||||
if (mIdx<0) mIdx=messages.push(event)-1;
|
||||
else Object.assign(messages[mIdx], event);
|
||||
}
|
||||
require("Storage").writeJSON("messages.json",messages);
|
||||
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
|
||||
// ok, saved now - we only care if it's new
|
||||
if (event.t!="notify") return;
|
||||
// if we're in a clock, go straight to messages app
|
||||
if (Bangle.CLOCK) return load("messages.app.js");
|
||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
WIDGETS.messages.newMessage();
|
||||
};
|
||||
})()
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
||||
if (!this.width) return;
|
||||
var c = (Date.now()-this.t)/1000;
|
||||
g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff");
|
||||
g.clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12);
|
||||
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
|
||||
if (c<120 && (Date.now()-this.l)>4000) {
|
||||
this.l = Date.now();
|
||||
Bangle.buzz(); // buzz every 4 seconds
|
||||
}
|
||||
setTimeout(()=>WIDGETS["messages"].draw(), 1000);
|
||||
},newMessage:function() {
|
||||
WIDGETS["messages"].t=Date.now(); // first time
|
||||
WIDGETS["messages"].l=Date.now()-10000; // last buzz
|
||||
if (WIDGETS["messages"].c!==undefined) return; // already called
|
||||
WIDGETS["messages"].width=64;
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLCDPower(1);// turns screen on
|
||||
}};
|
||||
|
|
@ -3,3 +3,4 @@
|
|||
0.04: Make this clock do 12h and 24h
|
||||
0.05: setUI, screen size changes
|
||||
0.06: Use Bangle.setUI for button/launcher handling
|
||||
0.07: Update *on* the minute rather than every 15 secs
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
const big = g.getWidth()>200;
|
||||
const timeFontSize = big?6:5;
|
||||
const dateFontSize = big?3:2;
|
||||
|
|
@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9;
|
|||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
function drawSimpleClock() {
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
|
@ -60,11 +71,18 @@ function drawSimpleClock() {
|
|||
var gmt = da[5];
|
||||
g.setFont(font, gmtFontSize);
|
||||
g.drawString(gmt, xyCenter, yposGMT, true);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawSimpleClock();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// clean app screen
|
||||
|
|
@ -74,8 +92,5 @@ Bangle.setUI("clock");
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawSimpleClock, 15E3);
|
||||
|
||||
// draw now
|
||||
drawSimpleClock();
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Modified for use with new bootloader and firmware
|
||||
0.02: Use Bangle.setUI for button/launcher handling
|
||||
0.03: Fix display for Bangle 2
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
|
||||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var time = da[4].substr(0,5);
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 30, 239, 99);
|
||||
g.clearRect(0, 30, w, 99);
|
||||
g.setFontAlign(0, -1);
|
||||
g.setFont("Vector", 80);
|
||||
g.drawString(time, 120, 40);
|
||||
g.setFont("Vector", w/3);
|
||||
g.drawString(time, w/2, 40);
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: first release
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Stopwatch Touch
|
||||
|
||||
A touch screen based stop watch for Bangle 2
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Attractive UI design
|
||||
* Will run up to 99 hours
|
||||
* Shows 10th of seconds up to 1 hour
|
||||
* Start / Pause button
|
||||
* Reset button
|
||||
|
||||
## Future features
|
||||
|
||||
I'm keen to complete this project with
|
||||
|
||||
* Ability to dismiss the app and leave it running in the background
|
||||
* A small widget to show the elapsed time on the current active clock
|
||||
* Laptimes, with a way to view all the laptimes on a scrollable screen
|
||||
|
||||
|
||||
## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't
|
||||
|
||||
Which one is which ?
|
||||
|
||||

|
||||

|
||||
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 123 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 192 B |
|
|
@ -0,0 +1,220 @@
|
|||
let w = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
let tTotal = Date.now();
|
||||
let tStart = tTotal;
|
||||
let tCurrent = tTotal;
|
||||
let running = false;
|
||||
let timeY = 2*h/5;
|
||||
let displayInterval;
|
||||
let redrawButtons = true;
|
||||
const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
|
||||
|
||||
// 24 pixel images, scale to watch
|
||||
// 1 bit optimal, image string, no E.toArrayBuffer()
|
||||
const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
|
||||
const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
|
||||
const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
function timeToText(t) {
|
||||
let hrs = Math.floor(t/3600000);
|
||||
let mins = Math.floor(t/60000)%60;
|
||||
let secs = Math.floor(t/1000)%60;
|
||||
let tnth = Math.floor(t/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0)
|
||||
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||
else
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||
|
||||
//log_debug(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function drawButtons() {
|
||||
log_debug("drawButtons()");
|
||||
if (!running && tCurrent == tTotal) {
|
||||
bigPlayPauseBtn.draw();
|
||||
} else if (!running && tCurrent != tTotal) {
|
||||
resetBtn.draw();
|
||||
smallPlayPauseBtn.draw();
|
||||
} else {
|
||||
bigPlayPauseBtn.draw();
|
||||
}
|
||||
|
||||
redrawButtons = false;
|
||||
}
|
||||
|
||||
function drawTime() {
|
||||
log_debug("drawTime()");
|
||||
let Tt = tCurrent-tTotal;
|
||||
let Ttxt = timeToText(Tt);
|
||||
|
||||
// total time
|
||||
g.setFont("Vector",38); // check
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0, timeY - 21, w, timeY + 21);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(Ttxt, w/2, timeY);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
let last = tCurrent;
|
||||
if (running) tCurrent = Date.now();
|
||||
g.setColor(g.theme.fg);
|
||||
if (redrawButtons) drawButtons();
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
log_debug("startTimer()");
|
||||
draw();
|
||||
displayInterval = setInterval(draw, 100);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
log_debug("stopTimer()");
|
||||
if (displayInterval) {
|
||||
clearInterval(displayInterval);
|
||||
displayInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// BTN stop start
|
||||
function stopStart() {
|
||||
log_debug("stopStart()");
|
||||
|
||||
if (running)
|
||||
stopTimer();
|
||||
|
||||
running = !running;
|
||||
Bangle.buzz();
|
||||
|
||||
if (running)
|
||||
tStart = Date.now() + tStart- tCurrent;
|
||||
tTotal = Date.now() + tTotal - tCurrent;
|
||||
tCurrent = Date.now();
|
||||
|
||||
setButtonImages();
|
||||
redrawButtons = true;
|
||||
if (running) {
|
||||
startTimer();
|
||||
} else {
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonImages() {
|
||||
if (running) {
|
||||
bigPlayPauseBtn.setImage(pause_img);
|
||||
smallPlayPauseBtn.setImage(pause_img);
|
||||
resetBtn.setImage(reset_img);
|
||||
} else {
|
||||
bigPlayPauseBtn.setImage(play_img);
|
||||
smallPlayPauseBtn.setImage(play_img);
|
||||
resetBtn.setImage(reset_img);
|
||||
}
|
||||
}
|
||||
|
||||
// lap or reset
|
||||
function lapReset() {
|
||||
log_debug("lapReset()");
|
||||
if (!running && tStart != tCurrent) {
|
||||
redrawButtons = true;
|
||||
Bangle.buzz();
|
||||
tStart = tCurrent = tTotal = Date.now();
|
||||
g.clearRect(0,24,w,h);
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
// simple on screen button class
|
||||
function BUTTON(name,x,y,w,h,c,f,i) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.color = c;
|
||||
this.callback = f;
|
||||
this.img = i;
|
||||
}
|
||||
|
||||
BUTTON.prototype.setImage = function(i) {
|
||||
this.img = i;
|
||||
}
|
||||
|
||||
// if pressed the callback
|
||||
BUTTON.prototype.check = function(x,y) {
|
||||
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
|
||||
|
||||
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
|
||||
log_debug(this.name + ":callback\n");
|
||||
this.callback();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BUTTON.prototype.draw = function() {
|
||||
g.setColor(this.color);
|
||||
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
|
||||
g.setColor("#000"); // the icons and boxes are drawn black
|
||||
if (this.img != undefined) {
|
||||
let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale
|
||||
let ix = this.x + ((this.w - iw) /2);
|
||||
let iy = this.y + ((this.h - iw) /2);
|
||||
log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
|
||||
g.drawImage(this.img, ix, iy, {scale: iconScale});
|
||||
}
|
||||
g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
|
||||
};
|
||||
|
||||
|
||||
var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img);
|
||||
var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img);
|
||||
var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img);
|
||||
|
||||
bigPlayPauseBtn.setImage(play_img);
|
||||
smallPlayPauseBtn.setImage(play_img);
|
||||
resetBtn.setImage(pause_img);
|
||||
|
||||
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
// not running, and reset
|
||||
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return;
|
||||
|
||||
// paused and hit play
|
||||
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return;
|
||||
|
||||
// paused and press reset
|
||||
if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return;
|
||||
|
||||
// must be running
|
||||
if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return;
|
||||
});
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
// above not working, hence using next 2 lines
|
||||
g.setColor("#000");
|
||||
g.fillRect(0,0,w,h);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
Bangle.setUI("clock"); // Show launcher when button pressed
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="));
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Modification of SimpleClock 0.04 to use Vectorfont
|
||||
0.02: Use Bangle.setUI for button/launcher handling
|
||||
0.03: Scale to BangleJS 2 and add locale
|
||||
0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
const locale = require("locale");
|
||||
|
||||
var timeFontSize;
|
||||
|
|
@ -12,8 +11,7 @@ var yposDate;
|
|||
var yposYear;
|
||||
var yposGMT;
|
||||
|
||||
switch (process.env.BOARD) {
|
||||
case "EMSCRIPTEN":
|
||||
if (g.getWidth() > 200) {
|
||||
timeFontSize = 65;
|
||||
dateFontSize = 20;
|
||||
gmtFontSize = 10;
|
||||
|
|
@ -22,8 +20,7 @@ switch (process.env.BOARD) {
|
|||
yposDate = 130;
|
||||
yposYear = 175;
|
||||
yposGMT = 220;
|
||||
break;
|
||||
case "EMSCRIPTEN2":
|
||||
} else {
|
||||
timeFontSize = 48;
|
||||
dateFontSize = 15;
|
||||
gmtFontSize = 10;
|
||||
|
|
@ -32,12 +29,23 @@ switch (process.env.BOARD) {
|
|||
yposDate = 95;
|
||||
yposYear = 128;
|
||||
yposGMT = 161;
|
||||
break;
|
||||
}
|
||||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
function drawSimpleClock() {
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
|
@ -76,23 +84,26 @@ function drawSimpleClock() {
|
|||
// draw gmt
|
||||
g.setFont(font, gmtFontSize);
|
||||
g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawSimpleClock();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
// clean app screen
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawSimpleClock, 15E3);
|
||||
|
||||
// draw now
|
||||
drawSimpleClock();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ"))
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
|
||||
// get settings
|
||||
var settings = require("Storage").readJSON("vernierrespirate.json",1)||{};
|
||||
settings.vibrateBPM = settings.vibrateBPM||27;
|
||||
// settings.vibrate; // undefined / "calculated" / "vernier"
|
||||
|
||||
function saveSettings() {
|
||||
require("Storage").writeJSON("vernierrespirate.json", settings);
|
||||
}
|
||||
|
||||
|
||||
g.clear();
|
||||
var graphHeight = g.getHeight()-100;
|
||||
var last = {
|
||||
time : Date.now(),
|
||||
x : 0,
|
||||
y : 24,
|
||||
};
|
||||
var avrValue;
|
||||
var aboveAvr = false;
|
||||
var lastBreath;
|
||||
var lastBreaths = [];
|
||||
var vibrateInterval;
|
||||
|
||||
function onMsg(txt) {
|
||||
print(txt);
|
||||
E.showMessage(txt);
|
||||
}
|
||||
|
||||
function setVibrate(isOn) {
|
||||
var wasOn = vibrateInterval!==undefined;
|
||||
if (isOn == wasOn) return;
|
||||
|
||||
if (isOn) {
|
||||
vibrateInterval = setInterval(function() {
|
||||
Bangle.buzz();
|
||||
}, 1000);
|
||||
} else {
|
||||
clearInterval(vibrateInterval);
|
||||
vibrateInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onBreath() {
|
||||
var t = Date.now();
|
||||
if (lastBreath!==undefined) {
|
||||
// time between breaths
|
||||
var value = 60000 / (t-lastBreath);
|
||||
// average of last 3
|
||||
while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small
|
||||
lastBreaths.push(value);
|
||||
value = E.sum(lastBreaths) / lastBreaths.length;
|
||||
// draw value
|
||||
g.reset();
|
||||
g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50);
|
||||
g.setFont("6x8").setFontAlign(0,0);
|
||||
g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70);
|
||||
// set vibration IF we're doing it from our calculations
|
||||
if (settings.vibrate == "calculated")
|
||||
setVibrate(value > settings.vibrateBPM);
|
||||
}
|
||||
lastBreath = t;
|
||||
}
|
||||
|
||||
function onData(n, value) {
|
||||
g.reset();
|
||||
if (n==2) {
|
||||
function scale(v) {
|
||||
return Math.max(graphHeight - (1+v*4),24);
|
||||
}
|
||||
if (avrValue==undefined) avrValue=value;
|
||||
avrValue = avrValue*0.95 + value*0.05;
|
||||
if (avrValue < 1) avrValue = 1;
|
||||
if (value > avrValue) {
|
||||
if (!aboveAvr) onBreath();
|
||||
aboveAvr = true;
|
||||
} else aboveAvr = false;
|
||||
|
||||
var t = Date.now();
|
||||
var x = Math.round((t - last.time) / 100) // 10 per second
|
||||
if (last.x>=g.getWidth()) {
|
||||
x = 0;
|
||||
last.x = 0;
|
||||
last.time = t;
|
||||
g.clearRect(0,24,g.getWidth(),graphHeight);
|
||||
}
|
||||
var y = scale(value);
|
||||
g.setPixel(x, scale(avrValue), "#f00");
|
||||
g.drawLine(last.x, last.y, x, y);
|
||||
last.x = x;
|
||||
last.y = y;
|
||||
}
|
||||
if (n==4) {
|
||||
g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight());
|
||||
g.setFont("6x8").setFontAlign(0,0);
|
||||
g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20);
|
||||
// set vibration IF we're doing it from our calculations
|
||||
if (settings.vibrate == "vernier")
|
||||
setVibrate(value > settings.vibrateBPM);
|
||||
}
|
||||
Bangle.setLCDPower(1); // ensure LCD is on
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var gatt, service, rx, tx;
|
||||
var rollingCounter = 0xFF;
|
||||
|
||||
// any button to exit
|
||||
Bangle.setUI("updown", function() {
|
||||
setVibrate(false);
|
||||
Bangle.buzz();
|
||||
try {
|
||||
if (gatt) gatt.disconnect();
|
||||
} catch (e) {
|
||||
}
|
||||
setTimeout(mainMenu, 1000);
|
||||
});
|
||||
|
||||
function sendCommand(subCommand) {
|
||||
const command = new Uint8Array(4 + subCommand.length);
|
||||
command.set(new Uint8Array(subCommand), 4);
|
||||
// Populate the packet header bytes
|
||||
command[0] = 0x58; // header
|
||||
command[1] = command.length;
|
||||
command[2] = --rollingCounter;
|
||||
command[3] = E.sum(command) & 0xFF; // checksum
|
||||
return tx.writeValue(command);
|
||||
}
|
||||
function firstSetBit(v) {
|
||||
return v & -v;
|
||||
}
|
||||
function handleResponse(dv) {
|
||||
//print(dv.buffer);
|
||||
var resType = dv.getUint8(0);
|
||||
if (resType==0x20) {
|
||||
// [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127])
|
||||
// 6 = data type = real
|
||||
// 6,0 = bit mask for sensors
|
||||
// 2 = value count
|
||||
if (dv.getUint8(4)!=6) return; //throw "Not float32 data";
|
||||
var sensorIds = dv.getUint16(5, true);
|
||||
// var count = dv.getUint8(7); doesn't seem right
|
||||
var offs = 9;
|
||||
while (sensorIds) {
|
||||
var value = dv.getFloat32(offs, true);
|
||||
var s = firstSetBit(sensorIds);
|
||||
if (isFinite(value)) onData(s,value);
|
||||
//else print(s,value);
|
||||
sensorIds &= ~s;
|
||||
offs += 4;
|
||||
}
|
||||
} else {
|
||||
var cmd = dv.getUint8(4); // cmd
|
||||
//print("CMD",dv.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
onMsg("Searching...");
|
||||
NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) {
|
||||
device.on("gattserverdisconnected", function() {
|
||||
onMsg("Device disconnected");
|
||||
});
|
||||
onMsg("Found. Connecting...");
|
||||
return device.gatt.connect({minInterval:20, maxInterval:20});
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112");
|
||||
}).then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb");
|
||||
}).then(function(c) {
|
||||
tx = c;
|
||||
return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe");
|
||||
}).then(function(c) {
|
||||
rx = c;
|
||||
rx.on('characteristicvaluechanged', function(event) {
|
||||
//print("EVT",event.target.value.buffer);
|
||||
handleResponse(event.target.value);
|
||||
});
|
||||
return rx.startNotifications();
|
||||
}).then(function() {
|
||||
onMsg("Init");
|
||||
sendCommand([ // init
|
||||
0x1a, 0xa5, 0x4a, 0x06,
|
||||
0x49, 0x07, 0x48, 0x08,
|
||||
0x47, 0x09, 0x46, 0x0a,
|
||||
0x45, 0x0b, 0x44, 0x0c,
|
||||
0x43, 0x0d, 0x42, 0x0e,
|
||||
0x41,
|
||||
]);
|
||||
/*setTimeout(function() {
|
||||
print("Set measurement period");
|
||||
var us = 100000; // period in us
|
||||
sendCommand([0x1b, 0xff, 0x00,
|
||||
us & 255,
|
||||
(us >> 8) & 255,
|
||||
(us >> 16) & 255,
|
||||
(us >> 24) & 255,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00]);
|
||||
}, 100);*/
|
||||
|
||||
/* setTimeout(function() {
|
||||
print("Get sensor info");
|
||||
sendCommand([0x51, 0]); // get sensor IDs
|
||||
// returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0]
|
||||
// 54 is the bit mask of available channels
|
||||
//sendCommand([106, 16]); // get sensor info
|
||||
}, 2000);*/
|
||||
|
||||
setTimeout(function() {
|
||||
onMsg("Start measurements");
|
||||
//https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588
|
||||
var channels = 6; // data channels 4 and 2
|
||||
sendCommand([ // start measurements
|
||||
0x18, 0xff, 0x01, channels,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00
|
||||
]);
|
||||
}, 500);
|
||||
}).catch(function() {
|
||||
onMsg("Connect Fail");
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
function mainMenu() {
|
||||
var vibText = ["No","Calculated","Vernier"];
|
||||
var vibValue = ["","calculated","vernier"];
|
||||
E.showMenu({"":{title:"Respiration Belt"},
|
||||
"< Back" : () => { saveSettings(); load(); },
|
||||
"Connect" : () => { saveSettings(); E.showMenu(); connect(); },
|
||||
"Vib" : {
|
||||
value : Math.max(vibValue.indexOf(settings.vibrate),0),
|
||||
format : v => vibText[v],
|
||||
min:0,max:2,
|
||||
onchange : v => { settings.vibrate=vibValue[v]; }
|
||||
},
|
||||
"BPM" : {
|
||||
value : settings.vibrateBPM,
|
||||
min:10,max:50,
|
||||
onchange : v => { settings.vibrateBPM=v; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mainMenu();
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -5,3 +5,4 @@
|
|||
0.06: Use 'g.theme' (requires bootloader 0.23)
|
||||
0.07: Move CHARGING variable to more readable string
|
||||
0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on
|
||||
0.09: Misc speed/memory tweaks
|
||||
|
|
|
|||
|
|
@ -1,26 +1,11 @@
|
|||
(function(){
|
||||
|
||||
function setWidth() {
|
||||
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
|
||||
}
|
||||
function draw() {
|
||||
var s = 39;
|
||||
var x = this.x, y = this.y;
|
||||
g.reset();
|
||||
if (Bangle.isCharging()) {
|
||||
g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
||||
x+=16;
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(x,y+2,x+s-4,y+21);
|
||||
g.clearRect(x+2,y+4,x+s-6,y+19);
|
||||
g.fillRect(x+s-3,y+10,x+s,y+14);
|
||||
g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
|
||||
}
|
||||
Bangle.on('charging',function(charging) {
|
||||
if(charging) Bangle.buzz();
|
||||
setWidth();
|
||||
Bangle.drawWidgets(); // relayout widgets
|
||||
Bangle.drawWidgets(); // re-layout widgets
|
||||
g.flip();
|
||||
});
|
||||
var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
|
||||
|
|
@ -37,6 +22,16 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
WIDGETS["bat"]={area:"tr",width:40,draw:draw};
|
||||
WIDGETS["bat"]={area:"tr",width:40,draw:function() {
|
||||
var s = 39;
|
||||
var x = this.x, y = this.y;
|
||||
g.reset();
|
||||
if (Bangle.isCharging()) {
|
||||
g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
||||
x+=16;
|
||||
}
|
||||
g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
|
||||
g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
|
||||
}};
|
||||
setWidth();
|
||||
})()
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 280 B |
|
|
@ -0,0 +1 @@
|
|||
0.01: New widget
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Bangle.on('charging',function(charging) {
|
||||
if(charging) Bangle.buzz();
|
||||
WIDGETS["batv"].draw();
|
||||
});
|
||||
setInterval(()=>WIDGETS["batv"].draw(), 60000);
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) WIDGETS["batv"].draw(); // refresh at power on
|
||||
});
|
||||
WIDGETS["batv"]={area:"tr",width:14,draw:function() {
|
||||
var x = this.x, y = this.y;
|
||||
g.reset();
|
||||
if (Bangle.isCharging()) {
|
||||
g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
||||
} else {
|
||||
g.clearRect(x,y,x+14,y+24);
|
||||
g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2);
|
||||
g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20);
|
||||
}
|
||||
}};
|
||||
|
After Width: | Height: | Size: 221 B |
|
|
@ -3,3 +3,4 @@
|
|||
0.04: Fix automatic update of Bluetooth connection status
|
||||
0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color
|
||||
0.06: Tweaking colors for dark/light themes and low bpp screens
|
||||
0.07: Memory usage improvements
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
(function(){
|
||||
function draw() {
|
||||
WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() {
|
||||
g.reset();
|
||||
if (NRF.getSecurityStatus().connected)
|
||||
g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
|
||||
else
|
||||
g.setColor(g.theme.dark ? "#666" : "#999");
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
|
||||
}
|
||||
function changed() {
|
||||
},changed:function() {
|
||||
WIDGETS["bluetooth"].draw();
|
||||
g.flip();// turns screen on
|
||||
}
|
||||
NRF.on('connect',changed);
|
||||
NRF.on('disconnect',changed);
|
||||
WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw};
|
||||
})()
|
||||
Bangle.setLCDPower(1); // turn screen on
|
||||
}};
|
||||
NRF.on('connect',WIDGETS["bluetooth"].changed);
|
||||
NRF.on('disconnect',WIDGETS["bluetooth"].changed);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.02: Works with light theme
|
||||
Doesn't drain battery by updating every 2 secs
|
||||
Fix alignment
|
||||
|
|
@ -1,30 +1,17 @@
|
|||
(function(){
|
||||
//var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA"));
|
||||
//var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA=="));
|
||||
var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"));
|
||||
var cp = Bangle.setCompassPower;
|
||||
Bangle.setCompassPower = () => {
|
||||
cp.apply(Bangle, arguments);
|
||||
WIDGETS.compass.draw();
|
||||
};
|
||||
|
||||
function draw() {
|
||||
WIDGETS.compass={area:"tr",width:24,draw:function() {
|
||||
g.reset();
|
||||
if (Bangle.isCompassOn()) {
|
||||
g.setColor(1,0.8,0); // on = amber
|
||||
g.setColor(g.theme.dark ? "#FC0" : "#F00");
|
||||
} else {
|
||||
g.setColor(0.3,0.3,0.3); // off = grey
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC");
|
||||
}
|
||||
g.drawImage(img, 10+this.x, 2+this.var);
|
||||
}
|
||||
|
||||
var timerInterval;
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
WIDGETS.compass.draw();
|
||||
if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000);
|
||||
} else {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
WIDGETS.compass={area:"tr",width:24,draw:draw};
|
||||
g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var);
|
||||
}};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Tweaks for variable size widget system
|
||||
0.03: Ensure redrawing works with variable size widget system
|
||||
0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||
0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,38 @@
|
|||
(() => {
|
||||
var currentBPM = undefined;
|
||||
var lastBPM = undefined;
|
||||
var firstBPM = true; // first reading since sensor turned on
|
||||
if (!Bangle.isLocked) return; // old firmware
|
||||
var currentBPM;
|
||||
var lastBPM;
|
||||
var isHRMOn = false;
|
||||
|
||||
function draw() {
|
||||
// turn on sensor when the LCD is unlocked
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
Bangle.setHRMPower(1,"widhrm");
|
||||
currentBPM = undefined;
|
||||
WIDGETS["hrm"].draw();
|
||||
} else {
|
||||
Bangle.setHRMPower(0,"widhrm");
|
||||
}
|
||||
});
|
||||
|
||||
var hp = Bangle.setHRMPower;
|
||||
Bangle.setHRMPower = () => {
|
||||
hp.apply(Bangle, arguments);
|
||||
isHRMOn = Bangle.isHRMOn();
|
||||
WIDGETS["hrm"].draw();
|
||||
};
|
||||
|
||||
Bangle.on('HRM',function(d) {
|
||||
currentBPM = d.bpm;
|
||||
lastBPM = currentBPM;
|
||||
WIDGETS["hrm"].draw();
|
||||
});
|
||||
|
||||
// add your widget
|
||||
WIDGETS["hrm"]={area:"tl",width:24,draw:function() {
|
||||
var width = 24;
|
||||
g.reset();
|
||||
g.setFont("6x8", 1);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0);
|
||||
g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
|
||||
var bpm = currentBPM, isCurrent = true;
|
||||
if (bpm===undefined) {
|
||||
|
|
@ -16,36 +41,12 @@
|
|||
}
|
||||
if (bpm===undefined)
|
||||
bpm = "--";
|
||||
g.setColor(isCurrent ? "#ffffff" : "#808080");
|
||||
g.setColor(isCurrent ? g.theme.fg : "#808080");
|
||||
g.drawString(bpm, this.x+width/2, this.y+19);
|
||||
g.setColor(isCurrent ? "#ff0033" : "#808080");
|
||||
g.setColor(isHRMOn ? "#ff0033" : "#808080");
|
||||
g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1);
|
||||
g.setColor(-1);
|
||||
}
|
||||
}};
|
||||
|
||||
// redraw when the LCD turns on
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
Bangle.setHRMPower(1,"widhrm");
|
||||
firstBPM = true;
|
||||
currentBPM = undefined;
|
||||
WIDGETS["hrm"].draw();
|
||||
} else {
|
||||
Bangle.setHRMPower(0,"widhrm");
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('HRM',function(d) {
|
||||
if (firstBPM)
|
||||
firstBPM=false; // ignore the first one as it's usually rubbish
|
||||
else {
|
||||
currentBPM = d.bpm;
|
||||
lastBPM = currentBPM;
|
||||
}
|
||||
WIDGETS["hrm"].draw();
|
||||
});
|
||||
Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm");
|
||||
|
||||
// add your widget
|
||||
WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
|
||||
Bangle.setHRMPower(!Bangle.isLocked(),"widhrm");
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
0.01: First version
|
||||
0.02: Don't break if running on 2v08 firmware (just don't display anything)
|
||||
|
||||
0.03: Works with light theme
|
||||
Doesn't drain battery by updating every 2 secs
|
||||
fix alignment
|
||||
|
|
|
|||
|
|
@ -1,28 +1,18 @@
|
|||
(function(){
|
||||
if (!Bangle.isHRMOn) return; // old firmware
|
||||
var hp = Bangle.setHRMPower;
|
||||
Bangle.setHRMPower = () => {
|
||||
hp.apply(Bangle, arguments);
|
||||
WIDGETS.widhrt.draw();
|
||||
};
|
||||
|
||||
function draw() {
|
||||
WIDGETS.widhrt={area:"tr",width:24,draw:function() {
|
||||
g.reset();
|
||||
if (Bangle.isHRMOn()) {
|
||||
g.setColor(1,0,0); // on = red
|
||||
g.setColor("#f00"); // on = red
|
||||
} else {
|
||||
g.setColor(0.3,0.3,0.3); // off = grey
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
||||
}
|
||||
g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y);
|
||||
}
|
||||
|
||||
var timerInterval;
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
WIDGETS.widhrt.draw();
|
||||
if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000);
|
||||
} else {
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
WIDGETS.widhrt={area:"tr",width:24,draw:draw};
|
||||
g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y);
|
||||
}};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New Widget!
|
||||
0.02: Theme support, memory savings
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
/* jshint esversion: 6 */
|
||||
(() => {
|
||||
const CBS = 0x41f, CBC = 0x07E0;
|
||||
var xo = 6, xl = 22, yo = 9, h = 17;
|
||||
|
||||
function draw() {
|
||||
g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4);
|
||||
g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h);
|
||||
var cbc = (Bangle.isCharging()) ? CBC : CBS;
|
||||
g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h);
|
||||
}
|
||||
Bangle.on('charging', function(charging) {
|
||||
Bangle.on('charging', function(charging) {
|
||||
if (charging) Bangle.buzz();
|
||||
Bangle.drawWidgets();
|
||||
});
|
||||
WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw };
|
||||
})();
|
||||
WIDGETS["widtbat"].draw();
|
||||
});
|
||||
WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() {
|
||||
const xo = 6, xl = 22, yo = 9, h = 17;
|
||||
g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4);
|
||||
g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h);
|
||||
var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f";
|
||||
g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h);
|
||||
} };
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 238 B |
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Update custom.html for refactor; add README
|
||||
0.03: Update for larger secondary timezone display (#610)
|
||||
0.04: setUI, different screen sizes
|
||||
0.05: Now update *on* the minute rather than every 15 secs
|
||||
Fix rendering of single extra timezone on Bangle.js 2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* jshint esversion: 6 */
|
||||
|
||||
const big = g.getWidth()>200;
|
||||
// Font for primary time and date
|
||||
const primaryTimeFontSize = big?6:5;
|
||||
|
|
@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1;
|
|||
|
||||
const font = "6x8";
|
||||
|
||||
/* TODO: we could totally use 'Layout' here and
|
||||
avoid a whole bunch of hard-coded offsets */
|
||||
|
||||
|
||||
const xyCenter = g.getWidth() / 2;
|
||||
const yposTime = big ? 75 : 60;
|
||||
const yposTime2 = yposTime + (big ? 100 : 60);
|
||||
const yposDate = big ? 130 : 90;
|
||||
const yposWorld = big ? 170 : 120;
|
||||
|
||||
|
|
@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || [];
|
|||
// TESTING CODE
|
||||
// Used to test offset array values during development.
|
||||
// Uncomment to override secondary offsets value
|
||||
|
||||
// const mockOffsets = {
|
||||
// zeroOffsets: [],
|
||||
// oneOffset: [["UTC", 0]],
|
||||
// twoOffsets: [
|
||||
// ["Tokyo", 9],
|
||||
// ["UTC", 0],
|
||||
// ],
|
||||
// fourOffsets: [
|
||||
// ["Tokyo", 9],
|
||||
// ["UTC", 0],
|
||||
// ["Denver", -7],
|
||||
// ["Miami", -5],
|
||||
// ],
|
||||
// fiveOffsets: [
|
||||
// ["Tokyo", 9],
|
||||
// ["UTC", 0],
|
||||
// ["Denver", -7],
|
||||
// ["Chicago", -6],
|
||||
// ["Miami", -5],
|
||||
// ],
|
||||
// };
|
||||
/*
|
||||
const mockOffsets = {
|
||||
zeroOffsets: [],
|
||||
oneOffset: [["UTC", 0]],
|
||||
twoOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
],
|
||||
fourOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
["Denver", -7],
|
||||
["Miami", -5],
|
||||
],
|
||||
fiveOffsets: [
|
||||
["Tokyo", 9],
|
||||
["UTC", 0],
|
||||
["Denver", -7],
|
||||
["Chicago", -6],
|
||||
["Miami", -5],
|
||||
],
|
||||
};*/
|
||||
|
||||
// Uncomment one at a time to test various offsets array scenarios
|
||||
// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time
|
||||
// offsets = mockOffsets.oneOffset; // should render larger in two rows
|
||||
// offsets = mockOffsets.twoOffsets; // should render two in columns
|
||||
// offsets = mockOffsets.fourOffsets; // should render in columns
|
||||
// offsets = mockOffsets.fiveOffsets; // should render first four in columns
|
||||
//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time
|
||||
//offsets = mockOffsets.oneOffset; // should render larger in two rows
|
||||
//offsets = mockOffsets.twoOffsets; // should render two in columns
|
||||
//offsets = mockOffsets.fourOffsets; // should render in columns
|
||||
//offsets = mockOffsets.fiveOffsets; // should render first four in columns
|
||||
|
||||
// END TESTING CODE
|
||||
|
||||
// Check settings for what type our clock should be
|
||||
//var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
var secondInterval;
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function doublenum(x) {
|
||||
return x < 10 ? "0" + x : "" + x;
|
||||
|
|
@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) {
|
|||
return new Date(dt.getTime() + offset * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function drawSimpleClock() {
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
|
@ -111,9 +125,9 @@ function drawSimpleClock() {
|
|||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||
const xOffset = 30;
|
||||
g.setFont(font, secondaryTimeFontSize);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
|
||||
|
||||
// draw Day, name of month, Date
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
|
|
@ -132,6 +146,8 @@ function drawSimpleClock() {
|
|||
g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
|
||||
}
|
||||
});
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
|
|
@ -141,18 +157,15 @@ Bangle.setUI("clock");
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 15 sec when screen is on
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
secondInterval = setInterval(drawSimpleClock, 15e3);
|
||||
drawSimpleClock(); // draw immediately
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// draw now and every 15 sec until display goes off
|
||||
drawSimpleClock();
|
||||
if (Bangle.isLCDOn()) {
|
||||
secondInterval = setInterval(drawSimpleClock, 15e3);
|
||||
}
|
||||
// draw now
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/nodejs
|
||||
/* Quick hack to add proper 'supports' field to apps.json
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
|
||||
var BASEDIR = __dirname+"/../";
|
||||
|
||||
var appsFile, apps;
|
||||
try {
|
||||
appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
|
||||
} catch (e) {
|
||||
ERROR("apps.json not found");
|
||||
}
|
||||
try{
|
||||
apps = JSON.parse(appsFile);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
var m = e.toString().match(/in JSON at position (\d+)/);
|
||||
if (m) {
|
||||
var char = parseInt(m[1]);
|
||||
console.log("===============================================");
|
||||
console.log("LINE "+appsFile.substr(0,char).split("\n").length);
|
||||
console.log("===============================================");
|
||||
console.log(appsFile.substr(char-10, 20));
|
||||
console.log("===============================================");
|
||||
}
|
||||
console.log(m);
|
||||
ERROR("apps.json not valid JSON");
|
||||
|
||||
}
|
||||
|
||||
apps = apps.map((app,appIdx) => {
|
||||
var tags = [];
|
||||
if (app.tags) tags = app.tags.split(",").map(t=>t.trim());
|
||||
var supportsB1 = true;
|
||||
var supportsB2 = false;
|
||||
if (tags.includes("b2")) {
|
||||
tags = tags.filter(x=>x!="b2");
|
||||
supportsB2 = true;
|
||||
}
|
||||
if (tags.includes("bno2")) {
|
||||
tags = tags.filter(x=>x!="bno2");
|
||||
supportsB2 = false;
|
||||
}
|
||||
if (tags.includes("bno1")) {
|
||||
tags = tags.filter(x=>x!="bno1");
|
||||
supportsB1 = false;
|
||||
}
|
||||
app.tags = tags.join(",");
|
||||
app.supports = [];
|
||||
if (supportsB1) app.supports.push("BANGLEJS");
|
||||
if (supportsB2) app.supports.push("BANGLEJS2");
|
||||
return app;
|
||||
});
|
||||
|
||||
var KEY_ORDER = [
|
||||
"id","name","shortName","version","description","icon","type","tags","supports",
|
||||
"dependencies", "readme", "custom", "customConnect", "interface",
|
||||
"allow_emulator", "storage", "data", "sortorder"
|
||||
];
|
||||
|
||||
var JS = JSON.stringify;
|
||||
var json = "[\n "+apps.map(app=>{
|
||||
var keys = KEY_ORDER.filter(k=>k in app);
|
||||
Object.keys(app).forEach(k=>{
|
||||
if (!KEY_ORDER.includes(k))
|
||||
throw new Error(`Key named ${k} not known!`);
|
||||
});
|
||||
|
||||
|
||||
return "{\n "+keys.map(k=>{
|
||||
var js = JS(app[k]);
|
||||
if (k=="storage")
|
||||
js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]";
|
||||
return JS(k)+": "+js;
|
||||
}).join(",\n ")+"\n }";
|
||||
}).join(",\n ")+"\n]\n";
|
||||
|
||||
//console.log(json);
|
||||
|
||||
console.log("new apps.json written");
|
||||
fs.writeFileSync(BASEDIR+"apps.json", json);
|
||||
|
|
@ -51,7 +51,8 @@ try{
|
|||
|
||||
const APP_KEYS = [
|
||||
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
|
||||
'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator',
|
||||
'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data',
|
||||
'supports', 'allow_emulator',
|
||||
'dependencies'
|
||||
];
|
||||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
|
||||
|
|
@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => {
|
|||
if (!app.name) ERROR(`App ${app.id} has no name`);
|
||||
var isApp = !app.type || app.type=="app";
|
||||
if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
|
||||
if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
|
||||
else {
|
||||
app.supports.forEach(dev => {
|
||||
if (!["BANGLEJS","BANGLEJS2"].includes(dev))
|
||||
ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (!app.version) WARN(`App ${app.id} has no version`);
|
||||
else {
|
||||
if (!fs.existsSync(appDir+"ChangeLog")) {
|
||||
|
|
|
|||
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
|
||||
Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a
|
||||
22
css/main.css
|
|
@ -23,6 +23,9 @@
|
|||
.filter-nav {
|
||||
display: inline-block;
|
||||
}
|
||||
.device-nav {
|
||||
display: inline-block;
|
||||
}
|
||||
.sort-nav {
|
||||
float: right;
|
||||
}
|
||||
|
|
@ -32,6 +35,21 @@
|
|||
top: 36px;
|
||||
left: -24px;
|
||||
}
|
||||
.btn-favourite {
|
||||
color: red;
|
||||
.btn.btn-favourite { color: red; }
|
||||
.btn.btn-favourite:hover { color: red; }
|
||||
|
||||
.icon.icon-emulator { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-emulator::before {
|
||||
content: "\01F5B5";
|
||||
}
|
||||
.icon.icon-favourite { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-favourite::before {
|
||||
content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */
|
||||
}
|
||||
.icon.icon-favourite-active::before {
|
||||
content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */
|
||||
}
|
||||
.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/
|
||||
.icon.icon-interface::before {
|
||||
content: "\01F5AB";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"]
|
||||
11
index.html
|
|
@ -60,6 +60,17 @@
|
|||
|
||||
<div class="container apploader-tab" id="librarycontainer">
|
||||
<div>
|
||||
<div class="dropdown devicetype-nav">
|
||||
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0">
|
||||
<span>All apps</span><i class="icon icon-caret"></i>
|
||||
</a>
|
||||
<!-- menu component -->
|
||||
<ul class="menu">
|
||||
<li class="menu-item"><a>All apps</a></li>
|
||||
<li class="menu-item"><a dt="BANGLEJS">Bangle.js 1</a></li>
|
||||
<li class="menu-item"><a dt="BANGLEJS2">Bangle.js 2</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="filter-nav">
|
||||
<label class="chip active" filterid="">Default</label>
|
||||
<label class="chip" filterid="clock">Clocks</label>
|
||||
|
|
|
|||
127
loader.js
|
|
@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") {
|
|||
var RECOMMENDED_VERSION = "2v10";
|
||||
// could check http://www.espruino.com/json/BANGLEJS.json for this
|
||||
|
||||
// We're only interested in Bangles
|
||||
DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS"));
|
||||
|
||||
// Set up source code URL
|
||||
(function() {
|
||||
let username = "espruino";
|
||||
let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
|
||||
|
|
@ -21,6 +25,7 @@ var RECOMMENDED_VERSION = "2v10";
|
|||
Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`;
|
||||
})();
|
||||
|
||||
// When a device is found, filter the apps accordingly
|
||||
function onFoundDeviceInfo(deviceId, deviceVersion) {
|
||||
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
|
||||
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
|
||||
|
|
@ -33,4 +38,126 @@ function onFoundDeviceInfo(deviceId, deviceVersion) {
|
|||
if (deviceId == "BANGLEJS2") {
|
||||
Const.MESSAGE_RELOAD = 'Hold button\nto reload';
|
||||
}
|
||||
|
||||
// check against features shown?
|
||||
filterAppsForDevice(deviceId);
|
||||
/* if we'd saved a device ID but this device is different, ensure
|
||||
we ask again next time */
|
||||
var savedDeviceId = getSavedDeviceId();
|
||||
if (savedDeviceId!==undefined && savedDeviceId!=deviceId)
|
||||
setSavedDeviceId(undefined);
|
||||
}
|
||||
|
||||
var originalAppJSON = undefined;
|
||||
function filterAppsForDevice(deviceId) {
|
||||
if (originalAppJSON===undefined)
|
||||
originalAppJSON = appJSON;
|
||||
|
||||
var device = DEVICEINFO.find(d=>d.id==deviceId);
|
||||
// set the device dropdown
|
||||
document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps";
|
||||
|
||||
if (!device) {
|
||||
if (deviceId!==undefined)
|
||||
showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning");
|
||||
appJSON = originalAppJSON;
|
||||
} else {
|
||||
// Now filter apps
|
||||
appJSON = originalAppJSON.filter(app => {
|
||||
var supported = ["BANGLEJS"];
|
||||
if (!app.supports) {
|
||||
console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`);
|
||||
return false;
|
||||
}
|
||||
if (app.supports.includes(deviceId)) return true;
|
||||
//console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
refreshLibrary();
|
||||
}
|
||||
|
||||
// If 'remember' was checked in the window below, this is the device
|
||||
function getSavedDeviceId() {
|
||||
let deviceId = localStorage.getItem("deviceId");
|
||||
if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId))
|
||||
return deviceId;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function setSavedDeviceId(deviceId) {
|
||||
localStorage.setItem("deviceId", deviceId);
|
||||
}
|
||||
|
||||
// At boot, show a window to choose which type of device you have...
|
||||
window.addEventListener('load', (event) => {
|
||||
let deviceId = getSavedDeviceId()
|
||||
if (deviceId !== undefined)
|
||||
return filterAppsForDevice(deviceId);
|
||||
|
||||
var html = `<div class="columns">
|
||||
${DEVICEINFO.map(d=>`
|
||||
<div class="column col-6 col-xs-6">
|
||||
<div class="card devicechooser" deviceid="${d.id}" style="cursor:pointer">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">${d.name}</div>
|
||||
<!--<div class="card-subtitle text-gray">...</div>-->
|
||||
</div>
|
||||
<div class="card-image">
|
||||
<img src="${d.img}" alt="${d.name}" width="256" height="256" class="img-responsive">
|
||||
</div>
|
||||
</div>
|
||||
</div>`).join("\n")}
|
||||
</div><div class="columns">
|
||||
<div class="column col-12">
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="remember_device">
|
||||
<i class="form-icon"></i> Don't ask again
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
showPrompt("Which Bangle.js?",html,{},false);
|
||||
htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => {
|
||||
button.addEventListener("click",event => {
|
||||
let rememberDevice = document.getElementById("remember_device").checked;
|
||||
|
||||
let button = event.currentTarget;
|
||||
let deviceId = button.getAttribute("deviceid");
|
||||
hidePrompt();
|
||||
console.log("Chosen device", deviceId);
|
||||
setSavedDeviceId(rememberDevice ? deviceId : undefined);
|
||||
filterAppsForDevice(deviceId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('load', (event) => {
|
||||
// Hook onto device chooser dropdown
|
||||
htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => {
|
||||
button.addEventListener("click", event => {
|
||||
var a = event.target;
|
||||
var deviceId = a.getAttribute("dt")||undefined;
|
||||
filterAppsForDevice(deviceId); // also sets the device dropdown
|
||||
setSavedDeviceId(undefined); // ask at startup next time
|
||||
document.querySelector(".devicetype-nav span").innerText = a.innerText;
|
||||
});
|
||||
});
|
||||
|
||||
// Button to install all default apps in one go
|
||||
document.getElementById("installdefault").addEventListener("click",event=>{
|
||||
getInstalledApps().then(() => {
|
||||
if (device.id == "BANGLEJS")
|
||||
return httpGet("defaultapps_banglejs.json");
|
||||
if (device.id == "BANGLEJS2")
|
||||
return httpGet("defaultapps_banglejs2.json");
|
||||
throw new Error("Unknown device "+device.id);
|
||||
}).then(json=>{
|
||||
return installMultipleApps(JSON.parse(json), "default");
|
||||
}).catch(err=>{
|
||||
Progress.hide({sticky:true});
|
||||
showToast("App Install failed, "+err,"error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -169,14 +169,23 @@ function Layout(layout, options) {
|
|||
Bangle.on('touch',Bangle.touchHandler);
|
||||
}
|
||||
|
||||
// add IDs
|
||||
// recurse over layout doing some fixing up if needed
|
||||
var ll = this;
|
||||
function idRecurser(l) {
|
||||
function recurser(l) {
|
||||
// add IDs
|
||||
if (l.id) ll[l.id] = l;
|
||||
// fix type up
|
||||
if (!l.type) l.type="";
|
||||
if (l.c) l.c.forEach(idRecurser);
|
||||
// FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally
|
||||
// fix fonts for pre-2v11 firmware
|
||||
if (l.font && l.font.includes(":")) {
|
||||
var f = l.font.split(":");
|
||||
l.font = f[0];
|
||||
l.fsz = f[1];
|
||||
}
|
||||
idRecurser(layout);
|
||||
if (l.c) l.c.forEach(recurser);
|
||||
}
|
||||
recurser(this._l);
|
||||
this.updateNeeded = true;
|
||||
}
|
||||
|
||||
|
|
@ -352,12 +361,6 @@ Layout.prototype.update = function() {
|
|||
"txt" : function(l) {
|
||||
if (l.font.endsWith("%"))
|
||||
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
||||
// FIXME ':'/fsz not needed in new firmwares - it's handled internally
|
||||
if (l.font.includes(":")) {
|
||||
var f = l.font.split(":");
|
||||
l.font = f[0];
|
||||
l.fsz = f[1];
|
||||
}
|
||||
if (l.wrap) {
|
||||
l._h = l._w = 0;
|
||||
} else {
|
||||
|
|
|
|||