Merge branch 'master' into patch-5
|
|
@ -0,0 +1,4 @@
|
|||
0.01: Release
|
||||
0.02: Rename app
|
||||
0.03: Add type "clock"
|
||||
0.04: changed update cylce, when locked
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Analog Clock
|
||||
|
||||
## Features
|
||||
|
||||
* second hand
|
||||
* date
|
||||
* battery percantage
|
||||
* no widgets
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -0,0 +1,125 @@
|
|||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||
let zahlpos=[];
|
||||
let unlock = false;
|
||||
|
||||
function zeiger(len,dia,tim){
|
||||
const x =c.x+ Math.cos(tim)*len/2,
|
||||
y =c.y + Math.sin(tim)*len/2,
|
||||
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
|
||||
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
|
||||
return pol;
|
||||
|
||||
}
|
||||
|
||||
function draw(){
|
||||
const d=new Date();
|
||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||
//draw black rectangle in the middle to clear screen from scale and hands
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
|
||||
if(h>12){
|
||||
h=h-12;
|
||||
}
|
||||
//calculates the position of the minute, second and hour hand
|
||||
h=2*Math.PI/12*(h+m/60)-Math.PI/2;
|
||||
//more accurate
|
||||
//m=2*Math.PI/60*(m+s/60)-Math.PI/2;
|
||||
m=2*Math.PI/60*(m)-Math.PI/2;
|
||||
|
||||
s=2*Math.PI/60*s-Math.PI/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",10);
|
||||
let dateStr = " "+require("locale").date(d)+" ";
|
||||
g.drawString(dateStr, c.x, c.y+20, true);
|
||||
// g.drawString(d.getDate(),1.4*c.x,c.y,true);
|
||||
g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true);
|
||||
drawlet();
|
||||
//g.setColor(1,0,0);
|
||||
const hz = zeiger(100,5,h);
|
||||
g.fillPoly(hz,true);
|
||||
// g.setColor(1,1,1);
|
||||
const minz = zeiger(150,5,m);
|
||||
g.fillPoly(minz,true);
|
||||
if (unlock){
|
||||
const sekz = zeiger(150,2,s);
|
||||
g.fillPoly(sekz,true);
|
||||
}
|
||||
g.fillCircle(c.x,c.y,4);
|
||||
|
||||
|
||||
|
||||
}
|
||||
//draws the scale once the app is startet
|
||||
function drawScale(){
|
||||
for(let i=-14;i<47;i++){
|
||||
const win=i*2*Math.PI/60;
|
||||
let d=2;
|
||||
if(i%5==0){d=5;}
|
||||
g.fillPoly(zeiger(300,d,win),true);
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
}
|
||||
|
||||
//draws the numbers on the screen
|
||||
|
||||
function drawlet(){
|
||||
g.setFont("Vector",20);
|
||||
for(let i = 0;i<12;i++){
|
||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
|
||||
}
|
||||
}
|
||||
//calcultes the Position of the numbers when app starts and saves them in an array
|
||||
function setlet(){
|
||||
let sk=1;
|
||||
for(let i=-10;i<50;i+=5){
|
||||
let win=i*2*Math.PI/60;
|
||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||
if(sk==3){xsk-=10;}
|
||||
if(sk==6){ysk-=10;}
|
||||
if(sk==9){xsk+=10;}
|
||||
if(sk==12){ysk+=10;}
|
||||
if(sk==10){xsk+=3;}
|
||||
zahlpos.push([sk,xsk,ysk]);
|
||||
sk+=1;
|
||||
}
|
||||
}
|
||||
setlet();
|
||||
// Clear the screen once, at startup
|
||||
g.setBgColor(0,0,0);
|
||||
g.clear();
|
||||
drawScale();
|
||||
draw();
|
||||
|
||||
let secondInterval= setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}else{
|
||||
}
|
||||
});
|
||||
Bangle.on('lock',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (!on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
unlock = true;
|
||||
draw(); // draw immediately
|
||||
}else{
|
||||
secondInterval = setInterval(draw, 60000);
|
||||
unlock = false;
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIEBoUAiAKCgUCBQUEColEAYUQhAmKCwgeCAAcCgEDjwEBkEAg8TBocNgYFDh8GAYMDxkPjEA8EAwkHJgIcBAoPfAoYWCBYYFIgfvAoX4FYRJEAp9gAomYNAOAArPwAogAC4AFiRoIFJLgIFJuADCg//Q4U//4FDj4FEAAV4Aoi0CSxBsCA=="))
|
||||
|
|
@ -1 +1,3 @@
|
|||
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)
|
||||
0.02: The app is now a clock, the data is greyed after the connection is lost (2021/12/22)
|
||||
0.03: Set the Awair's IP directly on the webpage (2021/12/27)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awai
|
|||
* What you need:
|
||||
* A BangleJS 2
|
||||
* An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature)
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrive the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrieve the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* How to get started
|
||||
* Open awair_to_bangle.html with a text/code editor and input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
* Launch the Awair Monitor app on your BangleJS
|
||||
* Open awair_to_bangle.html on Chrome and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Open awair_to_bangle.html on Chrome (desktop or Android), input the IP address of your Awair device, and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Once connected to the watch with the app running, the watch app is updated once per second
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ var bt_temp_history = new Array(10).fill(0);
|
|||
|
||||
var internal_last_update = -1;
|
||||
|
||||
var display_frozen = false;
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
||||
|
|
@ -47,14 +49,8 @@ function draw() {
|
|||
g.drawString("Humi", 125, 100);
|
||||
g.drawString("Temp", 160, 100);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
if (last_update != bt_last_update) {
|
||||
display_frozen = false;
|
||||
last_update = bt_last_update;
|
||||
internal_last_update = last_update;
|
||||
if (last_update % 10 == 0) {
|
||||
|
|
@ -68,12 +64,25 @@ function draw() {
|
|||
|
||||
if (internal_last_update == -1) {
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
} else if ((internal_last_update > last_update + 5) && (internal_last_update < last_update + 60)) {
|
||||
g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
display_frozen = true;
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
}
|
||||
|
||||
if (display_frozen) { g.setColor("#888"); }
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (display_frozen) { g.setColor("#888"); }
|
||||
|
||||
// max height = 32
|
||||
g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
|
||||
g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
|
||||
|
|
@ -91,6 +100,7 @@ function draw() {
|
|||
}
|
||||
|
||||
// init
|
||||
Bangle.setUI("clock");
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
// Don't forget to enable the Local API on your Awair before using this
|
||||
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
|
||||
const awair_ip_1 = "192.168.2.2"; // <- INPUT YOUR AWAIR IP ADDRESS HERE
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
var is_chart_started = false;
|
||||
|
||||
window.onload = function() {
|
||||
function initChart() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
|
|
@ -24,6 +24,8 @@ window.onload = function() {
|
|||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
var awair_ip_1 = document.getElementById('inputawairip').value;
|
||||
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
|
|
@ -105,11 +107,12 @@ window.onload = function() {
|
|||
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||
let last_update = dataPoints_1['temp'].length-1;
|
||||
if (is_connected && bt_connection.isOpen) {
|
||||
|
||||
if (is_connected && bt_connection && bt_connection.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
||||
|
||||
console.log("Sent data through Bluetooth");
|
||||
} else if (is_connected && !bt_connection.isOpen) {
|
||||
} else if (is_connected && bt_connection && !bt_connection.isOpen) {
|
||||
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
||||
reconnect_counter--;
|
||||
|
||||
|
|
@ -131,7 +134,6 @@ window.onload = function() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
setTimeout(function(){updateChart()}, 1000);
|
||||
});
|
||||
}
|
||||
|
|
@ -148,10 +150,16 @@ function connectBT() {
|
|||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
if (!is_chart_started) {
|
||||
initChart();
|
||||
is_chart_started = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function disconnectBT() {
|
||||
console.log("Disconnect Bluetooth button pressed. bt_connection value below.")
|
||||
console.log(bt_connection);
|
||||
if (is_connected && bt_connection) {
|
||||
bt_connection.close();
|
||||
is_connected = false;
|
||||
|
|
@ -167,23 +175,21 @@ function disconnectBT() {
|
|||
|
||||
<p style="color: #F7FAFC">
|
||||
<b>How to use</b>
|
||||
<br/><br/>
|
||||
<br><br>
|
||||
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
<br/><br/>
|
||||
Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
<br/><br/>
|
||||
Step 3: Launch the Awair Monitor app on your BangleJS
|
||||
<br/><br/>
|
||||
Step 4: Click "Connect BangleJS"
|
||||
<br/><br/>
|
||||
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
|
||||
<br><br>
|
||||
Step 2: Launch the Awair Monitor app on your BangleJS
|
||||
<br><br>
|
||||
Step 3: Input your Awair IP address and click the Connect button:
|
||||
<input type="text" id="inputawairip" value="192.168.2.1">
|
||||
<input type="button" value="Connect BangleJS" onclick="connectBT();">
|
||||
<br><br>
|
||||
Step 4: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the Bluetooth logs
|
||||
<br><br>
|
||||
Step 5: Once you are done, click the Disconnect button to properly close the Blutooth connection
|
||||
<center><button onclick="disconnectBT();">Disconnect BangleJS</button></center>
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<button onclick="connectBT();">Connect BangleJS</button>
|
||||
<button onclick="disconnectBT();">Disconnect BangleJS</button>
|
||||
</center>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
|
|
@ -192,4 +198,476 @@ Step 5: Optionally, open the web inspector's console (Right click > Inspector >
|
|||
<div id="chartContainer_humidity" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
</body>
|
||||
</html>
|
||||
</html>(buf));
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function handleQueue() {
|
||||
if (!queue.length) return;
|
||||
var q = queue.shift();
|
||||
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
||||
if (q.type == "write") puck.write(q.data, q.callback, q.callbackNewline);
|
||||
else log(1,"Unknown queue item "+JSON.stringify(q));
|
||||
}
|
||||
|
||||
function connect(callback) {
|
||||
if (!checkIfSupported()) return;
|
||||
|
||||
var connection = {
|
||||
on : function(evt,cb) { this["on"+evt]=cb; },
|
||||
emit : function(evt,data) { if (this["on"+evt]) this["on"+evt](data); },
|
||||
isOpen : false,
|
||||
isOpening : true,
|
||||
txInProgress : false
|
||||
};
|
||||
var btServer = undefined;
|
||||
var btService;
|
||||
var connectionDisconnectCallback;
|
||||
var txCharacteristic;
|
||||
var rxCharacteristic;
|
||||
var txDataQueue = [];
|
||||
var flowControlXOFF = false;
|
||||
var chunkSize = DEFAULT_CHUNKSIZE;
|
||||
|
||||
connection.close = function() {
|
||||
connection.isOpening = false;
|
||||
if (connection.isOpen) {
|
||||
connection.isOpen = false;
|
||||
connection.emit('close');
|
||||
} else {
|
||||
if (callback) callback(null);
|
||||
}
|
||||
if (btServer) {
|
||||
btServer.disconnect();
|
||||
btServer = undefined;
|
||||
txCharacteristic = undefined;
|
||||
rxCharacteristic = undefined;
|
||||
chunkSize = DEFAULT_CHUNKSIZE;
|
||||
}
|
||||
};
|
||||
|
||||
connection.write = function(data, callback) {
|
||||
if (data) txDataQueue.push({data:data,callback:callback,maxLength:data.length});
|
||||
if (connection.isOpen && !connection.txInProgress) writeChunk();
|
||||
|
||||
function writeChunk() {
|
||||
if (flowControlXOFF) { // flow control - try again later
|
||||
setTimeout(writeChunk, 50);
|
||||
return;
|
||||
}
|
||||
var chunk;
|
||||
if (!txDataQueue.length) {
|
||||
puck.writeProgress();
|
||||
return;
|
||||
}
|
||||
var txItem = txDataQueue[0];
|
||||
puck.writeProgress(txItem.maxLength - txItem.data.length, txItem.maxLength);
|
||||
if (txItem.data.length <= chunkSize) {
|
||||
chunk = txItem.data;
|
||||
txItem.data = undefined;
|
||||
} else {
|
||||
chunk = txItem.data.substr(0,chunkSize);
|
||||
txItem.data = txItem.data.substr(chunkSize);
|
||||
}
|
||||
connection.txInProgress = true;
|
||||
log(2, "Sending "+ JSON.stringify(chunk));
|
||||
txCharacteristic.writeValue(str2ab(chunk)).then(function() {
|
||||
log(3, "Sent");
|
||||
if (!txItem.data) {
|
||||
txDataQueue.shift(); // remove this element
|
||||
if (txItem.callback)
|
||||
txItem.callback();
|
||||
}
|
||||
connection.txInProgress = false;
|
||||
writeChunk();
|
||||
}).catch(function(error) {
|
||||
log(1, 'SEND ERROR: ' + error);
|
||||
txDataQueue = [];
|
||||
connection.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
navigator.bluetooth.requestDevice({
|
||||
filters:[
|
||||
{ namePrefix: 'Puck.js' },
|
||||
{ namePrefix: 'Pixl.js' },
|
||||
{ namePrefix: 'MDBT42Q' },
|
||||
{ namePrefix: 'RuuviTag' },
|
||||
{ namePrefix: 'iTracker' },
|
||||
{ namePrefix: 'Thingy' },
|
||||
{ namePrefix: 'Espruino' },
|
||||
{ services: [ NORDIC_SERVICE ] }
|
||||
], optionalServices: [ NORDIC_SERVICE ]}).then(function(device) {
|
||||
log(1, 'Device Name: ' + device.name);
|
||||
log(1, 'Device ID: ' + device.id);
|
||||
// Was deprecated: Should use getPrimaryServices for this in future
|
||||
//log('BT> Device UUIDs: ' + device.uuids.join('\n' + ' '.repeat(21)));
|
||||
device.addEventListener('gattserverdisconnected', function() {
|
||||
log(1, "Disconnected (gattserverdisconnected)");
|
||||
connection.close();
|
||||
});
|
||||
connection.device = device;
|
||||
connection.reconnect(callback);
|
||||
}).catch(function(error) {
|
||||
log(1, 'ERROR: ' + error);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
connection.reconnect = function(callback) {
|
||||
connection.device.gatt.connect().then(function(server) {
|
||||
log(1, "Connected");
|
||||
btServer = server;
|
||||
return server.getPrimaryService(NORDIC_SERVICE);
|
||||
}).then(function(service) {
|
||||
log(2, "Got service");
|
||||
btService = service;
|
||||
return btService.getCharacteristic(NORDIC_RX);
|
||||
}).then(function (characteristic) {
|
||||
rxCharacteristic = characteristic;
|
||||
log(2, "RX characteristic:"+JSON.stringify(rxCharacteristic));
|
||||
rxCharacteristic.addEventListener('characteristicvaluechanged', function(event) {
|
||||
var dataview = event.target.value;
|
||||
var data = ab2str(dataview.buffer);
|
||||
if (data.length > chunkSize) {
|
||||
log(2, "Received packet of length "+data.length+", increasing chunk size");
|
||||
chunkSize = data.length;
|
||||
}
|
||||
if (puck.flowControl) {
|
||||
for (var i=0;i<data.length;i++) {
|
||||
var ch = data.charCodeAt(i);
|
||||
var remove = true;
|
||||
if (ch==19) {// XOFF
|
||||
log(2,"XOFF received => pause upload");
|
||||
flowControlXOFF = true;
|
||||
} else if (ch==17) {// XON
|
||||
log(2,"XON received => resume upload");
|
||||
flowControlXOFF = false;
|
||||
} else
|
||||
remove = false;
|
||||
if (remove) { // remove character
|
||||
data = data.substr(0,i-1)+data.substr(i+1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
log(3, "Received "+JSON.stringify(data));
|
||||
connection.emit('data', data);
|
||||
});
|
||||
return rxCharacteristic.startNotifications();
|
||||
}).then(function() {
|
||||
return btService.getCharacteristic(NORDIC_TX);
|
||||
}).then(function (characteristic) {
|
||||
txCharacteristic = characteristic;
|
||||
log(2, "TX characteristic:"+JSON.stringify(txCharacteristic));
|
||||
}).then(function() {
|
||||
connection.txInProgress = false;
|
||||
connection.isOpen = true;
|
||||
connection.isOpening = false;
|
||||
isBusy = false;
|
||||
queue = [];
|
||||
callback(connection);
|
||||
connection.emit('open');
|
||||
// if we had any writes queued, do them now
|
||||
connection.write();
|
||||
}).catch(function(error) {
|
||||
log(1, 'ERROR: ' + error);
|
||||
connection.close();
|
||||
});
|
||||
};
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------
|
||||
var connection;
|
||||
/* convenience function... Write data, call the callback with data:
|
||||
callbackNewline = false => if no new data received for ~0.2 sec
|
||||
callbackNewline = true => after a newline */
|
||||
function write(data, callback, callbackNewline) {
|
||||
if (!checkIfSupported()) return;
|
||||
|
||||
let result;
|
||||
/// If there wasn't a callback function, then promisify
|
||||
if (typeof callback !== 'function') {
|
||||
callbackNewline = callback;
|
||||
|
||||
result = new Promise((resolve, reject) => callback = (value, err) => {
|
||||
if (err) reject(err);
|
||||
else resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
if (isBusy) {
|
||||
log(3, "Busy - adding Puck.write to queue");
|
||||
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
||||
return result;
|
||||
}
|
||||
|
||||
var cbTimeout;
|
||||
function onWritten() {
|
||||
if (callbackNewline) {
|
||||
connection.cb = function(d) {
|
||||
var newLineIdx = connection.received.indexOf("\n");
|
||||
if (newLineIdx>=0) {
|
||||
var l = connection.received.substr(0,newLineIdx);
|
||||
connection.received = connection.received.substr(newLineIdx+1);
|
||||
connection.cb = undefined;
|
||||
if (cbTimeout) clearTimeout(cbTimeout);
|
||||
cbTimeout = undefined;
|
||||
if (callback)
|
||||
callback(l);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
}
|
||||
};
|
||||
}
|
||||
// wait for any received data if we have a callback...
|
||||
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
||||
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
||||
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
||||
cbTimeout = setTimeout(function timeout() {
|
||||
cbTimeout = undefined;
|
||||
if (maxTime) maxTime--;
|
||||
if (maxDataTime) maxDataTime--;
|
||||
if (connection.hadData) maxDataTime=dataWaitTime;
|
||||
if (maxDataTime && maxTime) {
|
||||
cbTimeout = setTimeout(timeout, 100);
|
||||
} else {
|
||||
connection.cb = undefined;
|
||||
if (callback)
|
||||
callback(connection.received);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
connection.received = "";
|
||||
}
|
||||
connection.hadData = false;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
if (connection && (connection.isOpen || connection.isOpening)) {
|
||||
if (!connection.txInProgress) connection.received = "";
|
||||
isBusy = true;
|
||||
connection.write(data, onWritten);
|
||||
return result
|
||||
}
|
||||
|
||||
connection = connect(function(puck) {
|
||||
if (!puck) {
|
||||
connection = undefined;
|
||||
if (callback) callback(null);
|
||||
return;
|
||||
}
|
||||
connection.received = "";
|
||||
connection.on('data', function(d) {
|
||||
connection.received += d;
|
||||
connection.hadData = true;
|
||||
if (connection.cb) connection.cb(d);
|
||||
});
|
||||
connection.on('close', function(d) {
|
||||
connection = undefined;
|
||||
});
|
||||
isBusy = true;
|
||||
connection.write(data, onWritten);
|
||||
});
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var puck = {
|
||||
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||
debug : 1,
|
||||
/// Should we use flow control? Default is true
|
||||
flowControl : true,
|
||||
/// Used internally to write log information - you can replace this with your own function
|
||||
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
||||
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||||
writeProgress : function (charsSent, charsTotal) {
|
||||
//console.log(charsSent + "/" + charsTotal);
|
||||
},
|
||||
/** Connect to a new device - this creates a separate
|
||||
connection to the one `write` and `eval` use. */
|
||||
connect : connect,
|
||||
/// Write to Puck.js and call back when the data is written. Creates a connection if it doesn't exist
|
||||
write : write,
|
||||
/// Evaluate an expression and call cb with the result. Creates a connection if it doesn't exist
|
||||
eval : function(expr, cb) {
|
||||
|
||||
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
||||
.then(function (d) {
|
||||
try {
|
||||
return JSON.parse(d);
|
||||
} catch (e) {
|
||||
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
||||
return Promise.reject(d);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (cb) {
|
||||
return void response.then(cb, (err) => cb(null, err));
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
|
||||
},
|
||||
/// Write the current time to the Puck
|
||||
setTime : function(cb) {
|
||||
var d = new Date();
|
||||
var cmd = 'setTime('+(d.getTime()/1000)+');';
|
||||
// in 1v93 we have timezones too
|
||||
cmd += 'if (E.setTimeZone) E.setTimeZone('+d.getTimezoneOffset()/-60+');\n';
|
||||
write(cmd, cb);
|
||||
},
|
||||
/// Did `write` and `eval` manage to create a connection?
|
||||
isConnected : function() {
|
||||
return connection!==undefined;
|
||||
},
|
||||
/// get the connection used by `write` and `eval`
|
||||
getConnection : function() {
|
||||
return connection;
|
||||
},
|
||||
/// Close the connection used by `write` and `eval`
|
||||
close : function() {
|
||||
if (connection)
|
||||
connection.close();
|
||||
},
|
||||
/** Utility function to fade out everything on the webpage and display
|
||||
a window saying 'Click to continue'. When clicked it'll disappear and
|
||||
'callback' will be called. This is useful because you can't initialise
|
||||
Web Bluetooth unless you're doing so in response to a user input.*/
|
||||
modal : function(callback) {
|
||||
var e = document.createElement('div');
|
||||
e.style = 'position:absolute;top:0px;left:0px;right:0px;bottom:0px;opacity:0.5;z-index:100;background:black;';
|
||||
e.innerHTML = '<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-family: Sans-Serif;font-size:400%;color:white;">Click to Continue...</div>';
|
||||
e.onclick = function(evt) {
|
||||
callback();
|
||||
evt.preventDefault();
|
||||
document.body.removeChild(e);
|
||||
};
|
||||
document.body.appendChild(e);
|
||||
}
|
||||
};
|
||||
return puck;
|
||||
}));
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// Don't forget to enable the Local API on your Awair before using this
|
||||
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
var is_chart_started = false;
|
||||
|
||||
function initChart() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
var chart_temperature;
|
||||
var chart_humidity;
|
||||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
var awair_ip_1 = document.getElementById('inputawairip').value;
|
||||
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
|
||||
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
||||
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
||||
});
|
||||
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
||||
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
||||
});
|
||||
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
||||
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
||||
});
|
||||
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
||||
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
||||
});
|
||||
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
||||
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
||||
});
|
||||
|
||||
chart_co2.set("backgroundColor", "#1A202C");
|
||||
chart_voc.set("backgroundColor", "#1A202C");
|
||||
chart_pm.set("backgroundColor", "#1A202C");
|
||||
chart_humidity.set("backgroundColor", "#1A202C");
|
||||
chart_temperature.set("backgroundColor", "#1A202C");
|
||||
|
||||
updateChart();
|
||||
});
|
||||
|
||||
function updateChart() {
|
||||
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||
$.each(data, function(key, value){
|
||||
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||
});
|
||||
|
||||
posx++;
|
||||
chart_co2.render();
|
||||
chart_voc.render();
|
||||
chart_pm.render();
|
||||
chart_temperature.render();
|
||||
chart_humidity.render();
|
||||
|
||||
chart_co2.title.set("text", "CO2 level (ppm)");
|
||||
chart_voc.title.set("text", "VOC level (ppb)");
|
||||
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
||||
chart_humidity.title.set("text", "Humidity level (%)");
|
||||
chart_temperature.title.set("text", "Temperature level (°C)");
|
||||
|
||||
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
||||
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
||||
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
||||
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||
let last_update = dataPoints_1['temp'].length-1;
|
||||
|
||||
if (is_connected && bt_connection && bt_connection.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "bluetoothdock",
|
||||
"name": "Bluetooth Dock",
|
||||
"shortName":"Dock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
|
||||
"tags": "bluetooth",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bluetoothdock.app.js","url":"app.js"},
|
||||
{"name":"bluetoothdock.boot.js","url":"boot.js"},
|
||||
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: fix precision rounding issue + no reset when equals pressed
|
||||
0.03: Support for different screen sizes and touchscreen
|
||||
0.04: Display current operation on LHS
|
||||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
g.clear();
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
var DEFAULT_SELECTION = '5';
|
||||
var DEFAULT_SELECTION_NUMBERS = '5', DEFAULT_SELECTION_OPERATORS = '=', DEFAULT_SELECTION_SPECIALS = 'R';
|
||||
var RIGHT_MARGIN = 20;
|
||||
var RESULT_HEIGHT = 40;
|
||||
var COLORS = {
|
||||
|
|
@ -18,97 +18,45 @@ var COLORS = {
|
|||
SPECIAL: ['#65686C', '#7F8183']
|
||||
};
|
||||
|
||||
var keys = {
|
||||
'0': {
|
||||
xy: [0, 200, 120, 240],
|
||||
trbl: '2.00'
|
||||
},
|
||||
'.': {
|
||||
xy: [120, 200, 180, 240],
|
||||
trbl: '3=.0'
|
||||
},
|
||||
'=': {
|
||||
xy: [181, 200, 240, 240],
|
||||
trbl: '+==.',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'1': {
|
||||
xy: [0, 160, 60, 200],
|
||||
trbl: '4201'
|
||||
},
|
||||
'2': {
|
||||
xy: [60, 160, 120, 200],
|
||||
trbl: '5301'
|
||||
},
|
||||
'3': {
|
||||
xy: [120, 160, 180, 200],
|
||||
trbl: '6+.2'
|
||||
},
|
||||
'+': {
|
||||
xy: [181, 160, 240, 200],
|
||||
trbl: '-+=3',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'4': {
|
||||
xy: [0, 120, 60, 160],
|
||||
trbl: '7514'
|
||||
},
|
||||
'5': {
|
||||
xy: [60, 120, 120, 160],
|
||||
trbl: '8624'
|
||||
},
|
||||
'6': {
|
||||
xy: [120, 120, 180, 160],
|
||||
trbl: '9-35'
|
||||
},
|
||||
'-': {
|
||||
xy: [181, 120, 240, 160],
|
||||
trbl: '*-+6',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'7': {
|
||||
xy: [0, 80, 60, 120],
|
||||
trbl: 'R847'
|
||||
},
|
||||
'8': {
|
||||
xy: [60, 80, 120, 120],
|
||||
trbl: 'N957'
|
||||
},
|
||||
'9': {
|
||||
xy: [120, 80, 180, 120],
|
||||
trbl: '%*68'
|
||||
},
|
||||
'*': {
|
||||
xy: [181, 80, 240, 120],
|
||||
trbl: '/*-9',
|
||||
color: COLORS.OPERATOR
|
||||
},
|
||||
'R': {
|
||||
xy: [0, 40, 60, 79],
|
||||
trbl: 'RN7R',
|
||||
color: COLORS.SPECIAL,
|
||||
val: 'AC'
|
||||
},
|
||||
'N': {
|
||||
xy: [60, 40, 120, 79],
|
||||
trbl: 'N%8R',
|
||||
color: COLORS.SPECIAL,
|
||||
val: '+/-'
|
||||
},
|
||||
'%': {
|
||||
xy: [120, 40, 180, 79],
|
||||
trbl: '%/9N',
|
||||
color: COLORS.SPECIAL
|
||||
},
|
||||
'/': {
|
||||
xy: [181, 40, 240, 79],
|
||||
trbl: '//*%',
|
||||
color: COLORS.OPERATOR
|
||||
}
|
||||
var KEY_AREA = [0, RESULT_HEIGHT, g.getWidth(), g.getHeight()];
|
||||
|
||||
var screen, screenColor;
|
||||
var globalGrid = [4, 5];
|
||||
var swipeEnabled;
|
||||
|
||||
var numbersGrid = [3, 4];
|
||||
var numbers = {
|
||||
'0': {grid: [1, 3], globalGrid: [1, 4], trbl: '2.00'},
|
||||
'.': {grid: [2, 3], globalGrid: [2, 4], trbl: '3=.0'},
|
||||
'1': {grid: [0, 2], globalGrid: [0, 3], trbl: '4201'},
|
||||
'2': {grid: [1, 2], globalGrid: [1, 3], trbl: '5301'},
|
||||
'3': {grid: [2, 2], globalGrid: [2, 3], trbl: '6+.2'},
|
||||
'4': {grid: [0, 1], globalGrid: [0, 2], trbl: '7514'},
|
||||
'5': {grid: [1, 1], globalGrid: [1, 2], trbl: '8624'},
|
||||
'6': {grid: [2, 1], globalGrid: [2, 2], trbl: '9-35'},
|
||||
'7': {grid: [0, 0], globalGrid: [0, 1], trbl: 'R847'},
|
||||
'8': {grid: [1, 0], globalGrid: [1, 1], trbl: 'N957'},
|
||||
'9': {grid: [2, 0], globalGrid: [2, 1], trbl: '%*68'},
|
||||
};
|
||||
|
||||
var selected = DEFAULT_SELECTION;
|
||||
var prevSelected = DEFAULT_SELECTION;
|
||||
var operatorsGrid = [2, 3];
|
||||
var operators = {
|
||||
'+': {grid: [0, 0], globalGrid: [3, 3], trbl: '-+=3'},
|
||||
'-': {grid: [1, 0], globalGrid: [3, 2], trbl: '*-+6'},
|
||||
'*': {grid: [0, 1], globalGrid: [3, 1], trbl: '/*-9'},
|
||||
'/': {grid: [1, 1], globalGrid: [3, 0], trbl: '//*%'},
|
||||
'=': {grid: [1, 2], globalGrid: [3, 4], trbl: '+==.'},
|
||||
};
|
||||
|
||||
var specialsGrid = [2, 2];
|
||||
var specials = {
|
||||
'R': {grid: [0, 0], globalGrid: [0, 0], trbl: 'RN7R', val: 'AC'},
|
||||
'N': {grid: [1, 0], globalGrid: [1, 0], trbl: 'N%8R', val: '+/-'},
|
||||
'%': {grid: [0, 1], globalGrid: [2, 0], trbl: '%/9N'},
|
||||
};
|
||||
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevNumber = null;
|
||||
var currNumber = null;
|
||||
var operator = null;
|
||||
|
|
@ -116,6 +64,27 @@ var results = null;
|
|||
var isDecimal = false;
|
||||
var hasPressedEquals = false;
|
||||
|
||||
function prepareScreen(screen, grid, defaultColor) {
|
||||
for (var k in screen) {
|
||||
if (screen.hasOwnProperty(k)) {
|
||||
screen[k].color = screen[k].color || defaultColor;
|
||||
var position = [];
|
||||
var xGrid = (KEY_AREA[2]-KEY_AREA[0])/grid[0];
|
||||
var yGrid = (KEY_AREA[3]-KEY_AREA[1])/grid[1];
|
||||
if (swipeEnabled) {
|
||||
position[0] = KEY_AREA[0]+xGrid*screen[k].grid[0];
|
||||
position[1] = KEY_AREA[1]+yGrid*screen[k].grid[1];
|
||||
} else {
|
||||
position[0] = KEY_AREA[0]+xGrid*screen[k].globalGrid[0];
|
||||
position[1] = KEY_AREA[1]+yGrid*screen[k].globalGrid[1];
|
||||
}
|
||||
position[2] = position[0]+xGrid-1;
|
||||
position[3] = position[1]+yGrid-1;
|
||||
screen[k].xy = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawKey(name, k, selected) {
|
||||
var rMargin = 0;
|
||||
var bMargin = 0;
|
||||
|
|
@ -142,6 +111,56 @@ function drawKey(name, k, selected) {
|
|||
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
|
||||
}
|
||||
|
||||
function drawKeys() {
|
||||
g.setColor(screenColor[0]);
|
||||
g.fillRect(KEY_AREA[0], KEY_AREA[1], KEY_AREA[2], KEY_AREA[3]);
|
||||
for (var k in screen) {
|
||||
if (screen.hasOwnProperty(k)) {
|
||||
drawKey(k, screen[k], k == selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
function drawGlobal() {
|
||||
screen = {};
|
||||
screenColor = COLORS.DEFAULT;
|
||||
prepareScreen(numbers, globalGrid, COLORS.DEFAULT);
|
||||
for (var k in numbers) {
|
||||
screen[k] = numbers[k];
|
||||
}
|
||||
prepareScreen(operators, globalGrid, COLORS.OPERATOR);
|
||||
for (var k in operators) {
|
||||
screen[k] = operators[k];
|
||||
}
|
||||
prepareScreen(specials, globalGrid, COLORS.SPECIAL);
|
||||
for (var k in specials) {
|
||||
screen[k] = specials[k];
|
||||
}
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawNumbers() {
|
||||
screen = numbers;
|
||||
screenColor = COLORS.DEFAULT;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawOperators() {
|
||||
screen = operators;
|
||||
screenColor =COLORS.OPERATOR;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_OPERATORS;
|
||||
var prevSelected = DEFAULT_SELECTION_OPERATORS;
|
||||
}
|
||||
function drawSpecials() {
|
||||
screen = specials;
|
||||
screenColor = COLORS.SPECIAL;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_SPECIALS;
|
||||
var prevSelected = DEFAULT_SELECTION_SPECIALS;
|
||||
}
|
||||
|
||||
function getIntWithPrecision(x) {
|
||||
var xStr = x.toString();
|
||||
var xRadix = xStr.indexOf('.');
|
||||
|
|
@ -218,8 +237,8 @@ function displayOutput(num) {
|
|||
hasPressedEquals = false;
|
||||
prevNumber = null;
|
||||
operator = null;
|
||||
keys.R.val = 'AC';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'AC';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
g.setFont('Vector', 22);
|
||||
} else {
|
||||
// might not be a number due to display of dot "."
|
||||
|
|
@ -299,12 +318,12 @@ function buttonPress(val) {
|
|||
results = null;
|
||||
isDecimal = false;
|
||||
hasPressedEquals = false;
|
||||
if (keys.R.val == 'AC') {
|
||||
if (specials.R.val == 'AC') {
|
||||
prevNumber = null;
|
||||
operator = null;
|
||||
} else {
|
||||
keys.R.val = 'AC';
|
||||
drawKey('R', keys.R, true);
|
||||
specials.R.val = 'AC';
|
||||
drawKey('R', specials.R, true);
|
||||
}
|
||||
wasPressedEquals = false;
|
||||
hasPressedNumber = false;
|
||||
|
|
@ -331,10 +350,11 @@ function buttonPress(val) {
|
|||
case '+':
|
||||
calculatorLogic(val);
|
||||
hasPressedNumber = false;
|
||||
if (swipeEnabled) drawNumbers();
|
||||
break;
|
||||
case '.':
|
||||
keys.R.val = 'C';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'C';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
isDecimal = true;
|
||||
displayOutput(currNumber == null ? 0 + '.' : currNumber + '.');
|
||||
break;
|
||||
|
|
@ -348,8 +368,8 @@ function buttonPress(val) {
|
|||
hasPressedNumber = false;
|
||||
break;
|
||||
default:
|
||||
keys.R.val = 'C';
|
||||
drawKey('R', keys.R);
|
||||
specials.R.val = 'C';
|
||||
if (!swipeEnabled) drawKey('R', specials.R);
|
||||
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
|
||||
if (isDecimal) {
|
||||
currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val;
|
||||
|
|
@ -367,23 +387,31 @@ function buttonPress(val) {
|
|||
}
|
||||
|
||||
function moveDirection(d) {
|
||||
drawKey(selected, keys[selected]);
|
||||
drawKey(selected, screen[selected]);
|
||||
prevSelected = selected;
|
||||
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : keys[selected].trbl[d];
|
||||
drawKey(selected, keys[selected], true);
|
||||
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : screen[selected].trbl[d];
|
||||
drawKey(selected, screen[selected], true);
|
||||
}
|
||||
|
||||
if (global.BTN4) {
|
||||
if (process.env.HWVERSION==1) {
|
||||
setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
|
||||
setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
|
||||
setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
|
||||
swipeEnabled = false;
|
||||
drawGlobal();
|
||||
} else { // touchscreen?
|
||||
selected = "NONE";
|
||||
swipeEnabled = true;
|
||||
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
||||
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||
drawNumbers();
|
||||
Bangle.on('touch',(n,e)=>{
|
||||
for (var key in keys) {
|
||||
var r = keys[key].xy;
|
||||
for (var key in screen) {
|
||||
if (typeof screen[key] == "undefined") break;
|
||||
var r = screen[key].xy;
|
||||
if (e.x>=r[0] && e.y>=r[1] &&
|
||||
e.x<r[2] && e.y<r[3]) {
|
||||
//print("Press "+key);
|
||||
|
|
@ -391,19 +419,26 @@ if (global.BTN4) {
|
|||
}
|
||||
}
|
||||
});
|
||||
var lastX = 0, lastY = 0;
|
||||
Bangle.on('drag', (e) => {
|
||||
if (!e.b) {
|
||||
if (lastX > 50) { // right
|
||||
drawSpecials();
|
||||
} else if (lastX < -50) { // left
|
||||
drawOperators();
|
||||
} else if (lastY > 50) { // down
|
||||
drawNumbers();
|
||||
} else if (lastY < -50) { // up
|
||||
drawNumbers();
|
||||
}
|
||||
lastX = 0;
|
||||
lastY = 0;
|
||||
} else {
|
||||
lastX = lastX + e.dx;
|
||||
lastY = lastY + e.dy;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// rescale for non-240px screens
|
||||
if (g.getWidth()!=240) {
|
||||
RESULT_HEIGHT = RESULT_HEIGHT*g.getWidth()/240;
|
||||
for (var k in keys) {
|
||||
keys[k].xy = keys[k].xy.map(n => n*g.getWidth()/240);
|
||||
}
|
||||
}
|
||||
// draw keys
|
||||
for (var k in keys) {
|
||||
if (keys.hasOwnProperty(k)) {
|
||||
drawKey(k, keys[k], k == selected);
|
||||
}
|
||||
}
|
||||
|
||||
displayOutput(0);
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Make Bangle 2 compatible
|
||||
0.03: Add setting to start week on Sunday
|
||||
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
|
||||
0.05: Update calendar weekend colors for start on Sunday
|
||||
|
|
|
|||
|
|
@ -110,10 +110,19 @@ function drawCalendar(date) {
|
|||
g.clearRect(0, 0, maxX, maxY);
|
||||
g.setBgColor(bgColorMonth);
|
||||
g.clearRect(0, 0, maxX, headerH);
|
||||
if (settings.startOnSun){
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(0, headerH + rowH, colW, maxY);
|
||||
g.setBgColor(bgColorDow);
|
||||
g.clearRect(0, headerH, maxX, headerH + rowH);
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(colW * 6, headerH + rowH, maxX, maxY);
|
||||
} else {
|
||||
g.setBgColor(bgColorDow);
|
||||
g.clearRect(0, headerH, maxX, headerH + rowH);
|
||||
g.setBgColor(bgColorWeekend);
|
||||
g.clearRect(colW * 5, headerH + rowH, maxX, maxY);
|
||||
}
|
||||
for (let y = headerH; y < maxY; y += rowH) {
|
||||
g.drawLine(0, y, maxX, y);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.04: Change to 7 segment font, move to top widget bar
|
||||
Better auto-update behaviour, less RAM used
|
||||
0.05: Fix error running app on new firmwares (fix #1140)
|
||||
|
|
|
|||
|
|
@ -36,13 +36,7 @@ E.on('kill', () => {
|
|||
function showMenu() {
|
||||
const timerMenu = {
|
||||
'': {
|
||||
'title': 'Set timer',
|
||||
'predraw': function() {
|
||||
timerMenu.hours.value = settingsChronowid.hours;
|
||||
timerMenu.minutes.value = settingsChronowid.minutes;
|
||||
timerMenu.seconds.value = settingsChronowid.seconds;
|
||||
timerMenu.started.value = settingsChronowid.started;
|
||||
}
|
||||
'title': 'Set timer'
|
||||
},
|
||||
'< Back' : ()=>{load();},
|
||||
'Reset values': function() {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New clock
|
||||
0.02: Fix icon & add battery warn functionality
|
||||
0.03: Theming support & minor fixes
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ It shows besides time, date and day of week the following information:
|
|||
|
||||
## TODO
|
||||
* Show weather information
|
||||
* Configure which information to show in each circle
|
||||
* Configure visibility of widgets
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
|
|
|||
|
|
@ -7,19 +7,23 @@ const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbB
|
|||
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
|
||||
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
|
||||
|
||||
const SETTINGS_FILE = "circlesclock.json";
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE, 1) || {
|
||||
settings = require("Storage").readJSON("circlesclock.json", 1) || {
|
||||
'maxHR': 200,
|
||||
'stepGoal': 10000,
|
||||
'batteryWarn': 30
|
||||
};
|
||||
// Load step goal from pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||
}
|
||||
}
|
||||
|
||||
const colorFg = '#fff';
|
||||
const colorBg = '#000';
|
||||
const colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
const colorBg = g.theme.dark ? '#000' : '#fff';
|
||||
const colorGrey = '#808080';
|
||||
const colorRed = '#ff0000';
|
||||
const colorGreen = '#00ff00';
|
||||
|
|
@ -73,7 +77,7 @@ function drawSteps() {
|
|||
g.setColor(colorGrey);
|
||||
g.fillCircle(w1, h3, radiusOuter);
|
||||
|
||||
const stepGoal = settings.stepGoal;
|
||||
const stepGoal = settings.stepGoal || 10000;
|
||||
if (stepGoal > 0) {
|
||||
let percent = steps / stepGoal;
|
||||
if (stepGoal < steps) percent = 1;
|
||||
|
|
@ -97,8 +101,9 @@ function drawHeartRate() {
|
|||
g.setColor(colorGrey);
|
||||
g.fillCircle(w2, h3, radiusOuter);
|
||||
|
||||
if (hrtValue != undefined) {
|
||||
const percent = hrtValue / settings.maxHR;
|
||||
if (hrtValue != undefined && hrtValue > 0) {
|
||||
const minHR = 40;
|
||||
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
|
||||
drawGauge(w2, h3, percent, colorRed);
|
||||
}
|
||||
|
||||
|
|
@ -156,25 +161,26 @@ function radians(a) {
|
|||
return a * Math.PI / 180;
|
||||
}
|
||||
|
||||
|
||||
function drawGauge(cx, cy, percent, color) {
|
||||
let offset = 30;
|
||||
let end = 300;
|
||||
var i = 0;
|
||||
var r = radiusInner + 3;
|
||||
|
||||
if (percent <= 0) return;
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
var startrot = -offset;
|
||||
var endrot = startrot - ((end - offset) * percent);
|
||||
var endrot = startrot - ((end - offset) * percent) - 15;
|
||||
|
||||
g.setColor(color);
|
||||
|
||||
const size = 4;
|
||||
// draw gauge
|
||||
for (i = startrot; i > endrot; i -= 4) {
|
||||
for (i = startrot; i > endrot - size; i -= size) {
|
||||
x = cx + r * Math.sin(radians(i));
|
||||
y = cy + r * Math.cos(radians(i));
|
||||
g.fillCircle(x, y, 4);
|
||||
g.fillCircle(x, y, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,6 +207,10 @@ function getSteps() {
|
|||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
Bangle.setHRMPower(1, "watch");
|
||||
if (hrtValue == undefined) {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
}
|
||||
} else {
|
||||
Bangle.setHRMPower(0, "watch");
|
||||
}
|
||||
|
|
@ -218,6 +228,10 @@ Bangle.on('HRM', function(hrm) {
|
|||
//}
|
||||
});
|
||||
|
||||
Bangle.on('charging', function(charging) {
|
||||
drawBattery();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
/*
|
||||
|
|
@ -225,9 +239,11 @@ Bangle.loadWidgets();
|
|||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
for (let wd of WIDGETS) {
|
||||
if (typeof WIDGETS === "object") {
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
}
|
||||
}
|
||||
loadSettings();
|
||||
setInterval(draw, 60000);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# Colorful Analog Clock #
|
||||
|
||||
This app displays an analog clock with a colorful face. It considers the
|
||||
currently configured "theme" (and may therefore look different than shown in
|
||||
the screenshot on your watch depending on which theme you prefer).
|
||||
|
||||

|
||||
|
||||
This clock also acts as an example for the building blocks found in the author's
|
||||
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgZC/AB0BkmCCBsShEAiFBkARLwEBkGSgEECBQdBCIMSAwMYCBEKCgeQgE2gFgCA0C6Moiw1Dk0AhoRGikACIIHDgzECCI5ECg/gEYOACI+ggMti2ACIUkCIImCABARCAAMNCYIADgu0hdACI1twHACQm0rdoCI0BEYsoilRCI9sUgkBoG2rY1JgYjDCINLCI4fCa5ARHAAggCfYIjLgUB0AECCIy7BFwUCR4IKChIRFm1ACJAjGgwRL+AFDiwREI4YABn41FI4hxFn6IJPoh1B/AQFUI4ABh4RGUIsEyARC4ALEwAjECIl/CIkECIsICId+EQkkwEIA4gRDAAojBLwwHFexAADhaFDgETBw6UChdgA4cbCIKuGggCBCIMDCIkQCI8BEwMbCgMSAQIRGgGQQoQRCEYJrGAAMGIgZKDmBzIjARFTwpuHAARoGAAsMwQVCzARLAAPbtq5KAH4AEA"))
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -0,0 +1,247 @@
|
|||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
/**** updateClockFaceSize ****/
|
||||
|
||||
function updateClockFaceSize () {
|
||||
CenterX = ScreenWidth/2;
|
||||
CenterY = ScreenHeight/2;
|
||||
|
||||
outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
if (global.WIDGETS == null) { return; }
|
||||
|
||||
let WidgetLayouts = {
|
||||
tl:{ x:0, y:0, Direction:0 },
|
||||
tr:{ x:ScreenWidth-1, y:0, Direction:1 },
|
||||
bl:{ x:0, y:ScreenHeight-24, Direction:0 },
|
||||
br:{ x:ScreenWidth-1, y:ScreenHeight-24, Direction:1 }
|
||||
};
|
||||
|
||||
for (let Widget of WIDGETS) {
|
||||
let WidgetLayout = WidgetLayouts[Widget.area]; // reference, not copy!
|
||||
if (WidgetLayout == null) { continue; }
|
||||
|
||||
Widget.x = WidgetLayout.x - WidgetLayout.Direction * Widget.width;
|
||||
Widget.y = WidgetLayout.y;
|
||||
|
||||
WidgetLayout.x += Widget.width * (1-2*WidgetLayout.Direction);
|
||||
}
|
||||
|
||||
let x,y, dx,dy;
|
||||
let cx = CenterX, cy = CenterY, r = outerRadius, r2 = r*r;
|
||||
|
||||
x = WidgetLayouts.tl.x; y = WidgetLayouts.tl.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.tr.x; y = WidgetLayouts.tr.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.bl.x; y = WidgetLayouts.bl.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.br.x; y = WidgetLayouts.br.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
|
||||
}
|
||||
|
||||
updateClockFaceSize();
|
||||
|
||||
/**** custom version of Bangle.drawWidgets (does not clear the widget areas) ****/
|
||||
|
||||
Bangle.drawWidgets = function () {
|
||||
var w = g.getWidth(), h = g.getHeight();
|
||||
|
||||
var pos = {
|
||||
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
||||
tr:{x:w-1, y:0, r:1, c:0},
|
||||
bl:{x:0, y:h-24, r:0, c:0},
|
||||
br:{x:w-1, y:h-24, r:1, c:0}
|
||||
};
|
||||
|
||||
if (global.WIDGETS) {
|
||||
for (var wd of WIDGETS) {
|
||||
var p = pos[wd.area];
|
||||
if (!p) continue;
|
||||
|
||||
wd.x = p.x - p.r*wd.width;
|
||||
wd.y = p.y;
|
||||
|
||||
p.x += wd.width*(1-2*p.r);
|
||||
p.c++;
|
||||
}
|
||||
|
||||
g.reset(); // also loads the current theme
|
||||
|
||||
if (pos.tl.c || pos.tr.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
if (pos.bl.c || pos.br.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
try {
|
||||
for (wd of WIDGETS) {
|
||||
g.clearRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
||||
wd.draw(wd);
|
||||
}
|
||||
} catch (e) { print(e); }
|
||||
}
|
||||
};
|
||||
|
||||
let innerRadius = Math.min(CenterX,CenterY) * 0.8 - 14;
|
||||
|
||||
let HourHandLength = outerRadius * 0.5;
|
||||
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
|
||||
|
||||
let MinuteHandLength = outerRadius * 0.7;
|
||||
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
|
||||
|
||||
let SecondHandLength = outerRadius * 0.9;
|
||||
let SecondHandOffset = 6;
|
||||
|
||||
let twoPi = 2*Math.PI;
|
||||
let Pi = Math.PI;
|
||||
let halfPi = Math.PI/2;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
|
||||
let HourHandPolygon = [
|
||||
-halfHourHandWidth,halfHourHandWidth,
|
||||
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
||||
halfHourHandWidth,halfHourHandWidth,
|
||||
];
|
||||
|
||||
let MinuteHandPolygon = [
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
||||
halfMinuteHandWidth,halfMinuteHandWidth,
|
||||
];
|
||||
|
||||
/**** drawClockFace ****/
|
||||
|
||||
function drawClockFace () {
|
||||
for (let i = 0; i < 60; i++) {
|
||||
let Phi = i * twoPi/60;
|
||||
|
||||
let x = CenterX + outerRadius * sin(Phi);
|
||||
let y = CenterY - outerRadius * cos(Phi);
|
||||
|
||||
let Color = E.HSBtoRGB(i/60,1,1, true);
|
||||
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
||||
|
||||
g.fillCircle(x,y, 1);
|
||||
}
|
||||
|
||||
g.setFont('Vector', 20);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
let Phi = i * twoPi/12;
|
||||
|
||||
let Radius = innerRadius;
|
||||
if (i >= 10) { Radius -= 4; }
|
||||
|
||||
let x = CenterX + Radius * sin(Phi);
|
||||
let y = CenterY - Radius * cos(Phi);
|
||||
|
||||
let Color = E.HSBtoRGB(i/12,1,1, true);
|
||||
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
||||
|
||||
g.drawString(i == 0 ? '12' : '' + i, x,y);
|
||||
}
|
||||
}
|
||||
|
||||
/**** transforme polygon ****/
|
||||
|
||||
let transformedPolygon = new Array(HourHandPolygon.length);
|
||||
|
||||
function transformPolygon (originalPolygon, OriginX,OriginY, Phi) {
|
||||
let sPhi = sin(Phi), cPhi = cos(Phi), x,y;
|
||||
|
||||
for (let i = 0, l = originalPolygon.length; i < l; i+=2) {
|
||||
x = originalPolygon[i];
|
||||
y = originalPolygon[i+1];
|
||||
|
||||
transformedPolygon[i] = OriginX + x*cPhi + y*sPhi;
|
||||
transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi;
|
||||
}
|
||||
}
|
||||
|
||||
/**** draw clock hands ****/
|
||||
|
||||
function drawClockHands () {
|
||||
let now = new Date();
|
||||
|
||||
let Hours = now.getHours() % 12;
|
||||
let Minutes = now.getMinutes();
|
||||
let Seconds = now.getSeconds();
|
||||
|
||||
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
|
||||
let MinutesAngle = (Minutes/60) * twoPi - Pi;
|
||||
let SecondsAngle = (Seconds/60) * twoPi - Pi;
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
|
||||
|
||||
g.setColor(g.theme.fg2);
|
||||
g.drawLine(
|
||||
CenterX + SecondHandOffset*sPhi,
|
||||
CenterY - SecondHandOffset*cPhi,
|
||||
CenterX - SecondHandLength*sPhi,
|
||||
CenterY + SecondHandLength*cPhi
|
||||
);
|
||||
}
|
||||
|
||||
/**** refreshDisplay ****/
|
||||
|
||||
let Timer;
|
||||
function refreshDisplay () {
|
||||
g.clear(true); // also loads current theme
|
||||
|
||||
Bangle.drawWidgets();
|
||||
|
||||
drawClockFace();
|
||||
drawClockHands();
|
||||
|
||||
let Pause = 1000 - (Date.now() % 1000);
|
||||
Timer = setTimeout(refreshDisplay,Pause);
|
||||
}
|
||||
|
||||
setTimeout(refreshDisplay, 500); // enqueue first draw request
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
|
||||
refreshDisplay();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
Bangle.setUI('clock');
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andreas Rozek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# ColorWheel #
|
||||
|
||||
Choosing the right color on a Bangle.js 2 is not always easy. This little app therefore displays a wheel of rather good looking colors and reveals the associated color code by tapping on it
|
||||
|
||||

|
||||

|
||||
|
||||
Please note: you may also tap outside the wheel (for black) or inside it (for white).
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgYtr4cEiAQMku27ckyVICBUDlmy5YRBpMkCBEE7dtEYYRBwARHm3LEY3QCA0BEAIjFk3boARFhoOBEYs06dNCIogCEYoHCNAoOCEYlNEYUgCIcbEZekCIYODtgjHmgRHtu3///yQrESQfTBIYQBAAPNEYU2SQUGEYd/CIf9EYYRCDAYRF/4KBCIioBAwPfCAn/+wKC7QjEmgRG/xADZAIyBAwIQFCIgjFmoRKEYL4DRgQAFGoojKCIoje7Nly1ZEYLzCkojLNYIRCNZAjIkm/EZ4RH/1ZEYYRMWYpZDy4jJrARBggRBlrYG+VJEYgRBDIVfCIgtCy1QCIZhDCAfkKIW24ARBgJeBEYNbvoQBvOkCIQjDgE2EYYCD2gRCyQQCgEGEYYRBzVp0wRCyAREEY+2CIWAEY4OCEYoQDAAMbEY/SpMgCAkCjIjHzVJEQoABWYIjF7VICI8BBwYjDe4IAHSQ3QCBBuBLgQjCCBIAChu26dMCBgAdA"))
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
//----------------------------------------------------------------------------//
|
||||
//-- ColorWheel - draws a "wheel" of good looking colors --//
|
||||
//----------------------------------------------------------------------------//
|
||||
|
||||
let ColorList = [
|
||||
'#0000FF', '#8000FF', '#FF00FF', '#FF0080', '#FF0000', '#FF8000',
|
||||
'#FFFF00', '#80FF00', '#00FF00', '#00FF80', '#00FFFF', '#0080FF'
|
||||
];
|
||||
|
||||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
let innerRadius = outerRadius*0.5;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
let twoPi = 2*Math.PI, halfPi = Math.PI/2;
|
||||
|
||||
let DeltaPhi = twoPi/72;
|
||||
let Epsilon = 0.001;
|
||||
|
||||
g.clear();
|
||||
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(0,0, ScreenWidth,ScreenHeight);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
let Phi0 = i * twoPi/12, Phi1 = (i+1) * twoPi/12;
|
||||
|
||||
let Polygon = [];
|
||||
for (let Phi = Phi0; Phi <= Phi1+Epsilon; Phi += DeltaPhi) {
|
||||
Polygon.push(CenterX + outerRadius * sin(Phi));
|
||||
Polygon.push(CenterY - outerRadius * cos(Phi));
|
||||
}
|
||||
|
||||
for (let Phi = Phi1; Phi >= Phi0-Epsilon; Phi -= DeltaPhi) {
|
||||
Polygon.push(CenterX + innerRadius * sin(Phi));
|
||||
Polygon.push(CenterY - innerRadius * cos(Phi));
|
||||
}
|
||||
g.setColor(ColorList[i]);
|
||||
g.fillPoly(Polygon);
|
||||
}
|
||||
|
||||
g.setColor(1,1,1);
|
||||
g.fillCircle(CenterX,CenterY, innerRadius);
|
||||
|
||||
g.setFont12x20();
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(0,0,0);
|
||||
|
||||
g.drawString('Tap', CenterX,CenterY-20);
|
||||
g.drawString('on a', CenterX,CenterY);
|
||||
g.drawString('Color', CenterX,CenterY+20);
|
||||
|
||||
Bangle.on('touch', function (Button,Position) {
|
||||
Bangle.buzz();
|
||||
|
||||
let dx = Position.x - CenterX;
|
||||
let dy = Position.y - CenterY;
|
||||
|
||||
let Radius = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
let Color;
|
||||
switch (true) {
|
||||
case (Radius > outerRadius): Color = '#000000'; break;
|
||||
case (Radius < innerRadius): Color = '#FFFFFF'; break;
|
||||
default:
|
||||
let Phi = Math.atan2(dy,dx) + halfPi;
|
||||
if (Phi < 0) { Phi += twoPi; }
|
||||
if (Phi > twoPi) { Phi -= twoPi; }
|
||||
|
||||
let Index = Math.floor(12*Phi/twoPi);
|
||||
Color = ColorList[Index];
|
||||
}
|
||||
g.setColor(1,1,1);
|
||||
g.fillCircle(CenterX,CenterY, innerRadius);
|
||||
|
||||
g.setColor(0,0,0);
|
||||
g.drawString(Color, CenterX,CenterY);
|
||||
});
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/ABsH4/wv/H/EMlkMsF4hkYmEEwEwg0gmHCwEh4VAmPi/0j8Vkkcj4MjkU8kckocx4UEmPMoUQgkEEYNGnAFBnEGxFwg0Ek/jzFh8UEkEjkOikUcnFH8MiFIM3wnA8PisEwhnAkECAoMc4EYgk///3//n/Cl/AFYA="))
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
const digits = [
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVqlVVVVVVVVVVVVVaqqqqpVVVVVVVVVVWqqqqqqqVVVVVVVVVWqqqAKqqpVVVVVVVVaqgAAAACqpVVVVVVVaqAAAAAACqlVVVVVVaoAAAAAAACqVVVVVVaoAAAAAAAAKpVVVVVaoAAAAAAAAAqlVVVVaoAAAAAAAAACqVVVVWoAAAAAAAAAAKlVVVWoAAAAAAAAAAAqVVVWqAAAAAAAAAAAKpVVVqAAAAACgAAAAAqVVVagAAAAKqgAAAAKlVVagAAAAqqqgAAAAqVVWoAAAAKpaoAAAAKlVVqAAAAKlVagAAAAqVVqAAAACpVWoAAAAKlVagAAACpVVagAAACpVWoAAAAqVVWoAAAAqVVqAAAAqVVVagAAAKlVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVagAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVWoAAACpVqAAAAKlVVVqAAAAqVagAAAKlVVVagAAAKlWoAAACpVVVWoAAACpVqAAAAqVVVVqAAAAqVagAAAKlVVVagAAAKlWoAAACpVVVWoAAACpVqAAAAqVVVVqAAAAqVagAAACpVVVagAAAKlWoAAAAqVVVWoAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlWoAAAAqVVVagAAACpVqAAAAKlVVWoAAAAqVagAAACpVVVqAAAAKlVqAAAAqVVVagAAACpVagAAACpVVWoAAACpVWoAAAAqVVWoAAAAqVVqAAAAKlVVqAAAAKlVagAAAAqVVqAAAAKlVVqAAAAKpVqgAAACpVVagAAAAqqqgAAAAqVVVqAAAACqqgAAAAKlVVagAAAACqAAAAAKlVVWoAAAAAAAAAAACpVVVagAAAAAAAAAACpVVVWqAAAAAAAAAACqVVVVagAAAAAAAAAAqVVVVVqAAAAAAAAAAqVVVVVaoAAAAAAAAAqlVVVVVqgAAAAAAAAqlVVVVVWqgAAAAAAAqlVVVVVVaqAAAAAACqlVVVVVVVaqgAAAAqqlVVVVVVVVqqqqqqqqVVVVVVVVVVqqqqqqpVVVVVVVVVVVaqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVaqqqVVVVVVVVVVVVVqqqqqVVVVVVVVVVVWqqqqqpVVVVVVVVVVaqgAAAqVVVVVVVVVVaqAAAACpVVVVVVVVVqoAAAAAqVVVVVVVVVqoAAAAAKlVVVVVVVWqgAAAAACpVVVVVVVaqgAAAAAAqVVVVVVVaqAAAAAAAKlVVVVVVaoAAAAAAACpVVVVVVqoAAAAAAAAqVVVVVVaoAAAAAAAAKlVVVVVagAAAAAAAACpVVVVVWoAAAAAAAAAqVVVVVVqAAAAAAAAAKlVVVVVagAAAAAAAACpVVVVVWoAAAAAAAAAqVVVVVVqAAAAAAAAAKlVVVVVagAAAgAAAACpVVVVVWoAACogAAAAqVVVVVVagAKqoAAAAKlVVVVVWqqqqagAAACpVVVVVVaqqpWoAAAAqVVVVVVVaqlVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVaqqqAAAAAqqqpVVVWqqqqgAAAAKqqqpVVWqqqqAAAAAAKqqqlVVqgAAAAAAAAAAACpVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVWoAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWqAAAAAAAAAAAACqVVaqqqqqqqqqqqqqqVVVqqqqqqqqqqqqqqVVVWqqqqqqqqqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVWqVVVVVVVVVVVVVaqqqqqlVVVVVVVVVaqqqqqqqqVVVVVVVVqqqqoAqqqqVVVVVVWqqgAAAAAKqpVVVVVaqgAAAAAAAAqpVVVVaqAAAAAAAAACqlVVVWoAAAAAAAAAACpVVVWoAAAAAAAAAAAKlVVVqAAAAAAAAAAACqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAAqVVWoAAAKqqAAAAAAKlVVagACqqqqAAAAACpVVWqqqqqqqqAAAAAqVVVaqqqlVVqgAAAAKlVVVaqpVVVVqAAAACpVVVVVVVVVVagAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVqAAAACpVVVVVVVVVVagAAAAqVVVVVVVVVVagAAAAqVVVVVVVVVVaoAAAAKlVVVVVVVVVaoAAAACpVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACqVVVVVVVVVWoAAAAAqVVVVVVVVVWqAAAAAqVVVVVVVVVWqAAAAAqlVVVVVVVVWqAAAAAKlVVVVVVVVWqAAAAAKlVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAKpVVVVVVVVWqAAAAAqpVVVVVVVVWqAAAAACpVVVVVVVVWqAAAAAKlVVVVVVVVVqAAAAAACqqqqpVVVVqAAAAAAKqqqqqpVVVqgAAAAAAKqqqqqpVVagAAAAAAAAAAACqVVWoAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVWoAAAAAAAAAAAACpVVqAAAAAAAAAAAAAqVVaoAAAAAAAAAAAAqVVVqqqqqqqqqqqqqqlVVWqqqqqqqqqqqqqlVVVaqqqqqqqqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVapVVVVVVVVVVVVVaqqqqqlVVVVVVVVVaqqqqqqqqVVVVVVVWqqqqgCqqqpVVVVVVaqqgAAAAAKqpVVVVVaqgAAAAAAACqpVVVVaoAAAAAAAAACqlVVVaoAAAAAAAAAACqVVVWoAAAAAAAAAAAKlVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAAqVVVqAAAKqqgAAAAAKlVVaoAKqqqqgAAAACpVVVqqqqqqqqAAAAAqVVVWqqqlVVaoAAAAKlVVVaqlVVVVqAAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVaoAAAAqVVVVVVVVaqqqAAAAKlVVVVVVaqqqoAAAAKlVVVVVVaqqqgAAAAKpVVVVVVaqgAAAAAAKpVVVVVVaoAAAAAAAKpVVVVVVWoAAAAAAACpVVVVVVVqAAAAAAACpVVVVVVVagAAAAAAAqVVVVVVVWoAAAAAAACpVVVVVVVqAAAAAAAAqpVVVVVVagAAAAAAACqlVVVVVWoAAAAAAAACqVVVVVVaoAAAAAAAAKpVVVVVWqqqqgAAAAAqVVVVVVWqqqqgAAAACpVVVVVVaqqqqgAAAAqVVVVVVVVVVaqAAAAKlVVVVVVVVVVagAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVlVVVVVVqAAAAKlVVaqqVVVVVqAAAACpVVaqqqpVVWqgAAAAqVVaqKqqqqqqgAAAAKlVaoAAqqqqqAAAAACpVaoAAACqqoAAAAACpVWoAAAAAAAAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAAKpVVqAAAAAAAAAAAACpVVagAAAAAAAAAAAKpVVVqAAAAAAAAAAAKqVVVaqAAAAAAAAAAKpVVVVqqAAAAAAAACqpVVVVVqqoAAAAACqqpVVVVVWqqqqqqqqqqlVVVVVVVqqqqqqqqpVVVVVVVVVWqqqqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVVVaqqlVVVVVVVVVVVVVqqqqlVVVVVVVVVVVVqqqqqVVVVVVVVVVVVqgAAKpVVVVVVVVVVVqgAAAqVVVVVVVVVVVqgAAACpVVVVVVVVVVqgAAAAqVVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVaoAAAAAqVVVVVVVVVWoAAAAAKlVVVVVVVVWoAAAAACpVVVVVVVVWqAAAAAAqVVVVVVVVVqAAAAAAKlVVVVVVVVqAAAAAACpVVVVVVVVqgAAAAAAqVVVVVVVVagAAAAAAKlVVVVVVVagAAAAAACpVVVVVVVaoAAAAAAAqVVVVVVVaoAAAAAAAKlVVVVVVWoAAAAAAACpVVVVVVWoAAAAAAAAqVVVVVVWqAAAAAAAAKlVVVVVVqAAAAAAAACpVVVVVVqAAAAAAAAAqVVVVVVqgAAAAAAAAKlVVVVVagAAAgAAAACpVVVVVagAACogAAAAqVVVVVaoAACqoAAAAKlVVVVWoAAAqagAAACpVVVVWoAAAqWoAAAAqVVVVWqAAAqlqAAAAKlVVVVqAAAKlagAAACpVVVVqAAAKlWoAAAAqVVVVqgAAKpVqAAAAKlVVVagAACpVagAAACpVVVagAACpVWoAAAAqVVVaoAAAqVVqAAAAKlVVWoAAACqqqAAAAAqpVVqAAAAqqqgAAAAKqpVqAAAAAqqAAAAAAKqpagAAAAAAAAAAAAACqWoAAAAAAAAAAAAAACpqAAAAAAAAAAAAAAAqagAAAAAAAAAAAAAAKmoAAAAAAAAAAAAAACpqAAAAAAAAAAAAAAAqagAAAAAAAAAAAAAAKlqAAAAAAAAAAAAAACpaqAAAAAAAAAAAAACpVqqqqqqqgAAAAACqqVVqqqqqqqgAAAAKqqVVVqqqqqqoAAAACqpVVVVVVVVVagAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAACpVVVVVVVVVVVqAAACpVVVVVVVVVVVagAAAqVVVVVVVVVVVVqgACqVVVVVVVVVVVVaqqqqlVVVVVVVVVVVVaqqqVVVVVVVVVVVVVVqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVaqqqqqqqqpVVVVVVaqqqqqqqqqqqlVVVVaqqqqqqqqqqqqVVVVaqgAAAAAAAACqpVVVaoAAAAAAAAAAAqlVVWoAAAAAAAAAAACpVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVVqAAAAAAAAAAAAKlVVagAAAAAAAAAAAKlVVWoAAAAAAAAAAACpVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAKqVVVWoAAAACqqqqqqqlVVVqAAAAKqqqqqqqVVVVagAAACqqqqqqlVVVVWoAAACpVVVVVVVVVVVqAAAAqVVVVVVVVVVVagAAAKlVVVVVVVVVVWoAAACpVVVVVVVVVVVqAAAAqVVVVVVVVVVVagAAACqqqqqVVVVVVWoAAAAqqqqqqlVVVVVqAAAAAqqqqqqlVVVVagAAAAAAAAAqqVVVVWoAAAAAAAAAAKpVVVVqAAAAAAAAAAAqlVVVagAAAAAAAAAACqVVVWoAAAAAAAAAAAKpVVVqAAAAAAAAAAAAqlVVagAAAAAAAAAAACpVVWoAAAAAAAAAAAAKlVVqAAAAAAAAAAAACpVVagAAAACoAAAAAAKlVWoAAACqqqAAAAACpVVqAAAKqqqqAAAAAqVVWqgqqqpWqoAAAAKlVVqqqqpVVVqAAAACpVVWqqqlVVVWoAAAAqVVVVaVVVVVVqAAAACpVVVVVVVVVVWoAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAAKlVVWqpVVVVVagAAACpVVaqqqVVVVaoAAAAqVVaqqqqlVVqoAAAAKlVWoACqqqqqoAAAACpVWoAAAqqqqgAAAAAqVVqAAAAKqqAAAAAAqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAACpVVqAAAAAAAAAAAACpVVagAAAAAAAAAAACqVVVqAAAAAAAAAAACqVVVaoAAAAAAAAAAKqVVVVqgAAAAAAAAAKqVVVVWqoAAAAAAAAqpVVVVVaqqgAAAAAqqpVVVVVVaqqqqqqqqqlVVVVVVVWqqqqqqqqVVVVVVVVVVaqqqqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVqVVVVVVVVVVVVVVqqqqqpVVVVVVVVVVqqqqqqqqVVVVVVVVWqqqqAqqqqlVVVVVVWqqAAAAACqqlVVVVVaqAAAAAAAAqpVVVVVaqAAAAAAAAAKlVVVVaoAAAAAAAAACqVVVVqoAAAAAAAAAAKlVVVaoAAAAAAAAAACpVVVagAAAAAAAAAAAqVVVaoAAAAAAAAAAAKlVVaoAAAAAAAAAAACpVVWoAAAAAAAAAAAAqVVWoAAAAACqqqAAAqVVVqAAAAAKqqqqoAqlVVqAAAAAqqqqqqqqlVVagAAAAqpVVVqqqlVVagAAAAqlVVVVWqVVVWoAAAAqlVVVVVVVVVVqAAAAKlVVVVVVVVVVagAAAKlVVVVVVVVVVagAAACpVaqqqVVVVVWoAAAAqVqqqqqVVVVVqAAAAqWqqqqqqVVVVagAAAImqgAAAqpVVVWoAAAAoqAAAAAqlVVVqAAAAIqAAAAACqlVVqAAAAAIAAAAAAKpVVagAAAAAAAAAAAAKlVWoAAAAAAAAAAAACqVVqAAAAAAAAAAAAAKlVagAAAAAAAAAAAAAqVWoAAAAAAAAAAAAAKlVqAAAAAACqgAAAACpVagAAAAAKqqgAAAAKlWoAAAAAKqqqAAAACpVagAAAACpVaoAAAAqVWoAAAACpVVqAAAAKlVqAAAAAqVVWoAAACpVagAAAAqVVVqAAAAKlWoAAAAKlVVagAAACpVqAAAACpVVWoAAAAqVagAAAAqVVVqAAAAKlVqAAAAKlVVagAAAKlVagAAACpVVWoAAACpVWoAAAAKlVVqAAAAqVVagAAACpVVqAAAAKlVWoAAAAKlVqgAAACpVVqAAAACqqqgAAAAqVVWoAAAAKqqgAAAAqVVVqAAAAAKqAAAAAKlVVWoAAAAAAAAAAAKlVVVqgAAAAAAAAAACpVVVWoAAAAAAAAAACpVVVVagAAAAAAAAACqVVVVWqAAAAAAAAACqVVVVVaqAAAAAAAACqVVVVVVqoAAAAAAACqVVVVVVVqoAAAAAAKqVVVVVVVWqqAAAACqqVVVVVVVVWqqqqqqqpVVVVVVVVVWqqqqqqlVVVVVVVVVVVqqqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVqqqqqqqqqqqlVVVVqqqqqqqqqqqqqqVVVqqqqqqqqqqqqqqpVVqqAAAAAAAAAAAKqlVqgAAAAAAAAAAAACqVagAAAAAAAAAAAAAKlWoAAAAAAAAAAAAAAqVqAAAAAAAAAAAAAAKlagAAAAAAAAAAAAACpWoAAAAAAAAAAAAAAqVqAAAAAAAAAAAAAAKlagAAAAAAAAAAAAACpWqAAAAAAAAAAAAAAqVaqgAAAAAAAAAAAAqVVqqqqqqqqAAAAAAKlVWqqqqqqqqAAAAACpVVVaqqqqqqAAAAACpVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqgAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAACqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVagAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAAKpVVVVVVVVVaoAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVWqAAAAAqVVVVVVVVVVqAAAAAqlVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVagAAAACpVVVVVVVVVWoAAAACpVVVVVVVVVWoAAAAAqVVVVVVVVVVqAAAAAqVVVVVVVVVVqAAAAAKlVVVVVVVVVagAAAAKlVVVVVVVVVWoAAAAKpVVVVVVVVVVqAAAACpVVVVVVVVVVqAAAACpVVVVVVVVVVWoAAACqVVVVVVVVVVVqAAACqVVVVVVVVVVVaoAACqVVVVVVVVVVVVqqqqqVVVVVVVVVVVVWqqqqVVVVVVVVVVVVVWqqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVqVVVVVVVVVVVVVWqqqqqpVVVVVVVVVVqqqqqqqqlVVVVVVVWqqqqAqqqqlVVVVVVaqoAAAAACqqVVVVVVaqAAAAAAAAKqVVVVVaoAAAAAAAAAqpVVVVaoAAAAAAAAAAqlVVVaoAAAAAAAAAACqVVVaoAAAAAAAAAAAKlVVWoAAAAAAAAAAAAqVVWoAAAAAAAAAAAAKlVVqAAAAAAAAAAAAAqVVagAAAACqqAAAAAKlVagAAAAKqqqAAAACpVWoAAAAKqqqoAAAAqVVqAAAAKpVVqAAAAKlVagAAACpVVWoAAAAqVWoAAACpVVVqAAAAKlVqAAAAqVVVagAAAKlVagAAAKlVVWoAAACpVWoAAACpVVVqAAAAqVVagAAAKlVVagAAAKlVWoAAACqVVagAAACpVVqAAAAKqqqoAAACpVVWoAAAAqqqoAAAAqVVVqgAAAAqqgAAAAqVVVWqAAAAAAAAAAAqlVVVaoAAAAAAAAAAqlVVVVqAAAAAAAAAAqlVVVVWoAAAAAAAAAKlVVVVVqAAAAAAAAAKlVVVVVqAAAAAAAAAAKlVVVVqgAAAAAAAAAKqVVVWqgAAAAAAAAAAKpVVVqgAAAAAAAAAAAqlVVqAAAAAAAAAAAACqVVqgAAAAKqqAAAAAKlVagAAAAqqqqAAAAAqVWoAAAAqqqqqAAAAKlWoAAAAqlVVqoAAACpVqAAAAKlVVVqAAAAKlagAAAKlVVVWoAAACpWoAAACpVVVVqAAAAqVqAAACpVVVVagAAAKlqAAAAKlVVVWoAAACpagAAACpVVVVqAAAAqVqAAAAKlVVVqAAAAKlagAAACqVVWqgAAACpWoAAAAKqqqqgAAAAqVqAAAAAqqqqAAAAAKlagAAAAAqqoAAAAAKlVqAAAAAAAAAAAAACpVagAAAAAAAAAAAACpVVqAAAAAAAAAAAAAqVVaoAAAAAAAAAAAAqVVVqgAAAAAAAAAAAqlVVWqAAAAAAAAAACqlVVVaqAAAAAAAAACqlVVVVqqAAAAAAAAKqVVVVVVqqoAAAAAKqqVVVVVVVqqqqqqqqqpVVVVVVVVqqqqqqqqlVVVVVVVVVWqqqqqlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 52 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVValVVVVVVVVVVVVVWqqqqqVVVVVVVVVVVqqqqqqqVVVVVVVVVWqqqgKqqqVVVVVVVVWqoAAAAAqqVVVVVVVaqAAAAAAAqpVVVVVVaqAAAAAAAAqpVVVVVaoAAAAAAAACqVVVVVWoAAAAAAAAACpVVVVWoAAAAAAAAAAqlVVVWqAAAAAAAAAACqVVVVqAAAAAAAAAAAKpVVVqAAAAAAAAAAAAqVVVagAAAAKqgAAAACpVVagAAAAqqqgAAAAqVVWoAAAAKqqoAAAACpVVqAAAAKlVagAAAAqVVqAAAAKpVWqAAAAKlVagAAACpVVagAAACpVWoAAACpVVVqAAAAKlVqAAAAqVVVagAAACpVagAAAKlVVWoAAAAqVWoAAACpVVVqAAAACpVqAAAAqVVVagAAAAqVagAAACpVVagAAAAKlWoAAAAqVVWoAAAACpVqAAAACpVWoAAAAAqVagAAAAqlWqAAAAAKlVqAAAACqqqAAAAACpVagAAAAKqqAAAAAAqVWoAAAAAKoAAAAAAKlVagAAAAAAAAAAAACpVWoAAAAAAAAAAAAAqVVagAAAAAAAAAAAAKlVWqAAAAAAAAAAAACpVVagAAAAAACAAAAAqVVVqAAAAAAKiAAAAKlVVaqAAAAAKigAAACpVVVqoAAAAKpiAAAAqVVVVqoAAAqpagAAAKlVVVWqqqqqpagAAACpVVVVWqqqqlWoAAACpVVVVVWqqqVVqAAAAqVVVVVVVVVVVqAAAAKlVVVVVVVVVVqgAAACpVVVVVVVVVVqgAAACpVVVaqlVVVVqgAAAAqVVVqqqqVVWqgAAAAKlVVqqqqqqqqgAAAAKlVVagAKqqqqAAAAACpVVagAAAqqoAAAAACpVVWoAAAAAAAAAAACqVVVqAAAAAAAAAAAAqVVVagAAAAAAAAAAAqVVVWoAAAAAAAAAAAqlVVVqAAAAAAAAAAAqlVVVagAAAAAAAAAAqlVVVWoAAAAAAAAACqlVVVVagAAAAAAAACqlVVVVWqoAAAAAAAqqVVVVVVaqqAAAAAKqqVVVVVVVaqqqqqqqqpVVVVVVVVWqqqqqqqVVVVVVVVVVVqqqqqlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU= "))},
|
||||
{width : 25 , height : 86 , bpp : 2, transparent : 1, buffer : E.toArrayBuffer(atob(" VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaqlVVVVWqqqlVVVWqqqqlVVWqgAKqVVWqAAAKlVWqAAAAqVVqAAAAKlVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAKlVqAAAACpVagAAACpVWqAAAAqVVagAAAqlVVqgAAqlVVaqqqqlVVVaqqqlVVVVaqqVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVqqqVVVVVqqqqVVVWqqqqpVVVqgAAqlVVqAAACqVVqgAAAKlVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAKlVqAAAACpVagAAAAqVWoAAAAqVVagAAAKlVWqAAAKlVVaqACqpVVVqqqqpVVVWqqqlVVVVVqpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV "))}
|
||||
];
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2-31;
|
||||
g.reset();
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
// draw time
|
||||
g.clearRect(0,y,g.getWidth()-1,y+73+24+18);
|
||||
//use custom font spacing for overlapping digits
|
||||
g.drawImage(digits[parseInt(date.getHours()/10)],0,y);
|
||||
g.drawImage(digits[parseInt(date.getHours()%10)],37,y);
|
||||
g.drawImage(digits[10],74,y);
|
||||
g.drawImage(digits[parseInt(date.getMinutes()/10)],86,y);
|
||||
g.drawImage(digits[parseInt(date.getMinutes()%10)],123,y);
|
||||
// Draw day of the week
|
||||
y += 73;
|
||||
g.setFontAlign(0,-1).setFont("Teletext10x18Ascii");
|
||||
g.drawString(require("locale").dow(date).toUpperCase(),x,y);
|
||||
// Draw Date
|
||||
y += 24;
|
||||
g.drawString(require('locale').date(new Date(),1),x,y);
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
Bangle.setUI("clock");
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: New app
|
||||
0.02: Cleanup interface and add settings, widget, add skin temp reporting.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
# CoreTemp display
|
||||
|
||||
Basic bare-bones example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current body core temperature readings.
|
||||
Basic example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current skin and body core temperature readings.
|
||||
|
||||
## Usage
|
||||
|
||||
On startup connects to a CoreTemp device (1809/2A1C) and emits a "Core, temp" value for each reading.
|
||||
The app simply displays these readings on screen.
|
||||
Background task connects to any CoreTemp device (2100/2101) and emits a CoreTemp signal value for each reading.
|
||||
Application contains three components, one is a background task that monitors the sensor and emits a 'CoreTemp' signal on activity if activated in settings.
|
||||
The widget shows when the sensor is enabled with a mini value and blinks on use.
|
||||
The app listens for 'CoreTemp' signals and shows the current skin and core temperatures in large numbers.
|
||||
|
||||
## TODO
|
||||
|
||||
* Integrate with other tracking/sports apps to log data.
|
||||
* Add device selection
|
||||
* Provide enable/disable option
|
||||
* Check status, add Retry/reconnect
|
||||
* Also provide skin temp reading
|
||||
* Add specific device selection
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"enabled":false
|
||||
}
|
||||
|
|
@ -1,23 +1,114 @@
|
|||
//
|
||||
// If enabled in settings run constantly in background
|
||||
//
|
||||
(function() {
|
||||
var gatt;
|
||||
var log = function() {};//print
|
||||
var settings = {};
|
||||
var device;
|
||||
var gatt;
|
||||
var service;
|
||||
var characteristic;
|
||||
|
||||
//Would it be better to scan by uuid rather than name?
|
||||
NRF.requestDevice({ timeout: 20000, filters: [{ name: 'CORE [a]' }] }).then(function(device) {
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("1809");
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic("2A1C");
|
||||
}).then(function(characteristic) {
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
class CoreSensor {
|
||||
constructor() {
|
||||
this.unit = "";
|
||||
this.core = -1;
|
||||
this.skin = -1;
|
||||
this.battery = 0;
|
||||
}
|
||||
|
||||
updateSensor(event) {
|
||||
if (event.target.uuid == "00002101-5b1e-4347-b07c-97b514dae121") {
|
||||
var dv = event.target.value;
|
||||
var core = (dv.buffer[2]*256+dv.buffer[1])/100;
|
||||
Bangle.emit('Core',{
|
||||
temp:core
|
||||
});
|
||||
var flags = dv.buffer[0];
|
||||
|
||||
if (flags & 8) {
|
||||
this.unit = "F";
|
||||
} else {
|
||||
this.unit = "C";
|
||||
}
|
||||
|
||||
if (flags & 1) {
|
||||
this.skin = (dv.buffer[4] * 256 + dv.buffer[3]) / 100;
|
||||
} else {
|
||||
this.skin = 0;
|
||||
}
|
||||
if (flags & 2) {
|
||||
this.core = (dv.buffer[2] * 256 + dv.buffer[1]) / 100;
|
||||
} else {
|
||||
this.core = 0;
|
||||
}
|
||||
|
||||
Bangle.emit('CoreTemp',
|
||||
{core : this.core, skin : this.skin, unit : this.unit});
|
||||
}
|
||||
}
|
||||
|
||||
updateBatteryLevel(event) {
|
||||
if (event.target.uuid == "0x2a19")
|
||||
this.battery = event.target.value.getUint8(0);
|
||||
}
|
||||
}
|
||||
|
||||
var mySensor = new CoreSensor();
|
||||
|
||||
function getSensorBatteryLevel(gatt) {
|
||||
gatt.getPrimaryService("180f")
|
||||
.then(function(s) { return s.getCharacteristic("2a19"); })
|
||||
.then(function(c) {
|
||||
c.on('characteristicvaluechanged',
|
||||
(event) => mySensor.updateBatteryLevel(event));
|
||||
return c.startNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
function connection_setup() {
|
||||
log("Scanning for CoreTemp sensor...");
|
||||
NRF.requestDevice({active:true,timeout : 20000, filters : [ {namePrefix : 'CORE'} ]})
|
||||
.then(function(d) {
|
||||
device = d;
|
||||
log("Found device");
|
||||
return device.gatt.connect();
|
||||
})
|
||||
.then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService('00002100-5b1e-4347-b07c-97b514dae121');
|
||||
})
|
||||
.then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic(
|
||||
'00002101-5b1e-4347-b07c-97b514dae121');
|
||||
})
|
||||
.then(function(c) {
|
||||
characteristic = c;
|
||||
characteristic.on('characteristicvaluechanged',
|
||||
(event) => mySensor.updateSensor(event));
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
})
|
||||
.then(function() {
|
||||
log("Done!");
|
||||
// getSensorBatteryLevel(gatt);
|
||||
})
|
||||
.catch(function(e) {
|
||||
log(e.toString(), "ERROR");
|
||||
log(e);
|
||||
});
|
||||
}
|
||||
|
||||
function connection_end() {
|
||||
if (gatt != undefined)
|
||||
gatt.disconnect();
|
||||
}
|
||||
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
log("Settings:");
|
||||
log(settings);
|
||||
|
||||
if (settings.enabled) {
|
||||
connection_setup();
|
||||
NRF.on('disconnect', connection_setup);
|
||||
}
|
||||
|
||||
E.on('kill', () => { connection_end(); });
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,66 @@
|
|||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
var btm = g.getHeight()-1;
|
||||
// Simply listen for core events and show data
|
||||
|
||||
var btm = g.getHeight() - 1;
|
||||
var px = g.getWidth() / 2;
|
||||
|
||||
// Dark or light logo
|
||||
var col = (process.env.HWVERSION == 1) ? 65535 : 0;
|
||||
|
||||
var corelogo = {
|
||||
width : 146,
|
||||
height : 48,
|
||||
bpp : 4,
|
||||
transparent : 0,
|
||||
palette : new Uint16Array([ col, col, 2854, 1419 ]),
|
||||
buffer :
|
||||
require("heatshrink")
|
||||
.decompress(atob(
|
||||
"AEUDmczmBD/I4xJ/AAMCkBHFAAJG8kQABJAJHFSVURAAUQRphHCkQGBJAySngJHDJRhHEJALZDAgiSBEQ0RPBIAKHAwQQI4xIEaoQFEEZpIULSRHFkDZDBwZIMEYhITa44SKSAxIDSARIDJ4IjKJCpHNEoiQGJDA2CJCQSOCYaQGJDBsCGiKQGTZIJCI4xBEJBAAEFpQAPDQoMGBQyOGIJJPGF6AALC5glCbJAQEgZCEAoowTSBypJBwKQMIQaSBAgZIJWw5ITB5RTDSBLbEAAjDOPRIVabIiQFJBCQKPYhIVCRxIEBg7WDSBpIVbJ5IQJIqQBgZIiCh7ZLJIriDbhJI3JoxIebIZITI6BIjCZ5IRI4RIPHAYAJJH4AIUAJIzHIhI/SAwzBJH6QGJH5HIHApI2HCIAJL4pITkATOJQJIMHCJeFJD8zaZCQHJCEBJCUCJCKPBJBhWGJEcia5oACJBSfHJB4QMJA6SLI4ZIKPAg3QJCUAJCbbBJETbPJAbbKbIhIBYJpIQbZ5UDbZzZFPBxIVSRIOBJA5JISAhIIF4ZIUfQpJHEwQKDJAhJHbJbBJJCIZECY4KGSQoABBIZOBSBbbIJC6IEBQqSJJoyQLbZBIRbYoAKJAaSHJAjbCF541RSRISLSRkgJAKQKbY5ISJJyQDSRyQMbYxITChhHFSRhGMbY5IUCpRHHJJZITiBIVbpBHJbpJHPFhBITfI4ANIwcgI6AAV"))
|
||||
};
|
||||
|
||||
function onCore(c) {
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
var str = c.temp + "C";
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
// Large or small font
|
||||
var sz = ((process.env.HWVERSION == 1) ? 3 : 2);
|
||||
|
||||
g.setFontAlign(0, 0);
|
||||
g.clearRect(0, 32 + 48, g.getWidth(), 32 + 48 + 24 * 4);
|
||||
g.setColor(g.theme.dark ? "#CCC" : "#333"); // gray
|
||||
g.setFont("6x8", sz).drawString(
|
||||
"Core: " + ((c.core < 327) ? (c.core + c.unit) : 'n/a'), px, 48 + 48);
|
||||
g.setFont("6x8", sz).drawString("Skin: " + c.skin + c.unit, px, 48 + 48 + 24);
|
||||
}
|
||||
Bangle.on('Core', onCore);
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Background task will activate once settings are enabled.
|
||||
function enableSensor() {
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
if (!settings.enabled) {
|
||||
settings.enabled = true;
|
||||
require("Storage").write("coretemp.json", settings);
|
||||
|
||||
drawBackground("Waiting for\ndata...");
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackground(message) {
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
g.reset().setFont("6x8", 2).setFontAlign(0, 0);
|
||||
g.drawImage(corelogo, px - 146 / 2, 30);
|
||||
g.drawString(message, g.getWidth() / 2, g.getHeight() / 2 + 16);
|
||||
}
|
||||
|
||||
Bangle.on('CoreTemp', onCore);
|
||||
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
if (!settings.enabled) {
|
||||
drawBackground("Sensor off\nBTN" +
|
||||
((process.env.HWVERSION == 1) ? '2' : '1') + " to enable");
|
||||
} else {
|
||||
drawBackground("Waiting for\ndata...");
|
||||
}
|
||||
|
||||
setWatch(() => { enableSensor(); }, (process.env.HWVERSION == 1) ? BTN2 : BTN1,
|
||||
{repeat : false});
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,47 @@
|
|||
// This file should contain exactly one function, which shows the app's settings
|
||||
/**
|
||||
* @param {function} back Use back() to return to settings menu
|
||||
*/
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'coretemp.json'
|
||||
// initialize with default settings...
|
||||
let s = {
|
||||
'enabled': true,
|
||||
}
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key];
|
||||
}
|
||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||
function save(key) {
|
||||
return function (value) {
|
||||
s[key] = value;
|
||||
storage.write(SETTINGS_FILE, s);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
require("Storage").write("coretemp.json", s);
|
||||
if (WIDGETS["coretemp"])
|
||||
WIDGETS["coretemp"].reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const menu = {
|
||||
'' : {'title' : 'CoreTemp sensor'},
|
||||
'< Back' : back,
|
||||
'Enabled' : {
|
||||
value : !!s.enabled,
|
||||
format : v => v ? "Yes" : "No",
|
||||
onchange : v => {
|
||||
s.enabled = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
E.showMenu(menu);
|
||||
})
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// TODO Change to a generic multiple sensor widget?
|
||||
|
||||
(() => {
|
||||
var settings = {};
|
||||
var count = 0;
|
||||
var core = 0;
|
||||
|
||||
// draw your widget
|
||||
function draw() {
|
||||
if (!settings.enabled)
|
||||
return;
|
||||
g.reset();
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0);
|
||||
g.setFontAlign(0, 0);
|
||||
g.clearRect(this.x, this.y, this.x + 23, this.y + 23);
|
||||
|
||||
if (count & 1) {
|
||||
g.setColor("#0f0"); // green
|
||||
} else {
|
||||
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
||||
}
|
||||
|
||||
g.drawImage(
|
||||
atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),
|
||||
this.x+(24-12)/2,this.y+1);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(parseInt(core)+"\n."+parseInt((core*100)%100), this.x + 24 / 2, this.y + 18);
|
||||
|
||||
g.setColor(-1);
|
||||
}
|
||||
|
||||
// Set a listener to 'blink'
|
||||
function onTemp(temp) {
|
||||
count = count + 1;
|
||||
core = temp.core;
|
||||
WIDGETS["coretemp"].draw();
|
||||
}
|
||||
|
||||
// Called by sensor app to update status
|
||||
function reload() {
|
||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||
|
||||
Bangle.removeListener('CoreTemp', onTemp);
|
||||
|
||||
if (settings.enabled) {
|
||||
WIDGETS["coretemp"].width = 24;
|
||||
Bangle.on('CoreTemp', onTemp);
|
||||
} else {
|
||||
WIDGETS["coretemp"].width = 0;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
// add the widget
|
||||
WIDGETS["coretemp"] = {
|
||||
area : "tl",
|
||||
width : 24,
|
||||
draw : draw,
|
||||
reload : function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
}
|
||||
};
|
||||
// load settings, set correct widget width
|
||||
reload();
|
||||
})()
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# DirAct
|
||||
|
||||
[DirAct](https://www.reelyactive.com/diract/) implementation for the Bangle.js.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
Currently implements DirAct real-time functionality.
|
||||
|
||||
|
||||
## Controls
|
||||
|
||||
None.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
[Contact reelyActive](https://www.reelyactive.com/contact/) for support/updates.
|
||||
|
||||
|
||||
## Creator
|
||||
|
||||
Developed by [jeffyactive](https://github.com/jeffyactive) of [reelyActive](https://www.reelyactive.com). DirAct is jointly developed by reelyActive and Code Blue Consulting.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkE/4AImUQgMjBpIAI+UQFAMBn4XRmJBDiYXRFwQwCC9HzF93/kAXDgSPWj4XRMAZGSDAUhiTWSAH4Ag+URAAUvRBkSAofxgUzmchX4khSw3ygIID+UiFwMiF4bIBGowIBiYvEmUjF4kxEwgABmU/mMCC4cBiUhiAXDkET+cjC4cgj5IE+MAI4MAC4RGCHQIXEgRREF44kCCIIeCDoIIBMAYkDHQJeDEwJBBn/xgYGBn8hC5cS+YoBmEfFoP/IoMxgYXJmETJIISBj/zgBOBDgITCSgYXDBoYUCDQIXBiYXNBIIXBA4IXHI44XEJIJHIC5JHEVwRkBO5qKBbYqPGgMikTXDmKPDEAPyAIJJCX4cAAAQXDIoQtBl6IEGIIXJFoYmCSAQ4BSYIXJRYJWBDQIACkM/eYQXJdYcSC4fzG4J2CC5MwK4I+CAAR4BLwQXJBwowDiQfDC5HzLAIXFGwoXILAQALC5IANC/4X/C+w"))
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
/**
|
||||
* Copyright reelyActive 2017-2021
|
||||
* We believe in an open Internet of Things
|
||||
*
|
||||
* DirAct is jointly developed by reelyActive and Code Blue Consulting
|
||||
*/
|
||||
|
||||
// User-configurable constants
|
||||
const INSTANCE_ID = [ 0x00, 0x00, 0x00, 0x01 ];
|
||||
const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d,
|
||||
0xd1, 0xe0, 0x1b, 0xed, 0x0c ];
|
||||
const EXCITER_INSTANCE_IDS = new Uint32Array([ 0xe8c17e45 ]);
|
||||
const RESETTER_INSTANCE_IDS = new Uint32Array([ 0x4e5e77e4 ]);
|
||||
const PROXIMITY_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_LED_RSSI_THRESHOLD = -65;
|
||||
const PROXIMITY_TABLE_SIZE = 8;
|
||||
const DIGEST_TABLE_SIZE = 32;
|
||||
const OBSERVE_PERIOD_MILLISECONDS = 400;
|
||||
const BROADCAST_PERIOD_MILLISECONDS = 3600;
|
||||
const BROADCAST_DIGEST_PAGE_MILLISECONDS = 400;
|
||||
const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 400;
|
||||
const DIGEST_PACKET_INTERVAL_MILLISECONDS = 100;
|
||||
const DIGEST_TIME_CYCLE_THRESHOLD = 86400;
|
||||
const EXCITER_HOLDOFF_SECONDS = 60;
|
||||
const BLINK_ON_PROXIMITY = true;
|
||||
const BLINK_ON_DISTANCING = true;
|
||||
const BLINK_ON_DIGEST = true;
|
||||
const BLINK_ON_RESET = true;
|
||||
|
||||
|
||||
// Eddystone protocol constants
|
||||
const EDDYSTONE_UUID = 'feaa';
|
||||
const EDDYSTONE_UID_FRAME = 0x00;
|
||||
const EDDYSTONE_NAMESPACE_OFFSET = 2;
|
||||
const EDDYSTONE_NAMESPACE_LENGTH = 10;
|
||||
const EDDYSTONE_INSTANCE_OFFSET = 14;
|
||||
|
||||
|
||||
// DirAct constants
|
||||
const DIRACT_MANUFACTURER_ID = 0x0583; // Code Blue Consulting
|
||||
const DIRACT_PROXIMITY_FRAME = 0x01;
|
||||
const DIRACT_DIGEST_FRAME = 0x11;
|
||||
const DIRACT_DEFAULT_COUNT_LENGTH = 0x07;
|
||||
const DIRACT_INSTANCE_LENGTH = 4;
|
||||
const DIRACT_INSTANCE_OFFSET = 2;
|
||||
const MAX_NUMBER_STRONGEST = 3;
|
||||
const MAX_BATTERY_VOLTAGE = 3.3;
|
||||
const MIN_BATTERY_VOLTAGE = 3.0;
|
||||
const MAX_RSSI_TO_ENCODE = -28;
|
||||
const MIN_RSSI_TO_ENCODE = -92;
|
||||
const MAX_ACCELERATION_TO_ENCODE = 2;
|
||||
const MAX_ACCELERATION_MAGNITUDE = 0x1f;
|
||||
const INVALID_ACCELERATION_CODE = 0x20;
|
||||
const SCAN_OPTIONS = {
|
||||
filters: [
|
||||
{ manufacturerData: { 0x0583: {} } },
|
||||
{ services: [ EDDYSTONE_UUID ] }
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
// Other constants
|
||||
const BITS_PER_BYTE = 8;
|
||||
const DUMMY_INSTANCE_ID = 0;
|
||||
const DUMMY_RSSI = MIN_RSSI_TO_ENCODE;
|
||||
|
||||
|
||||
// Global variables
|
||||
let proximityInstances = new Uint32Array(PROXIMITY_TABLE_SIZE);
|
||||
let proximityRssis = new Int8Array(PROXIMITY_TABLE_SIZE);
|
||||
let digestInstances = new Uint32Array(DIGEST_TABLE_SIZE);
|
||||
let digestCounts = new Uint16Array(DIGEST_TABLE_SIZE);
|
||||
let digestTime = new Uint8Array([ 0, 0, 0 ]);
|
||||
let numberOfDigestPages = 0;
|
||||
let sensorData = [ 0x82, 0x08, 0x3f ];
|
||||
let cyclicCount = 0;
|
||||
let lastDigestTime = Math.round(getTime());
|
||||
let lastResetTime = Math.round(getTime());
|
||||
let isExciterPresent = false;
|
||||
let isResetterPresent = false;
|
||||
let isProximityDetected = false;
|
||||
let menu = {
|
||||
"": { "title": "-- DirAct --" }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initiate observer mode, scanning for devices in proximity.
|
||||
*/
|
||||
function observe() {
|
||||
proximityInstances.fill(DUMMY_INSTANCE_ID); // Reset proximity
|
||||
proximityRssis.fill(DUMMY_RSSI); // table data
|
||||
isExciterPresent = false;
|
||||
isResetterPresent = false;
|
||||
|
||||
NRF.setScan(handleDiscoveredDevice, SCAN_OPTIONS); // Start scanning
|
||||
setTimeout(broadcast, OBSERVE_PERIOD_MILLISECONDS); // ...until period end
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the scan results and initiate broadcaster mode, advertising either
|
||||
* proximity or digest packets, in consequence.
|
||||
*/
|
||||
function broadcast() {
|
||||
NRF.setScan(); // Stop scanning
|
||||
|
||||
let sortedProximityIndices = getSortedIndices(proximityRssis);
|
||||
|
||||
updateDigestTable(sortedProximityIndices);
|
||||
updateSensorData();
|
||||
|
||||
let currentTime = Math.round(getTime());
|
||||
let isExcited = isExciterPresent &&
|
||||
((currentTime - lastDigestTime) > EXCITER_HOLDOFF_SECONDS);
|
||||
|
||||
if(isResetterPresent) {
|
||||
if(BLINK_ON_RESET) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
lastResetTime = currentTime;
|
||||
resetDigest();
|
||||
broadcastProximity(sortedProximityIndices);
|
||||
}
|
||||
else if(isExcited) {
|
||||
let sortedDigestIndices = getSortedIndices(digestCounts);
|
||||
compileDigest();
|
||||
broadcastDigest(sortedDigestIndices, 0);
|
||||
}
|
||||
else {
|
||||
broadcastProximity(sortedProximityIndices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiate broadcaster mode advertising proximity packets.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function broadcastProximity(sortedIndices) {
|
||||
let advertisingOptions = {
|
||||
interval: PROXIMITY_PACKET_INTERVAL_MILLISECONDS,
|
||||
showName: false,
|
||||
manufacturer: DIRACT_MANUFACTURER_ID
|
||||
};
|
||||
|
||||
advertisingOptions.manufacturerData = compileProximityData(sortedIndices);
|
||||
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||
setTimeout(observe, BROADCAST_PERIOD_MILLISECONDS); // ...until period end
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initiate broadcaster mode advertising digest packets.
|
||||
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||
* @param {Number} pageNumber The page number to broadcast.
|
||||
*/
|
||||
function broadcastDigest(sortedIndices, pageNumber) {
|
||||
let isLastPage = (pageNumber === (numberOfDigestPages - 1));
|
||||
let advertisingOptions = {
|
||||
interval: DIGEST_PACKET_INTERVAL_MILLISECONDS,
|
||||
showName: false,
|
||||
manufacturer: DIRACT_MANUFACTURER_ID
|
||||
};
|
||||
|
||||
advertisingOptions.manufacturerData = compileDigestData(sortedIndices,
|
||||
pageNumber);
|
||||
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||
|
||||
if(isLastPage) {
|
||||
setTimeout(observe, BROADCAST_DIGEST_PAGE_MILLISECONDS);
|
||||
if(getTime() > DIGEST_TIME_CYCLE_THRESHOLD) {
|
||||
lastResetTime = Math.round(getTime());
|
||||
resetDigest();
|
||||
}
|
||||
if(BLINK_ON_DIGEST) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setTimeout(broadcastDigest, BROADCAST_DIGEST_PAGE_MILLISECONDS,
|
||||
sortedIndices, ++pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given device discovered on scan and process further if
|
||||
* Eddystone-UID or DirAct.
|
||||
* @param {BluetoothDevice} device The discovered device.
|
||||
*/
|
||||
function handleDiscoveredDevice(device) {
|
||||
let isEddystone = (device.hasOwnProperty('services') &&
|
||||
device.hasOwnProperty('serviceData') &&
|
||||
(device.services[0] === EDDYSTONE_UUID));
|
||||
let isManufacturer = (device.hasOwnProperty('manufacturer') &&
|
||||
device.manufacturer === DIRACT_MANUFACTURER_ID);
|
||||
|
||||
if(isEddystone) {
|
||||
let isEddystoneUID = (device.serviceData[EDDYSTONE_UUID][0] ===
|
||||
EDDYSTONE_UID_FRAME);
|
||||
if(isEddystoneUID) {
|
||||
handleEddystoneUidDevice(device.serviceData[EDDYSTONE_UUID], device.rssi);
|
||||
}
|
||||
}
|
||||
else if(isManufacturer) {
|
||||
let isDirAct = ((device.manufacturerData[0] === DIRACT_PROXIMITY_FRAME) ||
|
||||
(device.manufacturerData[0] === DIRACT_DIGEST_FRAME));
|
||||
if(isDirAct) {
|
||||
handleDirActDevice(device.manufacturerData, device.rssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given Eddystone-UID device, adding to the devices in range if
|
||||
* it meets the filter criteria.
|
||||
* @param {Array} serviceData The Eddystone service data.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function handleEddystoneUidDevice(serviceData, rssi) {
|
||||
for(let cByte = 0; cByte < EDDYSTONE_NAMESPACE_LENGTH; cByte++) {
|
||||
let namespaceIndex = EDDYSTONE_NAMESPACE_OFFSET + cByte;
|
||||
if(serviceData[namespaceIndex] !== NAMESPACE_FILTER_ID[cByte]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let instanceId = 0;
|
||||
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||
|
||||
for(let cByte = 0; cByte < DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||
let instanceByte = serviceData[EDDYSTONE_INSTANCE_OFFSET + cByte];
|
||||
instanceId += instanceByte << bitShift;
|
||||
bitShift -= BITS_PER_BYTE;
|
||||
}
|
||||
|
||||
let unsignedInstanceId = new Uint32Array([instanceId])[0];
|
||||
|
||||
if(EXCITER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||
isExciterPresent = true;
|
||||
}
|
||||
else if(RESETTER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||
isResetterPresent = true;
|
||||
}
|
||||
else {
|
||||
updateProximityTable(instanceId, rssi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given DirAct device, adding to the devices in range if
|
||||
* it meets the filter criteria.
|
||||
* @param {Array} manufacturerData The DirAct manufacturer data.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function handleDirActDevice(manufacturerData, rssi) {
|
||||
let instanceId = 0;
|
||||
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||
|
||||
for(let cByte = DIRACT_INSTANCE_OFFSET;
|
||||
cByte < DIRACT_INSTANCE_OFFSET + DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||
let instanceByte = manufacturerData[cByte];
|
||||
instanceId += instanceByte << bitShift;
|
||||
bitShift -= BITS_PER_BYTE;
|
||||
}
|
||||
|
||||
updateProximityTable(instanceId, rssi);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the proximity table with the given instance's RSSI. If the instance
|
||||
* already exists, combine RSSI values in a weighted average.
|
||||
* @param {String} instanceId The DirAct 4-byte instance id as a 32-bit integer.
|
||||
* @param {Number} rssi The received signal strength.
|
||||
*/
|
||||
function updateProximityTable(instanceId, rssi) {
|
||||
let instanceIndex = proximityInstances.indexOf(instanceId);
|
||||
let isNewInstance = (instanceIndex < 0);
|
||||
|
||||
if(isNewInstance) {
|
||||
let nextIndex = proximityInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||
if(nextIndex >= 0) {
|
||||
proximityInstances[nextIndex] = instanceId;
|
||||
proximityRssis[nextIndex] = rssi;
|
||||
}
|
||||
}
|
||||
else {
|
||||
proximityRssis[instanceIndex] = (proximityRssis[instanceIndex] + rssi) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the digest table based on the proximity table and its sorted indices.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function updateDigestTable(sortedIndices) {
|
||||
for(let cInstance = 0; cInstance < PROXIMITY_TABLE_SIZE; cInstance++) {
|
||||
let proximityIndex = sortedIndices[cInstance];
|
||||
|
||||
if(proximityRssis[proximityIndex] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||
let instanceId = proximityInstances[proximityIndex];
|
||||
let instanceIndex = digestInstances.indexOf(instanceId);
|
||||
let isNewInstance = (instanceIndex < 0);
|
||||
|
||||
if(isNewInstance) {
|
||||
let nextIndex = digestInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||
if(nextIndex >= 0) {
|
||||
digestInstances[nextIndex] = instanceId;
|
||||
digestCounts[nextIndex] = 1;
|
||||
}
|
||||
}
|
||||
else if(digestCounts[instanceIndex] < 65535) {
|
||||
digestCounts[instanceIndex]++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compile the digest from the digest table.
|
||||
*/
|
||||
function compileDigest() {
|
||||
let numberOfEntries = digestCounts.findIndex(count => count === 0);
|
||||
let currentTime = Math.round(getTime());
|
||||
let elapsedTime = currentTime - lastResetTime;
|
||||
if(numberOfEntries < 0) {
|
||||
numberOfEntries = DIGEST_TABLE_SIZE;
|
||||
}
|
||||
digestTime[0] = (elapsedTime >> 16) & 0xff;
|
||||
digestTime[1] = (elapsedTime >> 8) & 0xff;
|
||||
digestTime[2] = elapsedTime & 0xff;
|
||||
numberOfDigestPages = Math.max(1, Math.min(8, Math.ceil(numberOfEntries/3)));
|
||||
lastDigestTime = currentTime;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Clear the digest table.
|
||||
*/
|
||||
function resetDigest() {
|
||||
digestInstances.fill(DUMMY_INSTANCE_ID);
|
||||
digestCounts.fill(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the DirAct proximity data.
|
||||
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||
*/
|
||||
function compileProximityData(sortedIndices) {
|
||||
let data = [
|
||||
DIRACT_PROXIMITY_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||
sensorData[0], sensorData[1], sensorData[2]
|
||||
];
|
||||
let isNewProximityDetected = false;
|
||||
|
||||
for(let cInstance = 0; cInstance < MAX_NUMBER_STRONGEST; cInstance++) {
|
||||
let index = sortedIndices[cInstance];
|
||||
|
||||
if(proximityRssis[index] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||
let instanceId = proximityInstances[index];
|
||||
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||
encodeRssi(proximityRssis[index]));
|
||||
if(proximityRssis[index] >= PROXIMITY_LED_RSSI_THRESHOLD) {
|
||||
isNewProximityDetected = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||
}
|
||||
}
|
||||
|
||||
cyclicCount = (cyclicCount + 1) % 8;
|
||||
|
||||
data[1] = (cyclicCount << 5) + (data.length - 2);
|
||||
|
||||
if(isProximityDetected && !isNewProximityDetected && BLINK_ON_DISTANCING) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
else if(isNewProximityDetected && BLINK_ON_PROXIMITY) {
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
isProximityDetected = isNewProximityDetected;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile the DirAct digest data.
|
||||
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||
* @param {Number} digestPage The page of the digest to compile.
|
||||
*/
|
||||
function compileDigestData(sortedIndices, digestPage) {
|
||||
let isLastPage = (digestPage === (numberOfDigestPages - 1));
|
||||
|
||||
let digestStatus = digestTime[0] & 0x7f;
|
||||
if(isLastPage) {
|
||||
digestStatus |= 0x80;
|
||||
}
|
||||
|
||||
let data = [
|
||||
DIRACT_DIGEST_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||
digestStatus, digestTime[1], digestTime[2]
|
||||
];
|
||||
let pageIndex = digestPage * 3;
|
||||
|
||||
for(let cInstance = pageIndex; cInstance < (pageIndex + 3); cInstance++) {
|
||||
let index = sortedIndices[cInstance];
|
||||
|
||||
if(digestCounts[index] > 0) {
|
||||
let instanceId = digestInstances[index];
|
||||
let encodedCount = digestCounts[index];
|
||||
if(encodedCount > 127) {
|
||||
encodedCount = 0x80 | (Math.min((encodedCount >> 8), 0x7f) & 0x7f);
|
||||
}
|
||||
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||
encodedCount);
|
||||
}
|
||||
else {
|
||||
cInstance = pageIndex + 3; // Break
|
||||
}
|
||||
}
|
||||
|
||||
data[1] = (digestPage << 5) + (data.length - 2);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the given RSSI.
|
||||
* @param {Number} rssi The given RSSI.
|
||||
* @return {Number} The encoded RSSI.
|
||||
*/
|
||||
function encodeRssi(rssi) {
|
||||
rssi = Math.round(rssi);
|
||||
|
||||
if(rssi >= MAX_RSSI_TO_ENCODE) {
|
||||
return 0x3f;
|
||||
}
|
||||
if(rssi <= MIN_RSSI_TO_ENCODE) {
|
||||
return 0x00;
|
||||
}
|
||||
return rssi - MIN_RSSI_TO_ENCODE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the battery percentage.
|
||||
* @return {Number} The battery percentage.
|
||||
*/
|
||||
function encodeBatteryPercentage() {
|
||||
let voltage = NRF.getBattery();
|
||||
|
||||
if(voltage <= MIN_BATTERY_VOLTAGE) {
|
||||
return 0x00;
|
||||
}
|
||||
if(voltage >= MAX_BATTERY_VOLTAGE) {
|
||||
return 0x3f;
|
||||
}
|
||||
|
||||
return Math.round(0x3f * (voltage - MIN_BATTERY_VOLTAGE) /
|
||||
(MAX_BATTERY_VOLTAGE - MIN_BATTERY_VOLTAGE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode the acceleration.
|
||||
* @return {Array} The encoded acceleration [ x, y, z ].
|
||||
*/
|
||||
function encodeAcceleration() {
|
||||
let encodedAcceleration = { x: INVALID_ACCELERATION_CODE,
|
||||
y: INVALID_ACCELERATION_CODE,
|
||||
z: INVALID_ACCELERATION_CODE };
|
||||
|
||||
let acceleration = { x: Bangle.getAccel().x,
|
||||
y: Bangle.getAccel().y,
|
||||
z: Bangle.getAccel().z };
|
||||
|
||||
for(let axis in acceleration) {
|
||||
let magnitude = acceleration[axis];
|
||||
let encodedMagnitude = Math.min(MAX_ACCELERATION_MAGNITUDE,
|
||||
Math.round(MAX_ACCELERATION_MAGNITUDE *
|
||||
(Math.abs(magnitude) /
|
||||
MAX_ACCELERATION_TO_ENCODE)));
|
||||
if(magnitude < 0) {
|
||||
encodedMagnitude = 0x3f - encodedMagnitude;
|
||||
}
|
||||
encodedAcceleration[axis] = encodedMagnitude;
|
||||
}
|
||||
|
||||
return encodedAcceleration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the sensor data (battery & acceleration) for the advertising packet.
|
||||
*/
|
||||
function updateSensorData() {
|
||||
|
||||
// Update the battery measurement each time the cyclic count resets
|
||||
if(cyclicCount === 0) {
|
||||
encodedBattery = encodeBatteryPercentage();
|
||||
}
|
||||
|
||||
encodedAcceleration = encodeAcceleration();
|
||||
|
||||
sensorData[0] = ((encodedAcceleration.x << 2) & 0xfc) |
|
||||
((encodedAcceleration.y >> 4) & 0x3f);
|
||||
sensorData[1] = ((encodedAcceleration.y << 4) & 0xf0) |
|
||||
((encodedAcceleration.z >> 2) & 0x0f);
|
||||
sensorData[2] = ((encodedAcceleration.z << 6) & 0xc0) |
|
||||
(encodedBattery & 0x3f);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the sorted order of the indices of the given array.
|
||||
* @return {Uint8Array} The array of indices sorted in descending order.
|
||||
*/
|
||||
function getSortedIndices(unsortedArray) {
|
||||
let sortedIndices = new Uint8Array(unsortedArray.length);
|
||||
|
||||
sortedIndices.forEach((value, index) => sortedIndices[index] = index);
|
||||
sortedIndices.sort((a, b) => unsortedArray[b] - unsortedArray[a]);
|
||||
|
||||
return sortedIndices;
|
||||
}
|
||||
|
||||
|
||||
// On start: begin DirAct operation and display the menu
|
||||
g.clear();
|
||||
E.showMenu(menu);
|
||||
observe();
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,8 +1,18 @@
|
|||
# Desktop style App Launcher
|
||||
|
||||
Bangle 1:
|
||||
|
||||

|
||||
|
||||
In the picture above, the Settings app is selected.
|
||||
|
||||
Bangle 2:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Controls- Bangle
|
||||
|
||||
**BTN1** - move backward through app icons on a page
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.03: Show number of satellites while waiting for fix
|
||||
0.04: Add Maidenhead readout of GPS location
|
||||
0.05: Refactor to use 'layout' library for multi-device support
|
||||
0.06: Added number of satellites in view and fixed crash with GPS time
|
||||
|
|
|
|||
|
|
@ -16,13 +16,19 @@ var lastFix = {
|
|||
time: 0,
|
||||
satellites: 0
|
||||
};
|
||||
var nofix = 0;
|
||||
var SATinView = 0;
|
||||
var nofBD = 0;
|
||||
var nofGP = 0;
|
||||
|
||||
function formatTime(now) {
|
||||
if (now == undefined) {
|
||||
return "no GPS time available";
|
||||
} else {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
var time = fd[4].substr(0, 5);
|
||||
var date = [fd[0], fd[1], fd[2]].join(" ");
|
||||
return time + " - " + date;
|
||||
}
|
||||
}
|
||||
function getMaidenHead(param1,param2){
|
||||
var lat=-100.0;
|
||||
|
|
@ -77,9 +83,9 @@ function onGPS(fix) {
|
|||
{type:"txt", font:"6x8", label:"Waiting for GPS" },
|
||||
{type:"h", c: [
|
||||
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
|
||||
{type:"txt", font:"6x8", pad:3, label:"Satellites" }
|
||||
{type:"txt", font:"6x8", pad:3, label:"Satellites used" }
|
||||
]},
|
||||
{type:"txt", font:"6x8", label:"", id:"progress" }
|
||||
{type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
|
||||
]},{lazy:true});
|
||||
}
|
||||
g.clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
|
@ -87,7 +93,6 @@ function onGPS(fix) {
|
|||
}
|
||||
lastFix = fix;
|
||||
if (fix.fix) {
|
||||
nofix = 0;
|
||||
var locale = require("locale");
|
||||
var satellites = fix.satellites;
|
||||
var maidenhead = getMaidenHead(fix.lat,fix.lon);
|
||||
|
|
@ -100,12 +105,21 @@ function onGPS(fix) {
|
|||
layout.maidenhead.label = "Maidenhead: "+maidenhead;
|
||||
} else {
|
||||
layout.sat.label = fix.satellites;
|
||||
nofix = (nofix+1) % 4;
|
||||
layout.progress.label = ".".repeat(nofix) + " ".repeat(4-nofix);
|
||||
layout.progress.label = "in view: " + SATinView;
|
||||
}
|
||||
layout.render();
|
||||
}
|
||||
|
||||
function onGPSraw(nmea) {
|
||||
if (nmea.slice(3,6) == "GSV") {
|
||||
// console.log(nmea);
|
||||
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
||||
SATinView = nofBD + nofGP;
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.on('GPS', onGPS);
|
||||
Bangle.on('GPS-raw', onGPSraw);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: base code
|
||||
0.02: saved settings when switching color scheme
|
||||
0.01: Base code
|
||||
0.02: Saved settings when switching color scheme
|
||||
0.03: Added Button 3 opening messages (if app is installed)
|
||||
|
|
@ -5,6 +5,7 @@ A High-contrast, black-on-white or white-on-black clock displaying huge pixel di
|
|||
## Usage
|
||||
|
||||
* BTN 1 switches between the two modes : black-on-white or white-on-black
|
||||
* BTN 3 opens the messages (if installed, and there are new messages)
|
||||
* That's it!
|
||||
|
||||
## Issues and Requests
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ function updateTime()
|
|||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString(fmtDate(d,mo,y,hour), 120, 120);
|
||||
}
|
||||
drawMessages();
|
||||
}
|
||||
|
||||
function drawDigits(x, value)
|
||||
|
|
@ -222,6 +223,55 @@ function flipColors()
|
|||
setColorScheme(0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// MESSAGE HANDLING()
|
||||
//
|
||||
|
||||
let messages_installed = require("Storage").read("messages.app.js") != undefined;
|
||||
|
||||
function handleMessages()
|
||||
{
|
||||
if(messages_installed && hasMessages() > 0)
|
||||
{
|
||||
E.showMessage("Loading Messages...");
|
||||
load("messages.app.js");
|
||||
}
|
||||
}
|
||||
|
||||
function hasMessages()
|
||||
{
|
||||
if(!messages_installed)
|
||||
return false;
|
||||
|
||||
var messages = require("Storage").readJSON("messages.json",1)||[];
|
||||
if (messages.some(m=>m.new))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
let msg = atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==");
|
||||
let had_messages = false;
|
||||
|
||||
function drawMessages()
|
||||
{
|
||||
if(!had_messages && hasMessages()) {
|
||||
g.setColor(255,255,255);
|
||||
g.drawImage(msg, 184, 212);
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString(">", 224, 216);
|
||||
had_messages = true;
|
||||
}
|
||||
else if (had_messages && !hasMessages())
|
||||
{
|
||||
g.setColor(0,0,0);
|
||||
g.fillRect(180, 210, 240, 240);
|
||||
had_messages = false;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// MAIN FUNCTION()
|
||||
|
|
@ -238,6 +288,7 @@ setInterval(updateTime, interval);
|
|||
// Handle Button Press
|
||||
setWatch(flipColors, BTN1, true);
|
||||
setWatch(Bangle.showLauncher, BTN2, false);
|
||||
setWatch(handleMessages, BTN3, true);
|
||||
|
||||
// Handle redraw on LCD on / fullscreen notifications dismissed
|
||||
Bangle.on('lcdPower', (on) => { if(on) redraw(); });
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
||||
0.06: Fix - Alarm disabled, if clock was closed.
|
||||
0.07: Added settings to adjust data that is shown for each row.
|
||||
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
||||
|
|
@ -1,18 +1,33 @@
|
|||
# LCARS clock
|
||||
|
||||
A simple LCARS inspired clock.
|
||||
Note: To display the steps, its necessary to install
|
||||
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget).
|
||||
Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown.
|
||||
To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps)
|
||||
|
||||
## Features
|
||||
* LCARS Style watch face
|
||||
* Shows satate (charging, out of battery etc.)
|
||||
* SHows data that can be configured (steps, HRM, temperature etc.)
|
||||
* Swipe left/right to activate an alarm
|
||||
* LCARS Style watch face.
|
||||
* Full screen mode - widgets are still loaded.
|
||||
* Supports multiple screens with different data.
|
||||
* [Screen 1] Date + Time + Lock status.
|
||||
* [Screen 1] Shows randomly images of real planets.
|
||||
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
||||
* [Screen 1] Swipe up/down to activate an alarm.
|
||||
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
||||
* [Screen 1] The lower orange line indicates the battery level.
|
||||
* [Screen 2] Display month graphs for steps + hrm on the second screen.
|
||||
|
||||
|
||||
## Multiple screens support
|
||||
Access different screens via swipe left/ right
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Icons
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
||||
|
||||
|
||||
## Creator
|
||||
Made by [David Peer](https://github.com/peerdavid)
|
||||
## Contributors
|
||||
- Creator: [David Peer](https://github.com/peerdavid).
|
||||
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 795 B |
|
After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 8.5 KiB |
|
|
@ -15,85 +15,96 @@ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
|||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
/*
|
||||
* Colors to use
|
||||
*/
|
||||
let cBlue = "#0094FF";
|
||||
let cOrange = "#FF9900";
|
||||
let cPurple = "#FF00DC";
|
||||
let cWhite = "#FFFFFF";
|
||||
let cBlack = "#000000";
|
||||
|
||||
/*
|
||||
* Global lcars variables
|
||||
*/
|
||||
let lcarsViewPos = 0;
|
||||
let drag;
|
||||
let hrmValue = 0;
|
||||
var connected = NRF.getSecurityStatus().connected;
|
||||
var plotWeek = false;
|
||||
|
||||
/*
|
||||
* Requirements and globals
|
||||
*/
|
||||
const locale = require('locale');
|
||||
|
||||
var backgroundImage = {
|
||||
width : 176, height : 151, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("AAdx48cATsAg4daIAX3799ATv2wEFDrUAgNHQDyDghaAeQcJKG86D4gRKGgAA4jxKFuBB5iaDF6BB5ZwyD6QAYCC4CD/Qf6Dzg/gQf8H/iD/n//wCD9gP///wQfpBKQf6D4h5BB/yD8jl/IIIABjiD5n4/DAAWAQe8B//8QYfHj//PAaDzHwICCAAP4gYCBQep6DIIYFBRgKD1j/+gB9BQYYKBn/gQen/+BBFQAUH/iDzGoZBHJoOAQeRBDj5BHj6PB0WKlACDJQIAofYZBFBAZBBAGMHPQZB8QYZAEIIcDIOiDI/hB3QZBBFjlx44CDuBBpg4DCIJEfIIPnz15AQeAQeH8gIDBGoJBCnnz54CDZ1UHPQMHIIUAIIKD3II6MBQYQCCQeI1B+BBC/BKCBASGCQeK5B/xBC4BKEn/gAoKDyj//45BFj/xZYSDzgF/IAP+JQrLCQecAgKDBF4cHQYKJDQecAn6EBAAiJEQeZBB/jICAAMcvwMDQevgQwR0CIIiDzgP/BA1/4CD3nAHGhyD3ABqD0ABiD/Qf4ADjiD/gEnQYuQQf6D7gaDFzxB5gFzQYnz4BB5hyDFATfkEoIdagEBQYoCcgEHDrReBhKDhwEBQbYABjiD/AH4A/AH4AGiFx48cATsAg4daIIWSpMkATuQEbkAgJfbQckJQDyDhZxQA1gRKFpBA4gEQQYtwIPMSQYtAIPKADQfqADAQRA5Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AFkcuPHAQdAIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AGUcuPHAQdwIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AciSDFoCD/QfcCQYtIIPMAQYoC6gEJQYgC6gEBQf7HCQf4ABiiD9"))
|
||||
}
|
||||
var bgLeft = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))
|
||||
};
|
||||
|
||||
var bgRight = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA="))
|
||||
};
|
||||
|
||||
var iconEarth = {
|
||||
text: "EARTH",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconSaturn = {
|
||||
text: "SATURN",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
|
||||
}
|
||||
};
|
||||
|
||||
var iconMoon = {
|
||||
text: "MOON",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4AQjlx44CCCZsg8eOkHDwAQKEYgmPhEgEQM48AOIgMHEYoCB4ATI8UAmH/x04JoRuJsImHuBKLn37EwZuIgEQOI8cEpXj/yYBhE8+YNGgkYoJxITBUPnAaC///nC+FjBuIOJZEB8YeCh/8AoYACoMEEAnEjhQDPQJKJ/DCDAoi5DoLdHAoMQgLjFWYPOnngh02IwXzwDjEgPGEYS8BI4MBYoSVG4fP/nghkAgZrDkngJQqSG4gvBg4sBQgkImHihEAWwP8ZBMBEYl5/+cSoVAGQIUFh04weJn///0gj/OEw5KEz45BzhuCTYQAEgePB4IACAoJuBnAQEa4XHjxKB//xFgWHJQsCRgMDEonipwjENwUBDQNx8+evvn/hTDLw3igE+EgZxB8UOXIvEJQUfEYOfv53DEQkgga5BJQvzx84cAj+CDoNh8/eEYJKDuCSEcocnEon+/7xEgFBIIcfB4Mf/IICXI2DgDdBAAn758gCIq5Dv4zBvJuIOIfjEgvP/ARHgwdCB4P3AoTdFAAk4EYk8SQgAFTALaDSQwAGh08//vnDmBABYmEEZYAzA=="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconMars = {
|
||||
text: "MARS",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconSatellite = {
|
||||
text: "GPS ON",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("pMkyQC/ATGXhIRPyNl0gmPjlwCJ9ly1aCJ1c+fHJR1Hy1ZJR1I+fPnlx6QRLpe+/JKBr5KMuYjBJQMdCJce/fvJQW0CJUlEYQCBSpvvJQbXJjl0NwnzNxGQwEOnHhgF78+WqQyIrFx48cAQXz4ShJgAABh0+8cP//9LJEhg4jDuP3//0LhGQgYlBgeAn///5cIy8MuAmDCIP/9I4HkmCEYMOgHfCQWkCI0cuBuDgF/CIP+CI1Ny1IkeAgHANwIAB/QRFrj7BhkxEwQRC/4RFpbXDgSVBg4RCSorXDI4MJAQMfCIP8cwImDn37fwN58+kwHgLgSVFub7CI4NyBAJKDLgkuEYX78+evKtCLg0jEYRKC58JMoRcFkwjDJQTFDl65EkojEAQMdcwn/+gFC3YjEJQLXEpYRDWwQmEdI6SHAQO0CJUkx4jDF4gCIJQgRMXIjCEARIjCCJ2XEYPKCJqJBJQIROcAUpCJ0kybaDARtdCKAC2kAA="))
|
||||
}
|
||||
|
||||
var iconAlarm = {
|
||||
text: "TIMER",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpICEp//BAwCJn/+CJ8k//5CKAABCJs8uPH//x48EI5YjCAARNKEYUcv//jgFBExEnEYoAC+QmHIgIgC/gpCuPBCI2fIgU4AQXjA4P8CIuTEYZKBAolwHApXBEAWP//jxwpBAALaFDoYCIiQmDDIP4EAT+CEwnJEwYjLAQLaFEYomDKALmDNwoCIOIZuD8AkFgCYDHAQjMAQTdDNwOAEg0Dx0/cYeREZtxQYOTHgJuHOIvkXJy8DNwIACJQ8Ah4NDAAfxEZARHOIIkHg4jQAQb1CQ4KVJgEOnDIBSoIjNAQPBcAaVJcAKVBcDGOcD7OBMQM48BuH8f//JKCnhKNggRBkmfTQJxBEwhuD/gRCyVHJRlyCIVJXgYmB8ZQBAoIKBXIQmCOIt/NxAUCOIImCIgIpCBAJuDAQZEE/huIAQWTDgImBTYQGC8gRFcYpKFCI8kDwQAFCJBfBEAX/+IjBiQRIEw4jJAQc8v//NYwCIOgJrIJpA1OcwbaFAQWQA="))
|
||||
}
|
||||
};
|
||||
|
||||
var iconCharging = {
|
||||
text: "CHARGE",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 5,
|
||||
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
|
||||
}
|
||||
};
|
||||
|
||||
var iconNoBattery = {
|
||||
text: "NO BAT",
|
||||
width : 50, height : 50, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
|
||||
}
|
||||
};
|
||||
|
||||
// Font to use:
|
||||
// <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet">
|
||||
Graphics.prototype.setFontAntonioSmall = function(scale) {
|
||||
// Actual height 18 (17 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16));
|
||||
}
|
||||
Graphics.prototype.setFontAntonioMedium = function(scale) {
|
||||
// Actual height 20 (19 - 0)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAA//mP/5gAAAAAAAAAAAAA/gAMAAAAAA/gAPAAAEIIBP+H/8D+IYBP+H/8D+IABCAAwIAfnwP8+PHh448eP3+B4fAAAAAAAH/AD/4AwGAMBgD/4Af8GAAPgAPgAfgAfAAfAA+AAOP/AH/4BgGAYBgH/4A/8AAAAAAAAAQAA/B+f4/+GMPhjv/4/h8Dg/gAcYwAAPwADgAAAAAAAAB//8///sAAaAACAAAMAAb//+f//AAAAAAAbAAGwAA4AA/wADgABsAAbAAAAAAAgAAMAAPwAD8AAMAADAAAAAAAAAAHAAB/AAOAAAAAAAAMAADAAAwAAMAACAAAAAAAAAABgAAYAAAAAAAAA4AD+AP+A/4A/gAOAAAAAAAAAH//j//8wADMAAz//8f/+AAAAAAAMAADAABgAA//+P//gAAAAAAAAAAAAAfgfP4fzAfswfDP/gx/gMAAAHgPj4D8wMDMHAz//8f3+AAEAAAAADwAH8APzA/AwP//j//4AAwAAAD/Hw/x+MwBjOAYz/+Mf/AAAAAAAH//j//8wYDMGAz9/8fP+AAcDAAAwAAMAfjB/4z/wP+AD4AAwAAAAOB/f4///MHAzBwM///H9/gAAAAAAH/Pj/78wGDMBgz//8f/+AAAAAAADhwA4cAAAAAAAAAAAAAADh/A4fgAAAAOAAHwABsAA7gAccAGDAAAAANgADYAA2AANgADYAA2AAAAAAAABgwAccADuAAbAAHwAA4AAAAHwAD8c4/POMHAD/wAfwAAAAAAAAD/wD//B4B4Y/HMf8zMBMyATMwczP+M4BzHwcgf+AA+AAAAAAD4A/+P/8D+DA/4wH/+AB/4AAeAAAAAAA//+P//jBgYwYGP//j//4PH4AAAAAAAf/+P//zgAcwADP4fz+P4Ph8AAAAAAA//+P//jAAYwAGPADj//4P/4AAAAAAA//+P//jBgYwYGMGBgAAAAAAP//j//4wYAMGADBgAAAAAAAA//w///PAHzAQM4MHP7/x+/8AAAAAAD//4//+AGAABgAAYAP//j//4AAAAAAAAAA//+P//gAAAAAAAAAAAHwAB+AABgAAY//+P//AAAAAAAAAAD//4//+APgAf+Afj8PgPjAAYAAAAAAD//4//+AABgAAYAAGAAAAAAA//+P//j/gAD/wAB/gAP4B/4P/AD//4//+AAAAAAAAAAP//j//4P4AAfwAA/g//+P//gAAAAAAAAAA//g//+PAHjAAY4AOP//h//wAAAAAAD//4//+MDADAwA4cAP/AB/gAAAAAAAA//g//+PAHjAAc4APv//5//yAAAAAAD//4//+MGADBgA48AP//h+f4AAAAAAB+Pw/z+MOBjBwY/P+Hx/AAHgwAAMAAD//4//+MAADAAAAAAP//D//4AAOAABgAA4//+P//AAAAwAAP8AD//AA/+AAfgP/4//gPwAAAAA+AAP/4Af/4AD+A//j/wA/wAD/+AA/4B/+P/+D+AAAAAMADj8P4P/4A/4B//w+A+MABgAAA4AAPwAB/gAB/+A//j/gA+AAMAAAAAYwB+MH/jf+Y/8GPwBjAAAAAAP//7//+wABsAAYAAAAAAPAAD/gAH/gAD/gAD4AACAAADAAGwABv//7//+AAAA=="), 32, atob("BQUHCAgVCQQFBQkHBQcFBwgICAgICAgICAgFBQcHBwgPCQkJCQcHCQoFCQkHDQoJCQkJCAYJCQ0ICAcGBwY="), 20+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
||||
// Actual height 34 (34 - 1)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16));
|
||||
}
|
||||
// Actual height 39 (39 - 1)
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAPgAAAAAB8AAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAD8AAAAAH/gAAAAP/8AAAAf//gAAA///AAAB//+AAAD//8AAAH//4AAAP//wAAAB//gAAAAP/AAAAAB+AAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAAf////8AP/////4B//////Af/////8D8AAAAfgeAAAAA8DwAAAAHgeAAAAA8D//////gf/////8B//////AP/////wAf////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAHgAAAAAA8AAAAAAPgAAAAAB4AAAAAAf/////gP/////8B//////gP/////8B//////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAD/+AAP8A//wAP/gP/+AH/8D//wD//gfgAA//8DwAAf+HgeAAP/A8DwAH/gHgfgP/wA8D///4AHgP//+AA8A///AAHgB//AAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AA/gAD/AAH/gA/4AA/+AP/AAH/4D/4AA//gfgA4AB8DwAPAAHgeAB4AA8DwAPgAHgfAD+AB8D//////gP/////4B//5//+AD/+H//gAH/AH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAP/AAAAAP/4AAAAP//AAAAP/x4AAAf/wPAAAf/gB4AAf/AAPAAP/AAB4AB//////gP/////8B//////gP/////8AAAAAPAAAAAAB4AAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//wD/AB///Af+AP//4D/4B///Af/gP//4B/8B4D4AAPgPAeAAA8B4DwAAHgPAfAAB8B4D////gPAf///4B4B////APAD///gAAAD//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAP////4AH/////wB//////Af/////8D8APAA/geADwAB8DwAeAAHgeADwAA8D4AeAAPgf/j+AH8B/8f///gP/h///4Af8H//+AAPgP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAPAAAAAAB4AAAABgPAAAA/8B4AAB//gPAAD//8B4AH///gPAH///8B4P//+AAPH//wAAB///gAAAP//AAAAB/+AAAAAP+AAAAAB+AAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4A/+AAf/w//+AP//v//4B//////Af/////8D4AfwAPgeAB8AA8DwAHAAHgeAB8AA8D4Af4APgf/////8B//////AP//v//4A//4//8AA/4A/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAD//+D/gB///4f+AP///j/4D///8f/gfAAHgB8DwAA8AHgeAAHgA8DwAA8AHgfgAHgB8D//////gP/////4A/////+AD/////gAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAfgAAB+AD8AAAPwAfgAAB+AD8AAAPwAfgAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DBATExMTExMTExMTCw=="), 45+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Draw watch face
|
||||
|
|
@ -108,117 +119,299 @@ function queueDraw() {
|
|||
}
|
||||
|
||||
|
||||
function printData(key, y){
|
||||
function printData(key, y, c){
|
||||
g.setFontAlign(-1,-1,0);
|
||||
var text = "ERR";
|
||||
var value = "NOT FOUND";
|
||||
|
||||
if(key == "Battery"){
|
||||
var bat = E.getBattery();
|
||||
g.drawString("BAT:", 30, y);
|
||||
g.drawString(bat+ "%", 68, y);
|
||||
text = "BAT";
|
||||
value = E.getBattery() + "%";
|
||||
|
||||
} else if(key == "Steps"){
|
||||
var steps = getSteps();
|
||||
g.drawString("STEP:", 30, y);
|
||||
g.drawString(steps, 68, y);
|
||||
text = "STEP";
|
||||
value = getSteps();
|
||||
|
||||
} else if(key == "Temp."){
|
||||
var temperature = Math.floor(E.getTemperature());
|
||||
g.drawString("TEMP:", 30, y);
|
||||
g.drawString(temperature + "C", 69, y);
|
||||
text = "TEMP";
|
||||
value = Math.floor(E.getTemperature()) + "C";
|
||||
|
||||
} else if(key == "HRM"){
|
||||
g.drawString("HRM:", 30, y);
|
||||
g.drawString(hrmValue, 69, y);
|
||||
text = "HRM";
|
||||
value = hrmValue;
|
||||
|
||||
} else if (key == "VREF"){
|
||||
text = "VREF";
|
||||
value = E.getAnalogVRef().toFixed(2) + "V";
|
||||
|
||||
}
|
||||
|
||||
g.setColor(c);
|
||||
g.fillRect(133, y-2, 165 ,y+18);
|
||||
g.fillCircle(161, y+8, 10);
|
||||
g.setColor(cBlack);
|
||||
g.drawString(text, 135, y);
|
||||
|
||||
g.setColor(c);
|
||||
g.setFontAlign(1,-1,0);
|
||||
g.drawString(value, 130, y);
|
||||
}
|
||||
|
||||
function drawHorizontalBgLine(color, x1, x2, y, h){
|
||||
g.setColor(color);
|
||||
|
||||
for(var i=0; i<h; i++){
|
||||
g.drawLine(x1, y+i, x2,y+i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawLock(){
|
||||
if(lcarsViewPos != 0){
|
||||
return;
|
||||
}
|
||||
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cOrange);
|
||||
g.clearRect(120, 10, g.getWidth(), 75);
|
||||
g.drawString("LCARS", 128, 13);
|
||||
if(connected){
|
||||
g.drawString("CONN", 128, 33);
|
||||
} else {
|
||||
g.drawString("NOT FOUND", 30, y);
|
||||
g.drawString("NOCON", 128, 33);
|
||||
}
|
||||
if(Bangle.isLocked()){
|
||||
g.setColor(cPurple);
|
||||
g.drawString("LOCK", 128, 53);
|
||||
}
|
||||
}
|
||||
|
||||
function drawState(){
|
||||
if(lcarsViewPos != 0){
|
||||
return;
|
||||
}
|
||||
|
||||
g.clearRect(20, 93, 77, 170);
|
||||
g.setColor(cWhite);
|
||||
var bat = E.getBattery();
|
||||
var current = new Date();
|
||||
var hours = current.getHours();
|
||||
|
||||
if(!isAlarmEnabled()){
|
||||
var iconImg =
|
||||
Bangle.isCharging() ? iconCharging :
|
||||
bat < 30 ? iconNoBattery :
|
||||
Bangle.isGPSOn() ? iconSatellite :
|
||||
hours % 4 == 0 ? iconSaturn :
|
||||
hours % 4 == 1 ? iconMars :
|
||||
hours % 4 == 2 ? iconMoon :
|
||||
iconEarth;
|
||||
g.drawImage(iconImg, 29, 104);
|
||||
} else {
|
||||
// Alarm within symbol
|
||||
g.setFontAntonioMedium();
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor(cOrange);
|
||||
g.drawString("ALARM", 29+25, 107);
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(getAlarmMinutes(), 29+25, 107+35);
|
||||
}
|
||||
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
}
|
||||
|
||||
|
||||
function drawPosition0(){
|
||||
// Draw background image
|
||||
g.drawImage(bgLeft, 0, 0);
|
||||
drawHorizontalBgLine(cBlue, 25, 120, 0, 4);
|
||||
drawHorizontalBgLine(cBlue, 130, 176, 0, 4);
|
||||
drawHorizontalBgLine(cPurple, 20, 70, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 80, 176, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 35, 110, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 120, 176, 87, 4);
|
||||
|
||||
// The last line is a battery indicator too
|
||||
var bat = E.getBattery() / 100.0;
|
||||
var batX2 = parseInt((172 - 35) * bat + 35);
|
||||
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
|
||||
drawHorizontalBgLine(cPurple, batX2+10, 172, 171, 5);
|
||||
|
||||
// Draw logo
|
||||
drawLock();
|
||||
|
||||
// Write time
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setColor(cWhite);
|
||||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 28, 10);
|
||||
|
||||
// Write date
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioMedium();
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
dayStr += " " + currentDate.getDate();
|
||||
dayStr += " " + currentDate.getFullYear();
|
||||
g.drawString(dayStr, 29, 56);
|
||||
|
||||
// Draw data
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setColor(cWhite);
|
||||
printData(settings.dataRow1, 97, cOrange);
|
||||
printData(settings.dataRow2, 122, cPurple);
|
||||
printData(settings.dataRow3, 147, cBlue);
|
||||
|
||||
// Draw state
|
||||
drawState();
|
||||
}
|
||||
|
||||
function drawPosition1(){
|
||||
// Draw background image
|
||||
g.drawImage(bgRight, 149, 0);
|
||||
drawHorizontalBgLine(cBlue, 0, 140, 0, 4);
|
||||
drawHorizontalBgLine(cPurple, 0, 80, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 90, 150, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 0, 50, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 60, 140, 87, 4);
|
||||
drawHorizontalBgLine(cOrange, 0, 150, 171, 5);
|
||||
|
||||
// Draw steps bars
|
||||
g.setColor(cWhite);
|
||||
let health;
|
||||
|
||||
try {
|
||||
health = require("health");
|
||||
} catch(ex) {
|
||||
g.setFontAntonioMedium();
|
||||
g.drawString("MODULE HEALTH", 20, 110);
|
||||
g.drawString("REQUIRED.", 20, 130);
|
||||
g.drawString("MODULE HEALTH", 20, 20);
|
||||
g.drawString("REQUIRED.", 20, 40);
|
||||
return;
|
||||
}
|
||||
|
||||
// Plot HRM graph
|
||||
if(plotWeek){
|
||||
var data = new Uint16Array(32);
|
||||
var cnt = new Uint8Array(32);
|
||||
health.readDailySummaries(new Date(), h=>{
|
||||
data[h.day]+=h.bpm;
|
||||
if (h.bpm) cnt[h.day]++;
|
||||
});
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 5,
|
||||
gridy : 100,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 25
|
||||
});
|
||||
|
||||
// Plot step graph
|
||||
var data = new Uint16Array(32);
|
||||
health.readDailySummaries(new Date(), h=>data[h.day]+=h.steps/1000);
|
||||
var gridY = parseInt(Math.max.apply(Math, data)/2);
|
||||
gridY = gridY <= 0 ? 1 : gridY;
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 5,
|
||||
gridy : gridY,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 115
|
||||
});
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("WEEK HRM", 154, 27);
|
||||
g.drawString("WEEK STEPS [K]", 154, 115);
|
||||
|
||||
// Plot day
|
||||
} else {
|
||||
var data = new Uint16Array(24);
|
||||
var cnt = new Uint8Array(24);
|
||||
health.readDay(new Date(), h=>{
|
||||
data[h.hr]+=h.bpm;
|
||||
if (h.bpm) cnt[h.hr]++;
|
||||
});
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 4,
|
||||
gridy : 100,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 25
|
||||
});
|
||||
|
||||
// Plot step graph
|
||||
var data = new Uint16Array(24);
|
||||
health.readDay(new Date(), h=>data[h.hr]+=h.steps);
|
||||
var gridY = parseInt(Math.max.apply(Math, data)/1000)*1000;
|
||||
gridY = gridY <= 0 ? 1000 : gridY;
|
||||
require("graph").drawBar(g, data, {
|
||||
axes : true,
|
||||
minx: 1,
|
||||
gridx : 4,
|
||||
gridy : gridY,
|
||||
width : 140,
|
||||
height : 50,
|
||||
x: 5,
|
||||
y: 115
|
||||
});
|
||||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("DAY HRM", 154, 27);
|
||||
g.drawString("DAY STEPS", 154, 115);
|
||||
}
|
||||
}
|
||||
|
||||
function draw(){
|
||||
|
||||
// First handle alarm to show this correctly afterwards
|
||||
handleAlarm();
|
||||
|
||||
// Next draw the watch face
|
||||
g.reset();
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
// Draw background image
|
||||
g.drawImage(backgroundImage, 0, 24);
|
||||
|
||||
// Draw symbol
|
||||
var bat = E.getBattery();
|
||||
var timeInMinutes = getCurrentTimeInMinutes();
|
||||
|
||||
var iconImg =
|
||||
isAlarmEnabled() ? iconAlarm :
|
||||
Bangle.isCharging() ? iconCharging :
|
||||
bat < 30 ? iconNoBattery :
|
||||
Bangle.isGPSOn() ? iconSatellite :
|
||||
timeInMinutes % 4 == 0 ? iconSaturn :
|
||||
timeInMinutes % 4 == 1 ? iconMars :
|
||||
timeInMinutes % 4 == 2 ? iconMoon :
|
||||
iconEarth;
|
||||
g.drawImage(iconImg, 115, 115);
|
||||
|
||||
// Alarm within symbol
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setFontAntonioSmall();
|
||||
g.drawString(iconImg.text, 115+25, 105);
|
||||
if(isAlarmEnabled() > 0){
|
||||
g.drawString(getAlarmMinutes(), 115+25, 115+25);
|
||||
// Draw current lcars position
|
||||
if(lcarsViewPos == 0){
|
||||
drawPosition0();
|
||||
} else if (lcarsViewPos == 1) {
|
||||
drawPosition1();
|
||||
}
|
||||
|
||||
// Write time
|
||||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAlign(0,0,0);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 60, 55);
|
||||
|
||||
// Write date
|
||||
g.setFontAlign(-1,-1, 0);
|
||||
g.setFontAntonioSmall();
|
||||
|
||||
var dayName = locale.dow(currentDate, true).toUpperCase();
|
||||
var day = currentDate.getDate();
|
||||
g.drawString(day, 100, 35);
|
||||
g.drawString(dayName, 100, 55);
|
||||
|
||||
// Draw battery
|
||||
printData(settings.dataRow1, 98);
|
||||
printData(settings.dataRow2, 121);
|
||||
printData(settings.dataRow3, 144);
|
||||
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Step counter via widget
|
||||
*/
|
||||
function getSteps() {
|
||||
if (stepsWidget() !== undefined)
|
||||
return stepsWidget().getSteps();
|
||||
return "???";
|
||||
}
|
||||
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
var steps = 0
|
||||
try {
|
||||
health = require("health");
|
||||
} catch(ex) {
|
||||
return steps;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
health.readDay(new Date(), h=>steps+=h.steps);
|
||||
return steps;
|
||||
}
|
||||
|
||||
/*
|
||||
* HRM Listener
|
||||
*/
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
/*
|
||||
* Handle alarm
|
||||
|
|
@ -228,7 +421,7 @@ function getCurrentTimeInMinutes(){
|
|||
}
|
||||
|
||||
function isAlarmEnabled(){
|
||||
return settings.alarm > 0;
|
||||
return settings.alarm >= 0;
|
||||
}
|
||||
|
||||
function getAlarmMinutes(){
|
||||
|
|
@ -253,65 +446,130 @@ function handleAlarm(){
|
|||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||
.then(() => Bangle.buzz(t, 1));
|
||||
|
||||
.then(() => Bangle.buzz(t, 1))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
|
||||
.then(() => {
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Swipe to set an alarm
|
||||
* Listeners
|
||||
*/
|
||||
Bangle.on('swipe',function(dir) {
|
||||
// Increase alarm
|
||||
if(dir == -1){
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
// Whenever we connect to Gadgetbridge, reading data from
|
||||
// health failed. Therefore, we update and read data from
|
||||
// health iff the connection state did not change.
|
||||
if(connected == NRF.getSecurityStatus().connected) {
|
||||
draw();
|
||||
} else {
|
||||
connected = NRF.getSecurityStatus().connected
|
||||
drawLock();
|
||||
}
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
connected = NRF.getSecurityStatus().connected
|
||||
});
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
drawLock();
|
||||
});
|
||||
|
||||
Bangle.on('charging',function(charging) {
|
||||
drawState();
|
||||
});
|
||||
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmValue = hrm.bpm;
|
||||
});
|
||||
|
||||
|
||||
function increaseAlarm(){
|
||||
if(isAlarmEnabled()){
|
||||
settings.alarm += 5;
|
||||
} else {
|
||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease alarm
|
||||
if(dir == +1){
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
function decreaseAlarm(){
|
||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||
settings.alarm -= 5;
|
||||
} else {
|
||||
settings.alarm = -1;
|
||||
}
|
||||
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
// Thanks to the app "gbmusic" for this code to detect swipes in all 4 directions.
|
||||
Bangle.on("drag", e => {
|
||||
if (!drag) { // start dragging
|
||||
drag = {x: e.x, y: e.y};
|
||||
} else if (!e.b) { // released
|
||||
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||
drag = null;
|
||||
|
||||
// Horizontal swipe
|
||||
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||
if(dx > 0){
|
||||
lcarsViewPos = 0;
|
||||
} else {
|
||||
lcarsViewPos = 1;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
draw();
|
||||
// Vertical swipe
|
||||
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||
if(lcarsViewPos == 0){
|
||||
if(dy > 0){
|
||||
decreaseAlarm();
|
||||
} else {
|
||||
increaseAlarm();
|
||||
}
|
||||
|
||||
// Update alarm state
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
// Only update the state and return to
|
||||
// avoid a full draw as this is much faster.
|
||||
drawState();
|
||||
return;
|
||||
}
|
||||
|
||||
if(lcarsViewPos == 1){
|
||||
plotWeek = dy < 0 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Stop updates when LCD is off, restart when on
|
||||
* Lets start widgets, listen for btn etc.
|
||||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets - needed by draw
|
||||
Bangle.loadWidgets();
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw();
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
Bangle.drawWidgets();
|
||||
// Bangle.drawWidgets();
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
|
@ -18,14 +18,14 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var data_options = ['Battery', 'Steps', 'Temp.', "HRM"];
|
||||
var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'LCARS Clock' },
|
||||
'< Back': back,
|
||||
'Row 1': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow1),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow1 = data_options[v];
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
},
|
||||
'Row 2': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow2),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow2 = data_options[v];
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
'Row 3': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow3),
|
||||
min: 0, max: 3,
|
||||
min: 0, max: 4,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow3 = data_options[v];
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -457,12 +457,12 @@ var locales = {
|
|||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
|
||||
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
|
||||
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
|
||||
abday: "dom,lun,mar,mer,gio,ven,sab",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì,sabato",
|
||||
trans: { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }
|
||||
},
|
||||
"it_IT": {
|
||||
|
|
@ -475,12 +475,12 @@ var locales = {
|
|||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
|
||||
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
|
||||
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
|
||||
abday: "dom,lun,mar,mer,gio,ven,sab",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato",
|
||||
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì,sabato",
|
||||
trans: { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }
|
||||
},
|
||||
"wae_CH": {
|
||||
|
|
@ -589,7 +589,7 @@ var locales = {
|
|||
month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec",
|
||||
abday: "ne,po,út,st,čt,pá,so",
|
||||
day: "neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota",
|
||||
trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "na", off: "poza" }
|
||||
trans: { yes: "ano", Yes: "Ano", no: "ne", No: "Ne", ok: "ok", on: "zap", off: "vyp" }
|
||||
},
|
||||
"sl_SI": {
|
||||
lang: "sl_SI",
|
||||
|
|
|
|||
|
|
@ -21,3 +21,5 @@
|
|||
Add 'Delete All' option to message options
|
||||
Now update correctly when 'require("messages").clearAll()' is called
|
||||
0.14: Hide widget when all unread notifications are dismissed from phone
|
||||
0.15: Don't buzz when Quiet Mode is active
|
||||
0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ 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 && msg.new) {
|
||||
if (msg && msg.new && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) {
|
||||
if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
|
||||
else Bangle.buzz();
|
||||
}
|
||||
|
|
@ -243,7 +243,8 @@ function showMessage(msgid) {
|
|||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
|
||||
}});
|
||||
}
|
||||
lines = g.wrapString(msg.body, g.getWidth()-10);
|
||||
var bodyFont = fontMedium;
|
||||
lines = g.setFont(bodyFont).wrapString(msg.body, g.getWidth()-10);
|
||||
var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n");
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"h", fillx:1, bgCol:colBg, c: [
|
||||
|
|
@ -256,7 +257,7 @@ function showMessage(msgid) {
|
|||
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
|
||||
]},
|
||||
]},
|
||||
{type:"txt", font:fontMedium, label:body, fillx:1, filly:1, pad:2 },
|
||||
{type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2 },
|
||||
{type:"h",fillx:1, c: buttons}
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ exports.pushMessage = function(event) {
|
|||
// otherwise load messages/show widget
|
||||
var loadMessages = Bangle.CLOCK || event.important;
|
||||
// first, buzz
|
||||
if (loadMessages && global.WIDGETS && WIDGETS.messages)
|
||||
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||
if (!quiet && loadMessages && global.WIDGETS && WIDGETS.messages)
|
||||
WIDGETS.messages.buzz();
|
||||
// after a delay load the app, to ensure we have all the messages
|
||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||
|
|
@ -51,7 +52,7 @@ exports.pushMessage = function(event) {
|
|||
exports.messageTimeout = undefined;
|
||||
// if we're in a clock or it's important, go straight to messages app
|
||||
if (loadMessages) return load("messages.app.js");
|
||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
WIDGETS.messages.show();
|
||||
}, 500);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
WIDGETS["messages"].width=0;
|
||||
Bangle.drawWidgets();
|
||||
},buzz:function() {
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode
|
||||
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
|
||||
function b() {
|
||||
var c = v[0];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Andreas Rozek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Minimal Analog Clock #
|
||||
|
||||
This app displays the perhaps most basic analog clock one can think of - just
|
||||
some clock hands and no clock face. It considers the currently configured
|
||||
"theme" (and may therefore look different than shown in the screenshot on your
|
||||
watch depending on which theme you prefer).
|
||||
|
||||

|
||||
|
||||
This clock also acts as an example for the building blocks found in the author's
|
||||
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/AEBhCjgCBgeAgF8AoXggHwCIXwgfADAX8h4TBAAM+jwkDj/4AocPDwIACgdgBYgoCAAMEuB+/AH4="))
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,230 @@
|
|||
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
|
||||
|
||||
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
/**** updateClockFaceSize ****/
|
||||
|
||||
function updateClockFaceSize () {
|
||||
CenterX = ScreenWidth/2;
|
||||
CenterY = ScreenHeight/2;
|
||||
|
||||
outerRadius = Math.min(CenterX,CenterY) * 0.9;
|
||||
|
||||
if (global.WIDGETS == null) { return; }
|
||||
|
||||
let WidgetLayouts = {
|
||||
tl:{ x:0, y:0, Direction:0 },
|
||||
tr:{ x:ScreenWidth-1, y:0, Direction:1 },
|
||||
bl:{ x:0, y:ScreenHeight-24, Direction:0 },
|
||||
br:{ x:ScreenWidth-1, y:ScreenHeight-24, Direction:1 }
|
||||
};
|
||||
|
||||
for (let Widget of WIDGETS) {
|
||||
let WidgetLayout = WidgetLayouts[Widget.area]; // reference, not copy!
|
||||
if (WidgetLayout == null) { continue; }
|
||||
|
||||
Widget.x = WidgetLayout.x - WidgetLayout.Direction * Widget.width;
|
||||
Widget.y = WidgetLayout.y;
|
||||
|
||||
WidgetLayout.x += Widget.width * (1-2*WidgetLayout.Direction);
|
||||
}
|
||||
|
||||
let x,y, dx,dy;
|
||||
let cx = CenterX, cy = CenterY, r = outerRadius, r2 = r*r;
|
||||
|
||||
x = WidgetLayouts.tl.x; y = WidgetLayouts.tl.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.tr.x; y = WidgetLayouts.tr.y+24; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.bl.x; y = WidgetLayouts.bl.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
x = WidgetLayouts.br.x; y = WidgetLayouts.br.y; dx = x - cx; dy = y - cy;
|
||||
if (dx*dx + dy*dy < r2) {
|
||||
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.sqrt(r2);
|
||||
}
|
||||
|
||||
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
|
||||
}
|
||||
|
||||
updateClockFaceSize();
|
||||
|
||||
/**** custom version of Bangle.drawWidgets (does not clear the widget areas) ****/
|
||||
|
||||
Bangle.drawWidgets = function () {
|
||||
var w = g.getWidth(), h = g.getHeight();
|
||||
|
||||
var pos = {
|
||||
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
||||
tr:{x:w-1, y:0, r:1, c:0},
|
||||
bl:{x:0, y:h-24, r:0, c:0},
|
||||
br:{x:w-1, y:h-24, r:1, c:0}
|
||||
};
|
||||
|
||||
if (global.WIDGETS) {
|
||||
for (var wd of WIDGETS) {
|
||||
var p = pos[wd.area];
|
||||
if (!p) continue;
|
||||
|
||||
wd.x = p.x - p.r*wd.width;
|
||||
wd.y = p.y;
|
||||
|
||||
p.x += wd.width*(1-2*p.r);
|
||||
p.c++;
|
||||
}
|
||||
|
||||
g.reset(); // also loads the current theme
|
||||
|
||||
if (pos.tl.c || pos.tr.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
if (pos.bl.c || pos.br.c) {
|
||||
g.setClipRect(0,h-24,w-1,h-1);
|
||||
g.reset(); // also (re)loads the current theme
|
||||
}
|
||||
|
||||
try {
|
||||
for (wd of WIDGETS) {
|
||||
g.clearRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
||||
wd.draw(wd);
|
||||
}
|
||||
} catch (e) { print(e); }
|
||||
}
|
||||
};
|
||||
|
||||
let HourHandLength = outerRadius * 0.5;
|
||||
let HourHandWidth = 2*5, halfHourHandWidth = HourHandWidth/2;
|
||||
|
||||
let MinuteHandLength = outerRadius * 0.7;
|
||||
let MinuteHandWidth = 2*3, halfMinuteHandWidth = MinuteHandWidth/2;
|
||||
|
||||
let SecondHandLength = outerRadius * 0.9;
|
||||
let SecondHandOffset = halfHourHandWidth + 10;
|
||||
|
||||
let outerBoltRadius = halfHourHandWidth + 2, innerBoltRadius = outerBoltRadius - 4;
|
||||
let HandOffset = outerBoltRadius + 4;
|
||||
|
||||
let twoPi = 2*Math.PI, deg2rad = Math.PI/180;
|
||||
let Pi = Math.PI;
|
||||
let halfPi = Math.PI/2;
|
||||
|
||||
let sin = Math.sin, cos = Math.cos;
|
||||
|
||||
let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1];
|
||||
|
||||
let HandPolygon = [
|
||||
-sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3],
|
||||
sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0],
|
||||
sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3],
|
||||
-sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0],
|
||||
];
|
||||
|
||||
let HourHandPolygon = new Array(HandPolygon.length);
|
||||
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
|
||||
HourHandPolygon[i] = halfHourHandWidth*HandPolygon[i];
|
||||
HourHandPolygon[i+1] = halfHourHandWidth*HandPolygon[i+1];
|
||||
if (i < l/2) { HourHandPolygon[i+1] -= HourHandLength; }
|
||||
if (i > l/2) { HourHandPolygon[i+1] += HandOffset; }
|
||||
}
|
||||
let MinuteHandPolygon = new Array(HandPolygon.length);
|
||||
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
|
||||
MinuteHandPolygon[i] = halfMinuteHandWidth*HandPolygon[i];
|
||||
MinuteHandPolygon[i+1] = halfMinuteHandWidth*HandPolygon[i+1];
|
||||
if (i < l/2) { MinuteHandPolygon[i+1] -= MinuteHandLength; }
|
||||
if (i > l/2) { MinuteHandPolygon[i+1] += HandOffset; }
|
||||
}
|
||||
|
||||
/**** transforme polygon ****/
|
||||
|
||||
let transformedPolygon = new Array(HandPolygon.length);
|
||||
|
||||
function transformPolygon (originalPolygon, OriginX,OriginY, Phi) {
|
||||
let sPhi = sin(Phi), cPhi = cos(Phi), x,y;
|
||||
|
||||
for (let i = 0, l = originalPolygon.length; i < l; i+=2) {
|
||||
x = originalPolygon[i];
|
||||
y = originalPolygon[i+1];
|
||||
|
||||
transformedPolygon[i] = OriginX + x*cPhi + y*sPhi;
|
||||
transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi;
|
||||
}
|
||||
}
|
||||
|
||||
/**** draw clock hands ****/
|
||||
|
||||
function drawClockHands () {
|
||||
let now = new Date();
|
||||
|
||||
let Hours = now.getHours() % 12;
|
||||
let Minutes = now.getMinutes();
|
||||
let Seconds = now.getSeconds();
|
||||
|
||||
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
|
||||
let MinutesAngle = (Minutes/60) * twoPi - Pi;
|
||||
let SecondsAngle = (Seconds/60) * twoPi - Pi;
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
||||
g.fillPoly(transformedPolygon);
|
||||
|
||||
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
|
||||
|
||||
g.setColor(g.theme.fg2);
|
||||
g.drawLine(
|
||||
CenterX + SecondHandOffset*sPhi,
|
||||
CenterY - SecondHandOffset*cPhi,
|
||||
CenterX - SecondHandLength*sPhi,
|
||||
CenterY + SecondHandLength*cPhi
|
||||
);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(CenterX,CenterY, outerBoltRadius);
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
g.drawCircle(CenterX,CenterY, outerBoltRadius);
|
||||
g.fillCircle(CenterX,CenterY, innerBoltRadius);
|
||||
}
|
||||
|
||||
/**** refreshDisplay ****/
|
||||
|
||||
let Timer;
|
||||
function refreshDisplay () {
|
||||
g.clear(true); // also loads current theme
|
||||
|
||||
Bangle.drawWidgets();
|
||||
|
||||
drawClockHands();
|
||||
|
||||
let Pause = 1000 - (Date.now() % 1000);
|
||||
Timer = setTimeout(refreshDisplay,Pause);
|
||||
}
|
||||
|
||||
setTimeout(refreshDisplay, 500); // enqueue first draw request
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
|
||||
refreshDisplay();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
Bangle.setUI('clock');
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
E.showMenu = function(items) {
|
||||
function RectRnd(x1,y1,x2,y2,r) {
|
||||
pp = [];
|
||||
pp.push.apply(pp,g.quadraticBezier([x2-r,y1, x2,y1,x2,y1+r]));
|
||||
pp.push.apply(pp,g.quadraticBezier([x2,y2-r,x2,y2,x2-r,y2]));
|
||||
pp.push.apply(pp,g.quadraticBezier([x1+r,y2,x1,y2,x1,y2-r]));
|
||||
pp.push.apply(pp,g.quadraticBezier([x1,y1+r,x1,y1,x1+r,y1]));
|
||||
return pp;
|
||||
}
|
||||
function fillRectRnd(x1,y1,x2,y2,r,c) {
|
||||
g.setColor(c);
|
||||
g.fillPoly(RectRnd(x1,y1,x2,y2,r),1);
|
||||
g.setColor(255,255,255);
|
||||
}
|
||||
function drawRectRnd(x1,y1,x2,y2,r,c) {
|
||||
g.setColor(c);
|
||||
g.drawPoly(RectRnd(x1,y1,x2,y2,r),1);
|
||||
g.setColor(255,255,255);
|
||||
}
|
||||
g.reset().clearRect(Bangle.appRect); // clear if no menu supplied
|
||||
Bangle.setLCDPower(1); // ensure screen is on
|
||||
if (!items) {
|
||||
Bangle.setUI();
|
||||
return;
|
||||
}
|
||||
var menuItems = Object.keys(items);
|
||||
var options = items[""];
|
||||
if (options) menuItems.splice(menuItems.indexOf(""),1);
|
||||
if (!(options instanceof Object)) options = {};
|
||||
options.fontHeight = options.fontHeight||35;
|
||||
if (options.selected === undefined)
|
||||
options.selected = 0;
|
||||
if (!options.fontHeight)
|
||||
options.fontHeight = 6;
|
||||
var ar = Bangle.appRect;
|
||||
var x = ar.x;
|
||||
var x2 = ar.x2-11; // padding at side for up/down
|
||||
var y = ar.y;
|
||||
var y2 = ar.y2 - 20; // padding at end for arrow
|
||||
if (options.title)
|
||||
y += options.fontHeight+2;
|
||||
var loc = require("locale");
|
||||
var l = {
|
||||
lastIdx : 0,
|
||||
draw : function(rowmin,rowmax) {
|
||||
var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length);
|
||||
var idx = E.clip(options.selected-(rows>>1),0,menuItems.length-rows);
|
||||
if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled
|
||||
l.lastIdx = idx;
|
||||
var iy = y-5;
|
||||
g.reset().setFont('6x8',2).setFontAlign(0,-1,0);
|
||||
if (options.predraw) options.predraw(g);
|
||||
if (rowmin===undefined && options.title) {
|
||||
g.drawString(options.title,(x+x2)/2,y-options.fontHeight+5);
|
||||
g.drawLine(x,y-7,x2,y-7);
|
||||
}
|
||||
if (rowmin!==undefined) {
|
||||
if (idx<rowmin) {
|
||||
iy += options.fontHeight*(rowmin-idx);
|
||||
idx=rowmin;
|
||||
}
|
||||
if (idx+rows>rowmax) {
|
||||
rows = 1+rowmax-rowmin;
|
||||
}
|
||||
}
|
||||
while (rows--) {
|
||||
var name = menuItems[idx];
|
||||
var item = items[name];
|
||||
var hl = (idx==options.selected && !l.selectEdit);
|
||||
if(g.theme.dark){
|
||||
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+20);
|
||||
}else{
|
||||
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-10);
|
||||
}
|
||||
g.setColor(hl ? g.theme.fgH : g.theme.fg);
|
||||
g.setFontAlign(-1,-1);
|
||||
if(loc.translate(name).length >= 11 && "object" == typeof item){
|
||||
var v = item.value;
|
||||
if (item.format) v=item.format(v);
|
||||
v = loc.translate(""+v);
|
||||
g.drawString(loc.translate(name).substring(0, 15-v.length)+"...",x+8,iy+7);
|
||||
}else{
|
||||
if(loc.translate(name).length >= 15){
|
||||
g.drawString(loc.translate(name).substring(0, 15)+"...",x+8,iy+7);
|
||||
}else{
|
||||
g.drawString(loc.translate(name),x+8,iy+7);
|
||||
}
|
||||
}
|
||||
if ("object" == typeof item) {
|
||||
var xo = x2;
|
||||
var v = item.value;
|
||||
if (item.format) v=item.format(v);
|
||||
v = loc.translate(""+v);
|
||||
if (l.selectEdit && idx==options.selected) {
|
||||
xo -= 24 + 1;
|
||||
g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2});
|
||||
}
|
||||
g.setFontAlign(1,-1);
|
||||
g.drawString(v,xo-4,iy+8.5);
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
iy += options.fontHeight;
|
||||
idx++;
|
||||
}
|
||||
g.setFontAlign(-1,-1);
|
||||
var more = idx<menuItems.length;
|
||||
g.drawImage("\b\b\x01\x108|\xFE\x10\x10\x10\x10"/*E.toString(8,8,1,
|
||||
0b00010000,
|
||||
0b00111000,
|
||||
0b01111100,
|
||||
0b11111110,
|
||||
0b00010000,
|
||||
0b00010000,
|
||||
0b00010000,
|
||||
0b00010000
|
||||
)*/,x2+2,40);
|
||||
g.drawImage("\b\b\x01\x10\x10\x10\x10\xFE|8\x10"/*E.toString(8,8,1,
|
||||
0b00010000,
|
||||
0b00010000,
|
||||
0b00010000,
|
||||
0b00010000,
|
||||
0b11111110,
|
||||
0b01111100,
|
||||
0b00111000,
|
||||
0b00010000
|
||||
)*/,x2+2,194);
|
||||
g.drawImage("\b\b\x01\x00\b\f\x0E\xFF\x0E\f\b"/*E.toString(8,8,1,
|
||||
0b00000000,
|
||||
0b00001000,
|
||||
0b00001100,
|
||||
0b00001110,
|
||||
0b11111111,
|
||||
0b00001110,
|
||||
0b00001100,
|
||||
0b00001000
|
||||
)*/,x2+2,116);
|
||||
g.setColor(more?g.theme.fg:g.theme.bg).fillPoly([104,220,136,220,120,228]);
|
||||
g.flip();
|
||||
},
|
||||
select : function() {
|
||||
var item = items[menuItems[options.selected]];
|
||||
if ("function" == typeof item) item(l);
|
||||
else if ("object" == typeof item) {
|
||||
// if a number, go into 'edit mode'
|
||||
if ("number" == typeof item.value)
|
||||
l.selectEdit = l.selectEdit?undefined:item;
|
||||
else { // else just toggle bools
|
||||
if ("boolean" == typeof item.value) item.value=!item.value;
|
||||
if (item.onchange) item.onchange(item.value);
|
||||
}
|
||||
l.draw();
|
||||
}
|
||||
},
|
||||
move : function(dir) {
|
||||
var item = l.selectEdit;
|
||||
if (item) {
|
||||
item = l.selectEdit;
|
||||
item.value -= (dir||1)*(item.step||1);
|
||||
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
|
||||
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
|
||||
if (item.onchange) item.onchange(item.value);
|
||||
l.draw(options.selected,options.selected);
|
||||
} else {
|
||||
var lastSelected=options.selected;
|
||||
options.selected = (dir+options.selected+menuItems.length)%menuItems.length;
|
||||
l.draw(Math.min(lastSelected,options.selected), Math.max(lastSelected,options.selected));
|
||||
}
|
||||
}
|
||||
};
|
||||
l.draw();
|
||||
Bangle.setUI("updown",dir => {
|
||||
if (dir) l.move(dir);
|
||||
else l.select();
|
||||
});
|
||||
return l;
|
||||
};
|
||||
|
After Width: | Height: | Size: 251 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwyBC/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFE+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AAM+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AAM+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ADIA=="))
|
||||
|
|
@ -3,3 +3,5 @@
|
|||
0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns.
|
||||
0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.
|
||||
0.11: Respect theme colors. Fix: Do not pollute global space with internal variables ans functions in boot.js
|
||||
0.12: Improve pattern detection code readability by PaddeK http://forum.espruino.com/profiles/117930/
|
||||
0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/
|
||||
|
|
@ -29,12 +29,13 @@ Then launch the linked apps directly from the clock screen by simply drawing the
|
|||
## Detailed Steps
|
||||
|
||||
From the main menu you can:
|
||||
|
||||
- Add a new pattern and link it to an app (first entry)
|
||||
- To create a new pattern first select "Add Pattern"
|
||||
- Now draw any pattern you like, this will later launch the linked app from the clock screen
|
||||
- You can also draw a single-circle pattern (meaning a single tap on one circle) instead of drawing a 'complex' pattern
|
||||
- If you don't like the pattern, simply re-draw it. The previous pattern will be discarded.
|
||||
- If you are happy with the pattern tap on screen or press the button to continue
|
||||
- If you are happy with the pattern press the button to continue
|
||||
- Now select the app you want to launch with the pattern.
|
||||
- Note, you can bind multiple patterns to the same app.
|
||||
- Manage created patterns (second entry)
|
||||
|
|
@ -47,14 +48,22 @@ From the main menu you can:
|
|||
|
||||
## FAQ
|
||||
|
||||
1) Nothing happens when I draw on the clock screen!
|
||||
1. Nothing happens when I draw on the clock screen!
|
||||
|
||||
Please double-check if you actually have a pattern linked to an app.
|
||||
|
||||
2) I have a pattern linked to an app and still nothing happens when I draw on the clock screen!
|
||||
2. I have a pattern linked to an app and still nothing happens when I draw on the clock screen!
|
||||
|
||||
Make sure the watch is unlocked before you start drawing. If this bothers you, you can permanently disable the watch-lock from within the Pattern Launcher app (via the Settings).
|
||||
|
||||
3) I have done all that and still nothing happens!
|
||||
3. I have done all that and still nothing happens!
|
||||
|
||||
Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry!
|
||||
|
||||
## Authors
|
||||
|
||||
Initial creation: [crazysaem](https://github.com/crazysaem)
|
||||
|
||||
Improve pattern detection code readability: [PaddeK](http://forum.espruino.com/profiles/117930/)
|
||||
|
||||
Improve pattern rendering: [HughB](http://forum.espruino.com/profiles/167235/)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.6 KiB |
|
|
@ -114,7 +114,6 @@ var showMainMenu = () => {
|
|||
E.showMenu(mainmenu);
|
||||
};
|
||||
|
||||
var positions = [];
|
||||
var recognizeAndDrawPattern = () => {
|
||||
return new Promise((resolve) => {
|
||||
E.showMenu();
|
||||
|
|
@ -135,150 +134,55 @@ var recognizeAndDrawPattern = () => {
|
|||
resolve(pattern.join(""));
|
||||
};
|
||||
setWatch(() => finishHandler(), BTN);
|
||||
setTimeout(() => Bangle.on("tap", finishHandler), 250);
|
||||
// setTimeout(() => Bangle.on("tap", finishHandler), 250);
|
||||
|
||||
positions = [];
|
||||
var positions = [];
|
||||
var getPattern = (positions) => {
|
||||
var circles = [
|
||||
{ x: 25, y: 25, i: 0 },
|
||||
{ x: 87, y: 25, i: 1 },
|
||||
{ x: 150, y: 25, i: 2 },
|
||||
{ x: 25, y: 87, i: 3 },
|
||||
{ x: 87, y: 87, i: 4 },
|
||||
{ x: 150, y: 87, i: 5 },
|
||||
{ x: 25, y: 150, i: 6 },
|
||||
{ x: 87, y: 150, i: 7 },
|
||||
{ x: 150, y: 150, i: 8 },
|
||||
];
|
||||
return positions.reduce((pattern, p, i, arr) => {
|
||||
var idx = circles.findIndex((c) => {
|
||||
var dx = p.x > c.x ? p.x - c.x : c.x - p.x;
|
||||
if (dx > CIRCLE_RADIUS) {
|
||||
return false;
|
||||
}
|
||||
var dy = p.y > c.y ? p.y - c.y : c.y - p.y;
|
||||
if (dy > CIRCLE_RADIUS) {
|
||||
return false;
|
||||
}
|
||||
if (dx + dy <= CIRCLE_RADIUS) {
|
||||
return true;
|
||||
}
|
||||
return dx * dx + dy * dy <= CIRCLE_RADIUS_2;
|
||||
});
|
||||
if (idx >= 0) {
|
||||
pattern += circles[idx].i;
|
||||
circles.splice(idx, 1);
|
||||
}
|
||||
if (circles.length === 0) {
|
||||
arr.splice(1);
|
||||
}
|
||||
return pattern;
|
||||
}, "");
|
||||
};
|
||||
var dragHandler = (position) => {
|
||||
log(position);
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
if (isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This might actually be a 'tap' event.
|
||||
// Use this check in addition to the actual tap handler to make it more reliable
|
||||
if (pattern.length > 0 && positions.length === 2) {
|
||||
if (
|
||||
positions[0].x === positions[1].x &&
|
||||
positions[0].y === positions[1].y
|
||||
) {
|
||||
finishHandler();
|
||||
positions = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
E.showMessage("Calculating...");
|
||||
var t0 = Date.now();
|
||||
|
||||
log(positions.length);
|
||||
|
||||
var circlesClone = cloneCirclesArray();
|
||||
pattern = [];
|
||||
|
||||
var step = Math.floor(positions.length / 100) + 1;
|
||||
|
||||
var p, a, b, circle;
|
||||
|
||||
for (var i = 0; i < positions.length; i += step) {
|
||||
p = positions[i];
|
||||
|
||||
circle = circlesClone[0];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[1];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[2];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[3];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(3, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[4];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(4, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[5];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(5, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[6];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(6, 1);
|
||||
}
|
||||
}
|
||||
circle = circlesClone[7];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(7, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[8];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(8, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
var tx = Date.now();
|
||||
log(tx - t0);
|
||||
positions = [];
|
||||
var t1 = Date.now();
|
||||
log(t1 - t0);
|
||||
|
||||
log("pattern:");
|
||||
log(pattern);
|
||||
|
||||
log("redrawing");
|
||||
if (position.b === 0 || positions.length >= 200) {
|
||||
pattern = getPattern(positions).split("");
|
||||
g.clear();
|
||||
drawCirclesWithPattern(pattern);
|
||||
});
|
||||
positions = [];
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on("drag", dragHandler);
|
||||
});
|
||||
};
|
||||
|
|
@ -488,7 +392,8 @@ var drawCircle = (circle, drawBuffer, scale) => {
|
|||
log("drawing circle");
|
||||
log({ x: x, y: y, r: r });
|
||||
|
||||
drawBuffer.drawCircle(x, y, r);
|
||||
drawBuffer.setColor(0);
|
||||
drawBuffer.fillCircle(x, y, r);
|
||||
};
|
||||
|
||||
var cachedCirclesDrawings = {};
|
||||
|
|
@ -533,8 +438,11 @@ var drawCirclesWithPattern = (pattern, options) => {
|
|||
{ msb: true }
|
||||
);
|
||||
|
||||
CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
|
||||
drawBuffer.setColor(1);
|
||||
drawBuffer.fillRect(0, 0, drawBuffer.getWidth(), drawBuffer.getHeight());
|
||||
|
||||
CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
|
||||
drawBuffer.setColor(1);
|
||||
drawBuffer.setFontAlign(0, 0);
|
||||
drawBuffer.setFont("Vector", 40 * scale);
|
||||
pattern.forEach((circleIndex, patternIndex) => {
|
||||
|
|
@ -545,12 +453,12 @@ var drawCirclesWithPattern = (pattern, options) => {
|
|||
circle.y * scale
|
||||
);
|
||||
});
|
||||
|
||||
image = {
|
||||
width: drawBuffer.getWidth(),
|
||||
height: drawBuffer.getHeight(),
|
||||
bpp: 1,
|
||||
buffer: drawBuffer.buffer,
|
||||
palette: new Uint16Array([g.theme.fg, g.theme.bg], 0, 1),
|
||||
};
|
||||
|
||||
if (enableCaching) {
|
||||
|
|
@ -563,16 +471,6 @@ var drawCirclesWithPattern = (pattern, options) => {
|
|||
g.drawImage(image, offset.x, offset.y);
|
||||
};
|
||||
|
||||
var cloneCirclesArray = () => {
|
||||
var circlesClone = Array(CIRCLES.length);
|
||||
|
||||
for (var i = 0; i < CIRCLES.length; i++) {
|
||||
circlesClone[i] = CIRCLES[i];
|
||||
}
|
||||
|
||||
return circlesClone;
|
||||
};
|
||||
|
||||
//////
|
||||
// misc lib functions
|
||||
//////
|
||||
|
|
@ -583,20 +481,6 @@ var log = (message) => {
|
|||
}
|
||||
};
|
||||
|
||||
var debounceTimeoutId;
|
||||
var debounce = (delay) => {
|
||||
if (debounceTimeoutId) {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
debounceTimeoutId = undefined;
|
||||
resolve();
|
||||
}, delay || 500);
|
||||
});
|
||||
};
|
||||
|
||||
//////
|
||||
// run main function
|
||||
//////
|
||||
|
|
|
|||
|
|
@ -7,16 +7,10 @@
|
|||
};
|
||||
|
||||
var storedPatterns;
|
||||
var positions = [];
|
||||
var dragHandler = (position) => {
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
log(positions.length);
|
||||
|
||||
var CIRCLE_RADIUS = 25;
|
||||
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
|
||||
|
||||
var CIRCLE_RADIUS_2 = Math.pow(CIRCLE_RADIUS, 2);
|
||||
var positions = [];
|
||||
var getPattern = (positions) => {
|
||||
var circles = [
|
||||
{ x: 25, y: 25, i: 0 },
|
||||
{ x: 87, y: 25, i: 1 },
|
||||
|
|
@ -28,107 +22,36 @@
|
|||
{ x: 87, y: 150, i: 7 },
|
||||
{ x: 150, y: 150, i: 8 },
|
||||
];
|
||||
var pattern = [];
|
||||
|
||||
var step = Math.floor(positions.length / 100) + 1;
|
||||
|
||||
var p, a, b, circle;
|
||||
|
||||
for (var i = 0; i < positions.length; i += step) {
|
||||
p = positions[i];
|
||||
|
||||
circle = circles[0];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(0, 1);
|
||||
return positions.reduce((pattern, p, i, arr) => {
|
||||
var idx = circles.findIndex((c) => {
|
||||
var dx = p.x > c.x ? p.x - c.x : c.x - p.x;
|
||||
if (dx > CIRCLE_RADIUS) {
|
||||
return false;
|
||||
}
|
||||
var dy = p.y > c.y ? p.y - c.y : c.y - p.y;
|
||||
if (dy > CIRCLE_RADIUS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
circle = circles[1];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(1, 1);
|
||||
if (dx + dy <= CIRCLE_RADIUS) {
|
||||
return true;
|
||||
}
|
||||
return dx * dx + dy * dy <= CIRCLE_RADIUS_2;
|
||||
});
|
||||
if (idx >= 0) {
|
||||
pattern += circles[idx].i;
|
||||
circles.splice(idx, 1);
|
||||
}
|
||||
|
||||
circle = circles[2];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(2, 1);
|
||||
if (circles.length === 0) {
|
||||
arr.splice(1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circles[3];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(3, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circles[4];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(4, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circles[5];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(5, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circles[6];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(6, 1);
|
||||
}
|
||||
}
|
||||
circle = circles[7];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(7, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circles[8];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circles.splice(8, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
positions = [];
|
||||
|
||||
pattern = pattern.join("");
|
||||
return pattern;
|
||||
}, "");
|
||||
};
|
||||
var dragHandler = (position) => {
|
||||
positions.push(position);
|
||||
if (position.b === 0 || positions.length >= 200) {
|
||||
var pattern = getPattern(positions);
|
||||
log(pattern);
|
||||
|
||||
if (pattern) {
|
||||
if (storedPatterns[pattern]) {
|
||||
|
|
@ -145,21 +68,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var debounceTimeoutId;
|
||||
var debounce = (delay) => {
|
||||
if (debounceTimeoutId) {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
positions = [];
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
debounceTimeoutId = undefined;
|
||||
resolve();
|
||||
}, delay || 500);
|
||||
});
|
||||
};
|
||||
|
||||
var sui = Bangle.setUI;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 941 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.9 KiB |
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Add posibillity to generate Wifi code.
|
||||
0.03: Forces integer scaling and adds more configuration (error correction, description, display)
|
||||
0.04: Allow scanning of QR codes from camera or file
|
||||
0.05: Change brightness on touch
|
||||
|
|
|
|||
|
|
@ -72,8 +72,10 @@
|
|||
<p>Additional options:</p>
|
||||
<input type="checkbox" id="preventIntegerScaling" name="preventIntegerScaling"/>
|
||||
<label for="preventIntegerScaling">Prevent integer scaling</label></br>
|
||||
<input type="checkbox" id="preventBrightnessChangeOnTouch" name="preventBrightnessChangeOnTouch"/>
|
||||
<label for="preventBrightnessChangeOnTouch">Prevent brightness change on touch</label></br>
|
||||
<input type="checkbox" id="boostBacklight" name="boostBacklight"/>
|
||||
<label for="boostBacklight">Set backlight to max. while QR is shown</label></br>
|
||||
<label for="boostBacklight">Set initial backlight to max. while QR is shown</label></br>
|
||||
<input type="checkbox" id="stayOn" name="stayOn"/>
|
||||
<label for="stayOn">Do not lock or dim while showing QR</label></br>
|
||||
<input type="checkbox" id="hideDescription" name="hideDescription"/>
|
||||
|
|
@ -287,6 +289,14 @@
|
|||
}
|
||||
var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true});
|
||||
var app = `var img = ${img};
|
||||
${ document.getElementById("preventBrightnessChangeOnTouch").checked ? '' : `var backlight = 0;
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
backlight += 0.3;
|
||||
if (backlight > 1) backlight = 0;
|
||||
Bangle.setLCDBrightness(backlight);
|
||||
});
|
||||
`}
|
||||
|
||||
${document.getElementById("boostBacklight").checked ? 'Bangle.setLCDBrightness(1);' : ''}
|
||||
${document.getElementById("stayOn").checked ? 'Bangle.setLCDTimeout(0);' : ''}
|
||||
${document.getElementById("hideDescription").checked ? '' : `var content = ${JSON.stringify(content)};`}
|
||||
|
|
@ -294,7 +304,7 @@ g.clear(1).setColor(1,1,1).setBgColor(0,0,0);
|
|||
g.fillRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
g.drawImage(img,(g.getWidth()-img[0])/2,(g.getHeight()-img[1])/2);
|
||||
${ document.getElementById("hideDescription").checked ? '' : `g.setFontAlign(0,0).setFont("6x8").setColor(0,0,0);
|
||||
g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4));
|
||||
g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4);
|
||||
`}
|
||||
g.setColor(1,1,1);
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
Fix interface.html
|
||||
0.03: Fix theme and maps/graphing if no GPS
|
||||
0.04: Multiple bugfixes
|
||||
0.05: Add recording for coresensor
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ You can record
|
|||
* **GPS** GPS Latitude, Longitude and Altitude
|
||||
* **Steps** Steps counted by the step counter
|
||||
* **HR** Heart rate
|
||||
* **Core** CoreTemp body temperature
|
||||
|
||||
**Note:** It is possible for other apps to record information using this app
|
||||
as well. They need to define a `foobar.recorder.js` file - see the `getRecorders`
|
||||
|
|
|
|||
|
|
@ -18,8 +18,15 @@ ${track[0].Heartrate!==undefined ? `<gx:SimpleArrayField name="heartrate" type="
|
|||
<displayName>Heart Rate</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
${track[0].Steps!==undefined ? `<gx:SimpleArrayField name="steps" type="int">
|
||||
<displayName>Step Count</displayName>`:``}
|
||||
</gx:SimpleArrayField>
|
||||
<displayName>Step Count</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
${track[0].Core!==undefined ? `<gx:SimpleArrayField name="core" type="int">
|
||||
<displayName>Core Temp</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
${track[0].Skin!==undefined ? `<gx:SimpleArrayField name="skin" type="int">
|
||||
<displayName>Skin Temp</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
|
||||
</Schema>
|
||||
<Folder>
|
||||
<name>Tracks</name>
|
||||
|
|
@ -37,6 +44,12 @@ ${track.map(pt=>` <gx:value>${0|pt.Heartrate}</gx:value>\n`).join("")
|
|||
${track[0].Steps!==undefined ? `<gx:SimpleArrayData name="steps">
|
||||
${track.map(pt=>` <gx:value>${0|pt.Steps}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>`:``}
|
||||
${track[0].Core!==undefined ? `<gx:SimpleArrayData name="core">
|
||||
${track.map(pt=>` <gx:value>${0|pt.Core}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>`:``}
|
||||
${track[0].Skin!==undefined ? `<gx:SimpleArrayData name="skin">
|
||||
${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>`:``}
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
Bangle.removeListener('GPS', onGPS);
|
||||
Bangle.setGPSPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y)
|
||||
draw : (x,y) => g.setColor(hasFix?"#0ff":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y)
|
||||
};
|
||||
},
|
||||
hrm:function() {
|
||||
|
|
@ -82,6 +82,33 @@
|
|||
draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y)
|
||||
};
|
||||
},
|
||||
|
||||
temp:function() {
|
||||
var core = 0, skin = 0;
|
||||
var hasCore = false;
|
||||
function onCore(c) {
|
||||
core=c.core;
|
||||
skin=c.skin;
|
||||
hasCore = true;
|
||||
}
|
||||
return {
|
||||
name : "Core",
|
||||
fields : ["Core","Skin"],
|
||||
getValues : () => {
|
||||
var r = [core,skin];
|
||||
return r;
|
||||
},
|
||||
start : () => {
|
||||
hasCore = false;
|
||||
Bangle.on('CoreTemp', onCore);
|
||||
},
|
||||
stop : () => {
|
||||
hasCore = false;
|
||||
Bangle.removeListener('CoreTemp', onCore);
|
||||
},
|
||||
draw : (x,y) => g.setColor(hasCore?"#0f0":"#888").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
|
||||
};
|
||||
},
|
||||
steps:function() {
|
||||
var lastSteps = 0;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
{ "id": "scribble",
|
||||
"name": "Scribble",
|
||||
"shortName":"Scribble",
|
||||
"version":"0.01",
|
||||
"description": "A keyboard on your wrist!",
|
||||
"icon": "app.png",
|
||||
"tags": "keyboard, text, scribble",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"scribble.app.js","url":"app.js"},
|
||||
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Corrected variable initialisation
|
||||
0.03: Advertise app name, added screenshots
|
||||
0.04: Advertise bar, GPS, HRM and mag services
|
||||
0.05: Refactored for efficiency, corrected sensor value inaccuracies
|
||||
|
|
@ -124,27 +124,16 @@ function transmitUpdatedSensorData() {
|
|||
isNewMagData = false;
|
||||
}
|
||||
|
||||
NRF.setAdvertising(data, { showName: false, interval: 200 });
|
||||
let interval = 1000 / data.length;
|
||||
NRF.setAdvertising(data, { showName: false, interval: interval });
|
||||
}
|
||||
|
||||
|
||||
// Encode the bar service data to fit in a Bluetooth PDU
|
||||
function encodeBarServiceData() {
|
||||
let tEncoded = Math.round(bar.temperature * 100);
|
||||
let pEncoded = Math.round(bar.pressure * 100);
|
||||
let eEncoded = Math.round(bar.altitude * 100);
|
||||
|
||||
if(bar.temperature < 0) {
|
||||
tEncoded += 0x10000;
|
||||
}
|
||||
if(bar.altitude < 0) {
|
||||
eEncoded += 0x1000000;
|
||||
}
|
||||
|
||||
let t = [ tEncoded & 0xff, (tEncoded >> 8) & 0xff ];
|
||||
let p = [ pEncoded & 0xff, (pEncoded >> 8) & 0xff, (pEncoded >> 16) & 0xff,
|
||||
(pEncoded >> 24) & 0xff ];
|
||||
let e = [ eEncoded & 0xff, (eEncoded >> 8) & 0xff, (eEncoded >> 16) & 0xff ];
|
||||
let t = toByteArray(Math.round(bar.temperature * 100), 2, true);
|
||||
let p = toByteArray(Math.round(bar.pressure * 1000), 4, false);
|
||||
let e = toByteArray(Math.round(bar.altitude * 100), 3, true);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
|
|
@ -157,52 +146,26 @@ function encodeBarServiceData() {
|
|||
|
||||
// Encode the GPS service data using the Location and Speed characteristic
|
||||
function encodeGpsServiceData() {
|
||||
let latEncoded = Math.round(gps.lat * 10000000);
|
||||
let lonEncoded = Math.round(gps.lon * 10000000);
|
||||
let hEncoded = Math.round(gps.course * 100);
|
||||
let sEncoded = Math.round(1000 * gps.speed / 36);
|
||||
|
||||
if(gps.lat < 0) {
|
||||
latEncoded += 0x100000000;
|
||||
}
|
||||
if(gps.lon < 0) {
|
||||
lonEncoded += 0x100000000;
|
||||
}
|
||||
|
||||
let s = [ sEncoded & 0xff, (sEncoded >> 8) & 0xff ];
|
||||
let lat = [ latEncoded & 0xff, (latEncoded >> 8) & 0xff,
|
||||
(latEncoded >> 16) & 0xff, (latEncoded >> 24) & 0xff ];
|
||||
let lon = [ lonEncoded & 0xff, (lonEncoded >> 8) & 0xff,
|
||||
(lonEncoded >> 16) & 0xff, (lonEncoded >> 24) & 0xff ];
|
||||
let h = [ hEncoded & 0xff, (hEncoded >> 8) & 0xff ];
|
||||
let s = toByteArray(Math.round(1000 * gps.speed / 36), 2, false);
|
||||
let lat = toByteArray(Math.round(gps.lat * 10000000), 4, true);
|
||||
let lon = toByteArray(Math.round(gps.lon * 10000000), 4, true);
|
||||
let e = toByteArray(Math.round(gps.alt * 100), 3, true);
|
||||
let h = toByteArray(Math.round(gps.course * 100), 2, false);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x11, 0x16, 0x67, 0x2a, 0x95, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||
lat[3], lon[0], lon[1], lon[2], lon[3], h[0], h[1] // Location and Speed
|
||||
0x14, 0x16, 0x67, 0x2a, 0x9d, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||
lat[3], lon[0], lon[1], lon[2], lon[3], e[0], e[1], e[2], h[0], h[1]
|
||||
// Location and Speed
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Encode the mag service data using the magnetic flux density 3D characteristic
|
||||
function encodeMagServiceData() {
|
||||
let xEncoded = mag.x; // TODO: units???
|
||||
let yEncoded = mag.y;
|
||||
let zEncoded = mag.z;
|
||||
|
||||
if(xEncoded < 0) {
|
||||
xEncoded += 0x10000;
|
||||
}
|
||||
if(yEncoded < 0) {
|
||||
yEncoded += 0x10000;
|
||||
}
|
||||
if(yEncoded < 0) {
|
||||
yEncoded += 0x10000;
|
||||
}
|
||||
|
||||
let x = [ xEncoded & 0xff, (xEncoded >> 8) & 0xff ];
|
||||
let y = [ yEncoded & 0xff, (yEncoded >> 8) & 0xff ];
|
||||
let z = [ zEncoded & 0xff, (zEncoded >> 8) & 0xff ];
|
||||
let x = toByteArray(mag.x, 2, true);
|
||||
let y = toByteArray(mag.y, 2, true);
|
||||
let z = toByteArray(mag.z, 2, true);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
|
|
@ -211,6 +174,22 @@ function encodeMagServiceData() {
|
|||
}
|
||||
|
||||
|
||||
// Convert the given value to a little endian byte array
|
||||
function toByteArray(value, numberOfBytes, isSigned) {
|
||||
let byteArray = new Array(numberOfBytes);
|
||||
|
||||
if(isSigned && (value < 0)) {
|
||||
value += 1 << (numberOfBytes * 8);
|
||||
}
|
||||
|
||||
for(let index = 0; index < numberOfBytes; index++) {
|
||||
byteArray[index] = (value >> (index * 8)) & 0xff;
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
|
||||
// Update acceleration
|
||||
Bangle.on('accel', function(newAcc) {
|
||||
acc = newAcc;
|
||||
|
|
|
|||