Merge branch 'master' into swp2clk
203
apps.json
|
@ -77,7 +77,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -167,7 +167,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.38",
|
||||
"version": "0.39",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -1501,7 +1501,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
@ -4064,7 +4064,7 @@
|
|||
{
|
||||
"id": "hcclock",
|
||||
"name": "Hi-Contrast Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.",
|
||||
"icon": "hcclock-icon.png",
|
||||
"type": "clock",
|
||||
|
@ -4487,7 +4487,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
@ -4652,7 +4652,7 @@
|
|||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
|
@ -4697,6 +4697,8 @@
|
|||
"tags": "tool,timer",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"a_speech_timer.app.js","url":"app.js"},
|
||||
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
|
||||
|
@ -4861,7 +4863,7 @@
|
|||
"id": "ptlaunch",
|
||||
"name": "Pattern Launcher",
|
||||
"shortName": "Pattern Launcher",
|
||||
"version": "0.11",
|
||||
"version": "0.13",
|
||||
"description": "Directly launch apps from the clock screen with custom patterns.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"manage_patterns_light.png"}],
|
||||
|
@ -4875,6 +4877,20 @@
|
|||
],
|
||||
"data": [{"name":"ptlaunch.patterns.json"}]
|
||||
},
|
||||
{ "id": "slimehunt",
|
||||
"name": "Slime Hunt",
|
||||
"shortName":"SlimeHunt",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"description": "Fight against slimes in turn based combat, try to get the highscore!",
|
||||
"tags": "rpg,slime",
|
||||
"supports" : ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"slimehunt.app.js","url":"app.js"},
|
||||
{"name":"slimehunt.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
|
@ -4931,10 +4947,12 @@
|
|||
"id":"awairmonitor",
|
||||
"name":"Awair Monitor",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
|
||||
"tags": "tool,health",
|
||||
"type": "clock",
|
||||
"tags": "clock,tool,health",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
|
@ -5029,9 +5047,10 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"dependencies": {"widpedom":"app"},
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
@ -5047,6 +5066,22 @@
|
|||
{"name":"circlesclock.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.01",
|
||||
"icon": "app.png",
|
||||
"description": "A Minimalist clockface with large Digits. Looks best with the dark theme",
|
||||
"screenshots" : [{"url":"screenshot.png"}],
|
||||
"tags": "clock",
|
||||
"allow_emulator":true,
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"contourclock.app.js","url":"app.js"},
|
||||
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ltherm",
|
||||
"name": "Localized Thermometer",
|
||||
|
@ -5079,5 +5114,153 @@
|
|||
{"name":"swp2clk.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"swp2clk.data.json"}]
|
||||
},
|
||||
{
|
||||
"id":"colorwheel",
|
||||
"name":"Color Wheel",
|
||||
"tags":"app,tool",
|
||||
"version":"0.01",
|
||||
"description":"a tappable wheel of good-looking colors",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"icon":"colorwheel.png",
|
||||
"storage": [
|
||||
{"name":"colorwheel.app.js","url":"app.js"},
|
||||
{"name":"colorwheel.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "minimal_clock",
|
||||
"name": "Minimal Analog Clock",
|
||||
"shortName":"Minimal Clock",
|
||||
"version":"0.03",
|
||||
"description": "a minimal analog clock - just with some hands and no clock face",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"minimal_clock.app.js","url":"app.js"},
|
||||
{"name":"minimal_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "simple_clock",
|
||||
"name": "Simple Analog Clock",
|
||||
"shortName":"Simple Clock",
|
||||
"version":"0.02",
|
||||
"description": "a simple, yet stylish, analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"simple_clock.app.js","url":"app.js"},
|
||||
{"name":"simple_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "colorful_clock",
|
||||
"name": "Colorful Analog Clock",
|
||||
"shortName":"Colorful Clock",
|
||||
"version":"0.02",
|
||||
"description": "a colorful analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"colorful_clock.app.js","url":"app.js"},
|
||||
{"name":"colorful_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "themesetter",
|
||||
"name": "Theme Setter",
|
||||
"shortName":"Theme Setter",
|
||||
"version":"0.04",
|
||||
"description": "a comfortable way to configure theme colors",
|
||||
"icon": "app-icon.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"themesetter.app.js","url":"app.js"},
|
||||
{"name":"themesetter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "widviztime",
|
||||
"name": "Widget Autohide Widget",
|
||||
"shortName": "Viz Time Widget",
|
||||
"version": "0.01",
|
||||
"description": "The widgets will be shown for four seconds after the device is unlocked.",
|
||||
"icon": "eye.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"widviztime.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "supf",
|
||||
"name": "Simple Clock with Date",
|
||||
"shortName": "supf Clock",
|
||||
"version": "0.01",
|
||||
"description": "Simple Clock with seconds and date in custom language. Install 'Languages' to get localized names.",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot_supf.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"supf.app.js","url":"app.js"},
|
||||
{"name":"supf.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "andark",
|
||||
"name": "Analog Dark",
|
||||
"shortName":"AnDark",
|
||||
"version":"0.04",
|
||||
"description": "analog clock face without disturbing widgets",
|
||||
"icon": "andark_icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"andark.app.js","url":"app.js"},
|
||||
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "diract",
|
||||
"name": "DirAct",
|
||||
"shortName": "DirAct",
|
||||
"version": "0.01",
|
||||
"description": "Proximity interaction detection.",
|
||||
"icon": "diract.png",
|
||||
"type": "app",
|
||||
"tags": "tool,sensors",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"allow_emulator": false,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "diract.app.js", "url": "diract.js" },
|
||||
{ "name": "diract.img", "url": "diract-icon.js", "evaluate": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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) {
|
||||
|
@ -65,16 +61,29 @@ function draw() {
|
|||
bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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++) {
|
||||
// max height = 32
|
||||
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);
|
||||
g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+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;
|
||||
|
@ -23,6 +23,8 @@ window.onload = function() {
|
|||
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){
|
||||
|
@ -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,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) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
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 |
|
@ -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 |
|
@ -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) {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
var time = fd[4].substr(0, 5);
|
||||
var date = [fd[0], fd[1], fd[2]].join(" ");
|
||||
return time + " - " + date;
|
||||
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(); });
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
0.04: Inluded LCARS Logo.
|
||||
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.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));
|
||||
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
.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
|
||||
*/
|
||||
Bangle.on('swipe',function(dir) {
|
||||
// Increase alarm
|
||||
if(dir == -1){
|
||||
if(isAlarmEnabled()){
|
||||
settings.alarm += 5;
|
||||
} else {
|
||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease alarm
|
||||
if(dir == +1){
|
||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||
settings.alarm -= 5;
|
||||
} else {
|
||||
settings.alarm = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
draw();
|
||||
|
||||
// Update alarm state
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Stop updates when LCD is off, restart when on
|
||||
* Listeners
|
||||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Vertical swipe
|
||||
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||
if(lcarsViewPos == 0){
|
||||
if(dy > 0){
|
||||
decreaseAlarm();
|
||||
} else {
|
||||
increaseAlarm();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Lets start widgets, listen for btn etc.
|
||||
*/
|
||||
// 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();
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
||||
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
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 |
|
@ -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,4 @@
|
|||
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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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');
|
|
@ -2,4 +2,6 @@
|
|||
0.02: Turn on lcd when launching an app if the lock screen was disabled in the settings
|
||||
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.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,32 +29,41 @@ 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
|
||||
- Now select the app you want to launch with the pattern.
|
||||
- Note, you can bind multiple patterns to the same app.
|
||||
- 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 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)
|
||||
- To manage your patterns first select "Manage Patterns"
|
||||
- You will now see a scrollabe list of patterns + linked apps
|
||||
- If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
|
||||
- To manage your patterns first select "Manage Patterns"
|
||||
- You will now see a scrollabe list of patterns + linked apps
|
||||
- If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
|
||||
- Disable the lock screen on the clock screen from the settings (third entry)
|
||||
- To launch the app from the pattern on the clock screen the watch must be unlocked.
|
||||
- If this annoys you, you can disable the lock on the clock screen from the setting here
|
||||
- To launch the app from the pattern on the clock screen the watch must be unlocked.
|
||||
- If this annoys you, you can disable the lock on the clock screen from the setting here
|
||||
|
||||
## 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
|
||||
//////
|
||||
|
|
|
@ -5,131 +5,54 @@
|
|||
console.log(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var storedPatterns;
|
||||
var CIRCLE_RADIUS = 25;
|
||||
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 },
|
||||
{ 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) => {
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
log(positions.length);
|
||||
|
||||
var CIRCLE_RADIUS = 25;
|
||||
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
|
||||
|
||||
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 },
|
||||
];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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("");
|
||||
|
||||
if (position.b === 0 || positions.length >= 200) {
|
||||
var pattern = getPattern(positions);
|
||||
log(pattern);
|
||||
|
||||
if (pattern) {
|
||||
if (storedPatterns[pattern]) {
|
||||
var app = storedPatterns[pattern].app;
|
||||
|
@ -139,27 +62,15 @@
|
|||
Bangle.setLCDPower(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Bangle.removeListener("drag", dragHandler);
|
||||
load(app.src);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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: 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
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
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;
|
||||
|
|
|
@ -41,3 +41,4 @@
|
|||
0.36: Added 'Utils' menu with helpful utilities for restoring Bangle.js
|
||||
0.37: Going into passkey menu now saves settings with passkey
|
||||
0.38: Restructed menus as per forum discussion
|
||||
0.39: Fix misbehaving debug info option
|
||||
|
|
|
@ -499,6 +499,8 @@ function showUtilMenu() {
|
|||
'< Back': ()=>showMainMenu(),
|
||||
'Debug Info': {
|
||||
value: E.clip(0|settings.log,0,2),
|
||||
min: 0,
|
||||
max: 2,
|
||||
format: v => ["Hide","Show","Log"][E.clip(0|v,0,2)],
|
||||
onchange: v => {
|
||||
settings.log = v;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Simple Analog Clock #
|
||||
|
||||
This app displays a simple, yet stylish, analog clock. 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("mEwwcBIf4AOgPAjgROh/A/+AEZ8DCKH8Gp/4Gp0QCKAARgQRigFACMUICMT7SEcUAkAvK/EAv//BpH8eoYOBAQP//0ECIrvDCIQABj4TB8AREj4RCgIyFn4RJh5HBCJQ1DAA0/UKBuJQZIRgL4wRL4ARhAH4AIg4RQdIwRcnAjiLKIA/ACI="))
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.9 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*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 () {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont('Vector', 22);
|
||||
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString('12', CenterX,CenterY-outerRadius);
|
||||
|
||||
g.setFontAlign(1,0);
|
||||
g.drawString('3', CenterX+outerRadius,CenterY);
|
||||
|
||||
g.setFontAlign(0,1);
|
||||
g.drawString('6', CenterX,CenterY+outerRadius);
|
||||
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString('9', CenterX-outerRadius,CenterY);
|
||||
}
|
||||
|
||||
/**** 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');
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Public version is a go!
|
||||
0.02: Fixed bug where Critial Up wasn't letting player attack.
|
|
@ -0,0 +1,77 @@
|
|||
<><><><>-SLIME HUNT-<><><><>
|
||||
|
||||
Slime Hunt is a RPG turn-based style combat game
|
||||
where you fight slimes until your HP runs out.
|
||||
|
||||
The main goal is to beat your personal highscore!
|
||||
|
||||
============================
|
||||
|
||||
During each fight the player has 3 options,
|
||||
|
||||
BTN1) FIGHT
|
||||
- Attacks the slime, dealing 1 hp worth of damage.
|
||||
|
||||
BTN2) DEFEND
|
||||
- Defends against the slime, blocking 3 damage from the next slime attack.
|
||||
|
||||
BTN3) RUN
|
||||
- Find a new slime to fight against. (This could change in the future!)
|
||||
|
||||
============================
|
||||
|
||||
There are currently 5 types of slime each with unique behavior.
|
||||
|
||||
<><>-BEHAVIORS-<><>
|
||||
|
||||
1. NEUTRAL
|
||||
- Slime deals 0-1 damage on it's next attack.
|
||||
|
||||
2. ANGRY
|
||||
- Slime deals 3-5 damage on it's next attack.
|
||||
|
||||
3. ERACTIC
|
||||
- Slime deals 0-5 damage on it's next attack.
|
||||
|
||||
<><>-ITEMS-<><>
|
||||
|
||||
1. Attack Up
|
||||
- +1 damage next battle.
|
||||
|
||||
2. Defence Up
|
||||
- +1 defence next battle, stacks with block.
|
||||
Setting defence to 4 when using DEFEND, and 1 otherwise.
|
||||
|
||||
3. HP Up
|
||||
- +3 HP.
|
||||
|
||||
4. Block Up
|
||||
- +2 block on DEFEND next battle,
|
||||
setting Defence to 5 when using DEFEND command.
|
||||
|
||||
5. Critical Up
|
||||
- 20% chance to crit next battle on each attack,
|
||||
instantly defeating the Slime.
|
||||
|
||||
*****Using the RUN command causes you to lose your item!*****
|
||||
|
||||
<><>-SLIMES-<><>
|
||||
|
||||
1. GREEN SLIME
|
||||
- Is always neutral. | 0% chance of item.
|
||||
|
||||
2. RED SLIME
|
||||
- Can be either neutral or angry. | 10% chance of item.
|
||||
|
||||
3. GRAY SLIME
|
||||
- Can be neutral, angry or eratic. | 20% chance of item.
|
||||
|
||||
4. YELLOW SLIME
|
||||
- Is always eratic. | 50% chance of item.
|
||||
|
||||
5. PURPLE SLIME
|
||||
- Is always angry. | 100% chance of item.
|
||||
|
||||
============================
|
||||
|
||||
Created by Colton LaChance!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+If4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH9bxgAM1gtsGTwtTGDVhFSPX64wZLqgwFF6iMVGAhgUF6owBMCzsWAAthL1AAGF/4vxrdhADVbeCQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AFA"))
|
|
@ -0,0 +1,477 @@
|
|||
//Create constants------------------------------------------------------------------
|
||||
|
||||
//Slimes
|
||||
const GREEN_SLIME = 1; //Normal slime, is always neutral. | 0% Item chance
|
||||
const PINK_SLIME = 2; //Can get angry. | 10% Item chance
|
||||
const GRAY_SLIME = 3; //Can be neutral, angry or erratic. | 20% Item chance
|
||||
const YELLOW_SLIME = 4; //Is always erratic. | 50% Item chance
|
||||
const PURPLE_SLIME = 5; //Is always angry. | 100% Item chance
|
||||
|
||||
//Items
|
||||
const ITEM_ATK_UP = 1; //Raises damage dealt by +1 for next battle
|
||||
const ITEM_DEF_UP = 2; //Reduces all damage by +1 for next battle
|
||||
const ITEM_HP_UP = 3; //Increases HP by 3
|
||||
const ITEM_BLOCK_UP = 4; //Raises defence when defending by from 3 to 5 for next battle
|
||||
const ITEM_CRIT_UP = 5; //Gives attack a 20% chance to instantly KO slime for next battle
|
||||
|
||||
//Base stats
|
||||
const BASE_ATK = 1;
|
||||
const BASE_DEF = 0;
|
||||
const BASE_BLOCK = 3;
|
||||
const BASE_CRIT = 0;
|
||||
|
||||
//Initialize variables------------------------------------------------------------------
|
||||
var playerHP = 20;
|
||||
var slimeHP = 3;
|
||||
var slimeType = GREEN_SLIME;
|
||||
var turn = 0;
|
||||
var screenWidth = g.getWidth();
|
||||
var screenHeight = g.getHeight();
|
||||
var slimeState = 0;
|
||||
var showBattleResult = false;
|
||||
var dmgDealt = 0;
|
||||
var playerDefence = 0;
|
||||
var playerItem = 0;
|
||||
var critChance = 0;
|
||||
|
||||
//Stats (Modifiers)
|
||||
var statAtk = 1;
|
||||
var statDef = 0;
|
||||
var statBlock = 3;
|
||||
var statCrit = 0;
|
||||
|
||||
//Item vars
|
||||
var itemName = "";
|
||||
var itemDesc = "";
|
||||
var itemChance = 0;
|
||||
|
||||
var refreshInterval;
|
||||
var waitTime = 0;
|
||||
|
||||
var highscore = 0;
|
||||
var score = 0;
|
||||
|
||||
var themeNote = 0;
|
||||
|
||||
//Load files------------------------------------------------------------------
|
||||
var file = require("Storage").open("highscore.txt", "r");
|
||||
highscore = file.readLine();
|
||||
if (highscore == undefined) highscore = 0;
|
||||
|
||||
var greenSlime = require("Storage").read("slime.img");
|
||||
var pinkSlime = require("Storage").read("slimered.img");
|
||||
var graySlime = require("Storage").read("slimegray.img");
|
||||
var yellowSlime = require("Storage").read("slimeyellow.img");
|
||||
var purpleSlime = require("Storage").read("slimepurple.img");
|
||||
|
||||
//UI Stuff------------------------------------------------------------------
|
||||
function drawOpeningUI() {
|
||||
g.clear();
|
||||
g.setFont("Vector", screenWidth / 15);
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.drawString("SLIME HUNT", screenWidth / 2, screenHeight * 0.1);
|
||||
g.drawString("-SCORE TO BEAT-", screenWidth / 2, screenHeight * 0.3);
|
||||
g.drawString("<><><> " + highscore + " <><><>", screenWidth / 2, screenHeight * 0.45);
|
||||
g.setFont("Vector", screenWidth / 20);
|
||||
g.drawString("A Slime approches...", screenWidth / 2, screenHeight * 0.6);
|
||||
wait(8, waitForBattle);
|
||||
}
|
||||
|
||||
function drawSlime() {
|
||||
switch (slimeType) {
|
||||
case GREEN_SLIME:
|
||||
g.drawImage(greenSlime, screenWidth / 2, screenHeight / 2, {
|
||||
scale: 4,
|
||||
rotate: 0
|
||||
});
|
||||
break;
|
||||
case PINK_SLIME:
|
||||
g.drawImage(pinkSlime, screenWidth / 2, screenHeight / 2, {
|
||||
scale: 4,
|
||||
rotate: 0
|
||||
});
|
||||
break;
|
||||
case GRAY_SLIME:
|
||||
g.drawImage(graySlime, screenWidth / 2, screenHeight / 2, {
|
||||
scale: 4,
|
||||
rotate: 0
|
||||
});
|
||||
break;
|
||||
case YELLOW_SLIME:
|
||||
g.drawImage(yellowSlime, screenWidth / 2, screenHeight / 2, {
|
||||
scale: 4,
|
||||
rotate: 0
|
||||
});
|
||||
break;
|
||||
case PURPLE_SLIME:
|
||||
g.drawImage(purpleSlime, screenWidth / 2, screenHeight / 2, {
|
||||
scale: 4,
|
||||
rotate: 0
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function drawBattleUI() {
|
||||
g.clear();
|
||||
g.setFont("Vector", screenWidth / 8);
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.drawString("SLIME HP: " + slimeHP, screenWidth / 2, screenHeight * 0.1);
|
||||
g.setFont("Vector", screenWidth / 20);
|
||||
if (!showBattleResult) {
|
||||
switch (slimeState) {
|
||||
case 0:
|
||||
g.drawString("The slime seems neutral...", screenWidth / 2, screenHeight * 0.25);
|
||||
break;
|
||||
case 1:
|
||||
g.drawString("The slime seems angry...", screenWidth / 2, screenHeight * 0.25);
|
||||
break;
|
||||
case 2:
|
||||
g.drawString("The slime seems eratic...", screenWidth / 2, screenHeight * 0.25);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
var brString = (turn == 0 ? "The Slime loses " : "You lose ");
|
||||
g.drawString(brString + dmgDealt + "HP!", screenWidth / 2, screenHeight * 0.25);
|
||||
}
|
||||
drawSlime();
|
||||
g.drawLine(0, screenHeight * 0.72, screenWidth, screenHeight * 0.72);
|
||||
if (turn == 0) {
|
||||
g.setFont("Vector", screenWidth / 15);
|
||||
g.drawString("Your HP is " + playerHP + ".", screenWidth / 2, screenHeight * 0.8);
|
||||
g.setFont("Vector", screenWidth / 20);
|
||||
g.drawString("(B1) FIGHT\t|\t(B2) DEFEND\t|\t(B3) RUN", screenWidth / 2, screenHeight * 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
//Win / lose functions------------------------------------------------------------------
|
||||
function win() {
|
||||
wait(5, winTheme);
|
||||
calcScore(slimeType);
|
||||
showBattleResult = false;
|
||||
g.clear();
|
||||
g.setFont("Vector", screenWidth / 8);
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.drawString("YOU WON!", screenWidth / 2, screenHeight * 0.1);
|
||||
g.drawLine(0, screenHeight * 0.2, screenWidth, screenHeight * 0.2);
|
||||
g.setFont("Vector", screenWidth / 12);
|
||||
g.drawString((playerItem == 0 ? "No Item." : "GOT ITEM!"), screenWidth / 2, screenHeight * 0.27);
|
||||
g.setFont("Vector", screenWidth / 15);
|
||||
g.drawString((playerItem == 0 ? "" : "<><> " + itemName + " <><>"), screenWidth / 2, screenHeight * 0.40);
|
||||
g.setFont("Vector", screenWidth / 20);
|
||||
g.drawString((playerItem == 0 ? "" : itemDesc), screenWidth / 2, screenHeight * 0.52);
|
||||
g.drawLine(0, screenHeight * 0.6, screenWidth, screenHeight * 0.6);
|
||||
g.drawString("Your score is << " + score + " >>", screenWidth / 2, screenHeight * 0.75);
|
||||
g.drawString("Press (B3) to find another slime!", screenWidth / 2, screenHeight * 0.9);
|
||||
turn = 0;
|
||||
setWatch(run, BTN3);
|
||||
}
|
||||
|
||||
function lose() {
|
||||
wait(5, loseTheme);
|
||||
playerHP = 20;
|
||||
showBattleResult = false;
|
||||
g.clear();
|
||||
g.setFont("Vector", screenWidth / 8);
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.drawString("You lose...", screenWidth / 2, screenHeight * 0.1);
|
||||
g.drawLine(0, screenHeight * 0.2, screenWidth, screenHeight * 0.2);
|
||||
g.setFont("Vector", screenWidth / 12);
|
||||
g.drawString((score > highscore ? "-NEW HIGHSCORE-" : "-SCORE TO BEAT-"), screenWidth / 2, screenHeight * 0.27);
|
||||
g.setFont("Vector", screenWidth / 15);
|
||||
g.drawString((score > highscore ? "<><> " + score + " <><>" : "<><> " + highscore + " <><>"), screenWidth / 2, screenHeight * 0.43);
|
||||
g.drawLine(0, screenHeight * 0.6, screenWidth, screenHeight * 0.6);
|
||||
g.setFont("Vector", screenWidth / 20);
|
||||
g.drawString("Your score is << " + score + " >>", screenWidth / 2, screenHeight * 0.75);
|
||||
g.drawString("Press (B3) to try again...", screenWidth / 2, screenHeight * 0.9);
|
||||
score = 0;
|
||||
turn = 0;
|
||||
setWatch(run, BTN3);
|
||||
}
|
||||
|
||||
//Battle Stuff------------------------------------------------------------------
|
||||
function nextTurn() {
|
||||
turn = (turn == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
function slimeFight() {
|
||||
Bangle.beep(100, 500);
|
||||
switch (slimeState) {
|
||||
case 0:
|
||||
dmgDealt = Math.floor(Math.random() * 2);
|
||||
break;
|
||||
case 1:
|
||||
dmgDealt = Math.floor(Math.random() * 3) + 3;
|
||||
break;
|
||||
case 2:
|
||||
dmgDealt = Math.floor(Math.random() * 6);
|
||||
break;
|
||||
}
|
||||
dmgDealt = Math.max(0, dmgDealt - playerDefence);
|
||||
playerHP -= dmgDealt;
|
||||
slimeAI();
|
||||
}
|
||||
|
||||
function fight() {
|
||||
if (turn == 0 && waitTime <= 0) {
|
||||
Bangle.beep(100, 1000);
|
||||
dmgDealt = statAtk;
|
||||
playerDefence = statDef;
|
||||
if (statCrit == 0) {
|
||||
slimeHP -= dmgDealt;
|
||||
}else{
|
||||
critChance = Math.floor(Math.random() * 100);
|
||||
if (critChance >= 100-statCrit) {
|
||||
slimeHP = 0;
|
||||
dmgDealt = 99;
|
||||
}else{
|
||||
slimeHP -= dmgDealt;
|
||||
}
|
||||
critChance = 0;
|
||||
}
|
||||
showBattleResult = true;
|
||||
drawBattleUI();
|
||||
wait(5, waitForTurn);
|
||||
}
|
||||
}
|
||||
|
||||
function defend() {
|
||||
if (turn == 0 && waitTime <= 0) {
|
||||
dmgDealt = 0;
|
||||
playerDefence = statBlock + statDef;
|
||||
showBattleResult = true;
|
||||
drawBattleUI();
|
||||
wait(5, waitForTurn);
|
||||
}
|
||||
}
|
||||
|
||||
function run() {
|
||||
if (turn == 0 && waitTime <= 0) {
|
||||
showBattleResult = false;
|
||||
Bangle.beep(200, 4000);
|
||||
wait(3, waitForBattle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function newBattle() {
|
||||
showBattleResult = false;
|
||||
slimeType = Math.floor(Math.random() * 5) + 1;
|
||||
useItem(); //Use item at start of new battle
|
||||
switch (slimeType) {
|
||||
case GREEN_SLIME:
|
||||
slimeHP = 3;
|
||||
break;
|
||||
case PINK_SLIME:
|
||||
slimeHP = 3;
|
||||
break;
|
||||
case GRAY_SLIME:
|
||||
slimeHP = 5;
|
||||
break;
|
||||
case YELLOW_SLIME:
|
||||
slimeHP = 5;
|
||||
break;
|
||||
case PURPLE_SLIME:
|
||||
slimeHP = 5;
|
||||
break;
|
||||
}
|
||||
turn = 0;
|
||||
battle();
|
||||
slimeAI();
|
||||
drawBattleUI();
|
||||
}
|
||||
|
||||
function battle() {
|
||||
setWatch(fight, BTN1);
|
||||
setWatch(defend, BTN2);
|
||||
setWatch(run, BTN3);
|
||||
}
|
||||
|
||||
function slimeAI() {
|
||||
switch (slimeType) {
|
||||
case GREEN_SLIME:
|
||||
slimeState = 0;
|
||||
break;
|
||||
case PINK_SLIME:
|
||||
slimeState = Math.floor(Math.random() * 2);
|
||||
break;
|
||||
case GRAY_SLIME:
|
||||
slimeState = Math.floor(Math.random() * 3);
|
||||
break;
|
||||
case YELLOW_SLIME:
|
||||
slimeState = 2;
|
||||
break;
|
||||
case PURPLE_SLIME:
|
||||
slimeState = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Items------------------------------------------------------------------
|
||||
function getItem() {
|
||||
playerItem = Math.floor(Math.random() * 5) + 1;
|
||||
switch (playerItem) {
|
||||
case ITEM_ATK_UP:
|
||||
itemName = "Attack Up";
|
||||
itemDesc = "+1 damage next battle.";
|
||||
break;
|
||||
case ITEM_DEF_UP:
|
||||
itemName = "Defence Up";
|
||||
itemDesc = "+1 defence next battle.";
|
||||
break;
|
||||
case ITEM_HP_UP:
|
||||
itemName = "HP Up";
|
||||
itemDesc = "+3 HP.";
|
||||
break;
|
||||
case ITEM_BLOCK_UP:
|
||||
itemName = "Block Up";
|
||||
itemDesc = "+2 block on DEFEND next battle.";
|
||||
break;
|
||||
case ITEM_CRIT_UP:
|
||||
itemName = "Critical Up";
|
||||
itemDesc = "20% chance to crit next battle.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function useItem() {
|
||||
statAtk = BASE_ATK;
|
||||
statDef = BASE_DEF;
|
||||
statBlock = BASE_BLOCK;
|
||||
statCrit = BASE_CRIT;
|
||||
switch (playerItem) {
|
||||
case ITEM_ATK_UP:
|
||||
statAtk = 2;
|
||||
break;
|
||||
case ITEM_DEF_UP:
|
||||
statDef = 1;
|
||||
break;
|
||||
case ITEM_HP_UP:
|
||||
playerHP += 3;
|
||||
break;
|
||||
case ITEM_BLOCK_UP:
|
||||
statBlock = 5;
|
||||
break;
|
||||
case ITEM_CRIT_UP:
|
||||
statCrit = 20;
|
||||
break;
|
||||
}
|
||||
playerItem = 0;
|
||||
}
|
||||
|
||||
//Timed transitions------------------------------------------------------------------
|
||||
function wait(duration, waitFunc) {
|
||||
waitTime = duration;
|
||||
if (!refreshInterval)
|
||||
refreshInterval = setInterval(waitFunc, 500);
|
||||
}
|
||||
|
||||
function waitForTurn() {
|
||||
waitTime--;
|
||||
if (waitTime <= 0) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = undefined;
|
||||
nextTurn();
|
||||
if (playerHP > 0 && slimeHP > 0) {
|
||||
if (turn == 1) {
|
||||
slimeFight();
|
||||
wait(5, waitForTurn);
|
||||
} else {
|
||||
showBattleResult = false;
|
||||
battle();
|
||||
}
|
||||
drawBattleUI();
|
||||
} else {
|
||||
if (playerHP <= 0) {
|
||||
lose();
|
||||
}
|
||||
if (slimeHP <= 0) {
|
||||
win();
|
||||
}
|
||||
}
|
||||
}
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
function waitForBattle() {
|
||||
waitTime--;
|
||||
Bangle.beep(100, 1000);
|
||||
if (waitTime <= 0) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = undefined;
|
||||
showBattleResult = false;
|
||||
newBattle();
|
||||
}
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
function winTheme() {
|
||||
waitTime--;
|
||||
Bangle.beep(200, 100 * themeNote);
|
||||
themeNote++;
|
||||
if (waitTime <= 0) {
|
||||
themeNote = 0;
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = undefined;
|
||||
setWatch(run, BTN3);
|
||||
}
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
function loseTheme() {
|
||||
waitTime--;
|
||||
Bangle.beep(200, 600 - (100 * themeNote));
|
||||
themeNote++;
|
||||
if (waitTime <= 0) {
|
||||
themeNote = 0;
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = undefined;
|
||||
setWatch(run, BTN3);
|
||||
}
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
//Calculations------------------------------------------------------------------
|
||||
function calcScore(slimeType) {
|
||||
switch (slimeType) {
|
||||
case GREEN_SLIME:
|
||||
score += 1;
|
||||
//No items
|
||||
break;
|
||||
case PINK_SLIME:
|
||||
score += 2;
|
||||
itemChance = Math.floor(Math.random() * 100);
|
||||
if (itemChance >= 100 - 10) { //100 - ITEM CHANCE %
|
||||
getItem();
|
||||
}
|
||||
break;
|
||||
case GRAY_SLIME:
|
||||
score += 3;
|
||||
itemChance = Math.floor(Math.random() * 100);
|
||||
if (itemChance >= 100 - 25) { //100 - ITEM CHANCE %
|
||||
getItem();
|
||||
}
|
||||
break;
|
||||
case YELLOW_SLIME:
|
||||
score += 5;
|
||||
itemChance = Math.floor(Math.random() * 100);
|
||||
if (itemChance >= 100 - 50) { //100 - ITEM CHANCE %
|
||||
getItem();
|
||||
}
|
||||
break;
|
||||
case PURPLE_SLIME:
|
||||
score += 10;
|
||||
getItem();
|
||||
break;
|
||||
}
|
||||
if (score > highscore) {
|
||||
file.erase();
|
||||
file = require("Storage").open("highscore.txt", "w");
|
||||
file.write(score);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------GAME STARTS HERE -----------------------------------------------
|
||||
|
||||
//Load opening UI
|
||||
drawOpeningUI();
|
After Width: | Height: | Size: 217 B |
|
@ -0,0 +1 @@
|
|||
0.01: New App
|
|
@ -0,0 +1,4 @@
|
|||
# Simple Clock with Date
|
||||
Simple Clock with seconds and date in custom language. Install 'Languages' to get localized names.
|
||||
|
||||

|
|
@ -0,0 +1,56 @@
|
|||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var size = Math.floor(g.getWidth()/(7*6));
|
||||
var x = (g.getWidth()/2) - size*6,
|
||||
y = (g.getHeight()/2) - size*7 - 0;
|
||||
// y variable for ':'
|
||||
var y_dop = 70 - 0;
|
||||
g.reset().clearRect(0,y,g.getWidth(),y+size*28);
|
||||
// draw hours in 24h format
|
||||
g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1);
|
||||
if (d.getHours().toString.length < 2) {
|
||||
g.drawString('0'+d.getHours(), 58, y);
|
||||
}
|
||||
else {
|
||||
g.drawString(d.getHours(), 58, y);
|
||||
}
|
||||
g.setFont("7x11Numeric7Seg",size/2).setFontAlign(1,-1);
|
||||
g.drawString(":",64,y_dop);
|
||||
g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1);
|
||||
// draw minutes
|
||||
g.drawString(("0"+d.getMinutes()).substr(-2),118,y);
|
||||
g.setFont("7x11Numeric7Seg",size/2).setFontAlign(1,-1);
|
||||
g.drawString(":",124,y_dop);
|
||||
// draw seconds
|
||||
g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1);
|
||||
g.drawString(("0"+d.getSeconds()).substr(-2),178,y);
|
||||
// date
|
||||
g.setFont("6x8",size/2).setFontAlign(0,-1);
|
||||
// draw name of day
|
||||
g.drawString(require('locale').dow(new Date()),g.getWidth()/2, y + size*16);
|
||||
// draw date and name of month
|
||||
g.drawString(d.getDate()+' '+require('locale').month(new Date()),g.getWidth()/2, y + size*20);
|
||||
// draw year
|
||||
g.drawString((d.getFullYear()),g.getWidth()/2, y + size*24);
|
||||
|
||||
}
|
||||
// Only update when display turns on
|
||||
if (process.env.BOARD!="SMAQ3") // hack for Q3 which is always-on
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (secondInterval)
|
||||
clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on)
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UBAoP/AAXnnNVAH4ACoEABZJXBgoLUGaIAIgILLbf4AUnWqweq1gEB4QEBBZ0OwEA9k7h3C2ALGlk4BZAeBBZAvCBZOv/gLJ2EABZOggE7hQLFL5U+1XDBYYEB3jB/AClABRMBqoAXEhAiCBZdQBY8FHTAAj"))
|
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,22 @@
|
|||
# Theme Setter #
|
||||
|
||||
This little tool allows you to configure the global theme of all Bangle.js apps
|
||||
(provided that they do not override global settings) in a more comfortable way
|
||||
than through the settings menu.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
This app also acts as an example for a non-trivial Bangle.js application
|
||||
using the "layout" library, custom controls and generic event dispatching.
|
||||
See [GitHub](https://github.com/rozek/banglejs-2-activities) for details.
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## Credits ##
|
||||
|
||||
The icon for this app was taken from [icons8.com](https://icons8.com/).
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP/ACHgDAQWBApfjCoXxAqHwg4FP+PHApY7EApheEAq3+g4FD/EPAofAj4QDgAQECwgQQ8E/Cwg+EAvYAhA=="))
|
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,498 @@
|
|||
let Layout = require('Layout');
|
||||
|
||||
let ScreenWidth = g.getWidth(), halfWidth = ScreenWidth/2;
|
||||
let ScreenHeight = g.getHeight();
|
||||
|
||||
let normalizedColorSet = {
|
||||
black:g.toColor(0,0,0), white: g.toColor(1,1,1),
|
||||
red: g.toColor(1,0,0), yellow: g.toColor(1,1,0),
|
||||
green:g.toColor(0,1,0), magenta:g.toColor(1,0,1),
|
||||
blue: g.toColor(0,0,1), cyan: g.toColor(0,1,1)
|
||||
};
|
||||
|
||||
let activeTheme = g.theme; // currently active theme
|
||||
let pendingTheme = Object.assign({},activeTheme);
|
||||
let chosenDetail = null; // one of 'fg','bg','fg2','bg2','fgH','bgH'
|
||||
|
||||
/**** Label ****/
|
||||
|
||||
function Label (Text, Options) {
|
||||
function renderLabel (Details) {
|
||||
let halfWidth = Details.w/2, xAlignment = Details.halign || 0;
|
||||
let halfHeight = Details.h/2, yAlignment = Details.valign || 0;
|
||||
let Padding = Details.pad || 0;
|
||||
|
||||
g.setColor(Details.col || g.theme.fg || '#000000');
|
||||
|
||||
if (Details.font != null) { g.setFont(Details.font); }
|
||||
g.setFontAlign(xAlignment,yAlignment);
|
||||
|
||||
let x = Details.x + halfWidth + xAlignment*(halfWidth+Padding);
|
||||
let y = Details.y + halfHeight + yAlignment*(halfHeight+Padding);
|
||||
|
||||
g.drawString(Details.label, x,y);
|
||||
if (Details.bold) {
|
||||
g.drawString(Details.label, x+1,y);
|
||||
g.drawString(Details.label, x,y+1);
|
||||
g.drawString(Details.label, x+1,y+1);
|
||||
}
|
||||
}
|
||||
|
||||
let Result = Object.assign((
|
||||
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
||||
), {
|
||||
type:'custom', render:renderLabel, label:Text || ''
|
||||
});
|
||||
let TextMetrics;
|
||||
if (! Result.width || ! Result.height) {
|
||||
if (Result.font != null) { g.setFont(Result.font); }
|
||||
TextMetrics = g.stringMetrics(Result.label);
|
||||
}
|
||||
|
||||
Result.width = Result.width || TextMetrics.width + 2*(Result.pad || 0);
|
||||
Result.height = Result.height || TextMetrics.height + 2*(Result.pad || 0);
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (g.drawRoundedRect == null) {
|
||||
g.drawRoundedRect = function drawRoundedRect (x1,y1, x2,y2, r) {
|
||||
let x,y;
|
||||
if (x1 > x2) { x = x1; x1 = x2; x2 = x; }
|
||||
if (y1 > y2) { y = y1; y1 = y2; y2 = y; }
|
||||
|
||||
r = Math.min(r || 0, (x2-x1)/2, (y2-y1)/2);
|
||||
|
||||
let cx1 = x1+r, cx2 = x2-r;
|
||||
let cy1 = y1+r, cy2 = y2-r;
|
||||
|
||||
this.drawLine(cx1,y1, cx2,y1);
|
||||
this.drawLine(cx1,y2, cx2,y2);
|
||||
this.drawLine(x1,cy1, x1,cy2);
|
||||
this.drawLine(x2,cy1, x2,cy2);
|
||||
|
||||
x = r; y = 0;
|
||||
|
||||
let dx,dy, Error = 0;
|
||||
while (y <= x) {
|
||||
dy = 1 + 2*y; y++; Error -= dy;
|
||||
if (Error < 0) {
|
||||
dx = 1 - 2*x; x--; Error -= dx;
|
||||
}
|
||||
|
||||
this.setPixel(cx1 - x, cy1 - y); this.setPixel(cx1 - y, cy1 - x);
|
||||
this.setPixel(cx2 + x, cy1 - y); this.setPixel(cx2 + y, cy1 - x);
|
||||
this.setPixel(cx2 + x, cy2 + y); this.setPixel(cx2 + y, cy2 + x);
|
||||
this.setPixel(cx1 - x, cy2 + y); this.setPixel(cx1 - y, cy2 + x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**** Button ****/
|
||||
|
||||
function Button (Text, Options) {
|
||||
function renderButton (Details) {
|
||||
let x = Details.x, Width = Details.w, halfWidth = Width/2;
|
||||
let y = Details.y, Height = Details.h, halfHeight = Height/2;
|
||||
let Padding = Details.pad || 0;
|
||||
|
||||
g.setColor(Details.col || g.theme.fg || '#000000');
|
||||
|
||||
if (Details.font != null) { g.setFont(Details.font); }
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
g.drawRoundedRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1,8);
|
||||
g.drawString(Details.label, x+halfWidth,y+halfHeight);
|
||||
g.drawString(Details.label, x+halfWidth+1,y+halfHeight);
|
||||
g.drawString(Details.label, x+halfWidth,y+halfHeight+1);
|
||||
g.drawString(Details.label, x+halfWidth+1,y+halfHeight+1);
|
||||
}
|
||||
|
||||
let Result = Object.assign((
|
||||
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
||||
), {
|
||||
type:'custom', render:renderButton, label:Text || 'Tap'
|
||||
});
|
||||
let TextMetrics;
|
||||
if (! Result.width || ! Result.height) {
|
||||
if (Options.font != null) { g.setFont(Options.font); }
|
||||
TextMetrics = g.stringMetrics(Result.label);
|
||||
}
|
||||
|
||||
Result.width = Result.width || TextMetrics.width + 2*10 + 2*(Result.pad || 0);
|
||||
Result.height = Result.height || TextMetrics.height + 2*5 + 2*(Result.pad || 0);
|
||||
return Result;
|
||||
}
|
||||
|
||||
/**** ColorDemo ****/
|
||||
|
||||
function ColorDemo (Text, Options) {
|
||||
function renderDemo (Details) {
|
||||
let x = Details.x, Width = Details.w, halfWidth = Width/2;
|
||||
let y = Details.y, Height = Details.h, halfHeight = Height/2;
|
||||
let Padding = Details.pad || 0;
|
||||
|
||||
if (Details.font != null) { g.setFont(Details.font); }
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
g.setColor(Details.bg); // do not use "bgCol"!
|
||||
g.fillRect(x+Padding, y+Padding, x+Width-Padding, y+Height-Padding);
|
||||
|
||||
g.setColor(Details.fg);
|
||||
g.drawString(Details.label, x+halfWidth,y+halfHeight);
|
||||
}
|
||||
|
||||
let Result = Object.assign((
|
||||
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
||||
), {
|
||||
type:'custom', render:renderDemo, label:Text || 'Test'
|
||||
});
|
||||
let TextMetrics;
|
||||
if (! Result.width || ! Result.height) {
|
||||
if (Result.font != null) { g.setFont(Result.font); }
|
||||
TextMetrics = g.stringMetrics(Result.label);
|
||||
}
|
||||
|
||||
Result.width = Result.width || TextMetrics.width + 2*2 + 2*(Result.pad || 0);
|
||||
Result.height = Result.height || TextMetrics.height + 2*2 + 2*(Result.pad || 0);
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
/**** ColorView ****/
|
||||
|
||||
function ColorView (Color, Options) {
|
||||
function renderColorView (Details) {
|
||||
let x = Details.x, Width = Details.w;
|
||||
let y = Details.y, Height = Details.h;
|
||||
let Padding = Details.pad || 0;
|
||||
|
||||
g.setColor('#000000');
|
||||
g.drawRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1);
|
||||
|
||||
g.setColor(Details.col);
|
||||
g.fillRect(x+Padding+2, y+Padding+2, x+Width-Padding-3, y+Height-Padding-3);
|
||||
}
|
||||
|
||||
let Result = Object.assign((
|
||||
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
||||
), {
|
||||
type:'custom', render:renderColorView, col:Color
|
||||
});
|
||||
Result.width = Math.max(10, Result.width || 10) + 2*(Result.pad || 0);
|
||||
Result.height = Math.max(10, Result.height || 10) + 2*(Result.pad || 0);
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
/**** ColorSelectionView ****/
|
||||
|
||||
function ColorSelectionView (Color, Options) {
|
||||
function renderColorView (Details) {
|
||||
let x = Details.x, Width = Details.w;
|
||||
let y = Details.y, Height = Details.h;
|
||||
let Padding = Details.pad || 0;
|
||||
|
||||
if (Details.selected) {
|
||||
g.setColor(Details.selected ? '#FF0000' : '#000000');
|
||||
g.fillRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1);
|
||||
|
||||
g.setColor('#FFFFFF');
|
||||
g.drawRect(x+Padding+4,y+Padding+4, x+Width-Padding-5,y+Height-Padding-5);
|
||||
} else {
|
||||
g.setColor('#000000');
|
||||
g.drawRect(x+Padding+3,y+Padding+3, x+Width-Padding-4,y+Height-Padding-4);
|
||||
}
|
||||
|
||||
g.setColor(Details.col);
|
||||
g.fillRect(x+Padding+5, y+Padding+5, x+Width-Padding-6, y+Height-Padding-6);
|
||||
}
|
||||
|
||||
let Result = Object.assign((
|
||||
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
||||
), {
|
||||
type:'custom', render:renderColorView, col:Color
|
||||
});
|
||||
Result.width = Math.max(10, Result.width || 10) + 2*(Result.pad || 0);
|
||||
Result.height = Math.max(10, Result.height || 10) + 2*(Result.pad || 0);
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
/**** EventConsumerAtPoint ****/
|
||||
|
||||
function EventConsumerAtPoint (HandlerName, x,y) {
|
||||
let Layout = (activeLayout || {}).l;
|
||||
if (Layout == null) { return; }
|
||||
|
||||
function ConsumerIn (Control) {
|
||||
if (
|
||||
(x < Control.x) || (x >= Control.x + Control.w) ||
|
||||
(y < Control.y) || (y >= Control.y + Control.h)
|
||||
) { return undefined; }
|
||||
|
||||
if (typeof Control[HandlerName] === 'function') { return Control; }
|
||||
|
||||
if (Control.c != null) {
|
||||
let ControlList = Control.c;
|
||||
for (let i = 0, l = ControlList.length; i < l; i++) {
|
||||
let Consumer = ConsumerIn(ControlList[i]);
|
||||
if (Consumer != null) { return Consumer; }
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return ConsumerIn(Layout);
|
||||
}
|
||||
|
||||
/**** dispatchTouchEvent ****/
|
||||
|
||||
function dispatchTouchEvent () {
|
||||
function handleTouchEvent (Button, xy) {
|
||||
let Control = EventConsumerAtPoint('onTouch', xy.x,xy.y);
|
||||
if (Control != null) {
|
||||
Control.onTouch(Control, Button, xy);
|
||||
}
|
||||
}
|
||||
Bangle.on('touch',handleTouchEvent);
|
||||
}
|
||||
dispatchTouchEvent();
|
||||
|
||||
/**** dispatchStrokeEvent ****/
|
||||
|
||||
function dispatchStrokeEvent () {
|
||||
function handleStrokeEvent (Coordinates) {
|
||||
let Control = EventConsumerAtPoint('onStroke', Coordinates.xy[0],Coordinates.xy[1]);
|
||||
if (Control != null) {
|
||||
Control.onStroke(Control, Coordinates);
|
||||
}
|
||||
}
|
||||
Bangle.on('stroke',handleStrokeEvent);
|
||||
}
|
||||
dispatchStrokeEvent();
|
||||
|
||||
let ScreenSet = {};
|
||||
|
||||
g.setFont12x20(); // does not seem to be respected in layout!
|
||||
let leftColumnWidth = Math.max(
|
||||
g.stringWidth('Normal '), g.stringWidth('Accented '), g.stringWidth('Hilighted ')
|
||||
);
|
||||
|
||||
let StdFont = { font:'12x20' };
|
||||
let legible = Object.assign({ col:'#000000', bgCol:'#FFFFFF' }, StdFont);
|
||||
let leftAligned = Object.assign({ halign:-1, valign:0 }, legible);
|
||||
let MainLabel = Object.assign({ pad:4, width:leftColumnWidth }, leftAligned);
|
||||
let halfWidthButton = Object.assign({ pad:4, width:halfWidth }, legible);
|
||||
|
||||
ScreenSet['MainScreen'] = new Layout({
|
||||
type:'v', c:[
|
||||
Label('Current Theme', { common:legible, pad:8, bold:true, filly:1 }),
|
||||
{ type:'h', c:[
|
||||
Label('Normal', { common:MainLabel }),
|
||||
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'NormalDemo' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('Accented', { common:MainLabel }),
|
||||
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'AccentedDemo' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('Hilighted', { common:MainLabel }),
|
||||
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'HilitedDemo' }),
|
||||
] },
|
||||
{ height:4 },
|
||||
{ type:'h', c:[
|
||||
Button('Exit', { common:halfWidthButton, onTouch:() => load() }),
|
||||
Button('Config', { common:halfWidthButton, onTouch:() => gotoScreen('DetailSelectionScreen') })
|
||||
], filly:1 }
|
||||
]
|
||||
});
|
||||
|
||||
let LabelWidth = Math.max(
|
||||
g.stringWidth('Fg '), g.stringWidth('Fg2 '), g.stringWidth('FgH '),
|
||||
g.stringWidth('Bg '), g.stringWidth('Bg2 '), g.stringWidth('BgH ')
|
||||
);
|
||||
let LabelHeight = g.stringMetrics('FgH').height;
|
||||
|
||||
let DetailLabel = Object.assign({ pad:4, width:LabelWidth }, leftAligned);
|
||||
let DetailView = { width:30, height:LabelHeight, pad:2 };
|
||||
|
||||
ScreenSet['DetailSelectionScreen'] = new Layout({
|
||||
type:'v', c:[
|
||||
Label('Configure Detail', { font:'12x20', pad:8, col:'#000000', bgCol:'#FFFFFF', bold:true, filly:1 }),
|
||||
{ type:'h', c:[
|
||||
Label('fg', { common:DetailLabel, onTouch:() => configureDetail('fg') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fg'), id:'fgView' }),
|
||||
{ width:20 },
|
||||
Label('bg', { common:DetailLabel, onTouch:() => configureDetail('bg') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bg'), id:'bgView' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('fg2', { common:DetailLabel, onTouch:() => configureDetail('fg2') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fg2'), id:'fg2View' }),
|
||||
{ width:20 },
|
||||
Label('bg2', { common:DetailLabel, onTouch:() => configureDetail('bg2') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bg2'), id:'bg2View' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('fgH', { common:DetailLabel, onTouch:() => configureDetail('fgH') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fgH'), id:'fgHView' }),
|
||||
{ width:20 },
|
||||
Label('bgH', { common:DetailLabel, onTouch:() => configureDetail('bgH') }),
|
||||
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bgH'), id:'bgHView' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Button('Save', { common:halfWidthButton, onTouch:() => { applyChanges(); gotoScreen('MainScreen'); } }),
|
||||
Button('Cancel', { common:halfWidthButton, onTouch:() => gotoScreen('MainScreen') })
|
||||
], filly:1 },
|
||||
]
|
||||
});
|
||||
|
||||
let StdSelectionView = { width:40, height:40, pad:2 };
|
||||
|
||||
ScreenSet['ColorSelectionScreen'] = new Layout({
|
||||
type:'v', c:[
|
||||
Label('Choose Color', { font:'12x20', pad:8, col:'#000000', bgCol:'#FFFFFF', bold:true, filly:1 }),
|
||||
{ type:'h', c:[
|
||||
ColorSelectionView('#000000',{ common:StdSelectionView, id:'black',
|
||||
onTouch:() => selectColor(0,0,0) }),
|
||||
ColorSelectionView('#FF0000',{ common:StdSelectionView, id:'red',
|
||||
onTouch:() => selectColor(1,0,0) }),
|
||||
ColorSelectionView('#00FF00',{ common:StdSelectionView, id:'green',
|
||||
onTouch:() => selectColor(0,1,0) }),
|
||||
ColorSelectionView('#0000FF',{ common:StdSelectionView, id:'blue',
|
||||
onTouch:() => selectColor(0,0,1) }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
ColorSelectionView('#FFFFFF',{ common:StdSelectionView, id:'white',
|
||||
onTouch:() => selectColor(1,1,1) }),
|
||||
ColorSelectionView('#FFFF00',{ common:StdSelectionView, id:'yellow',
|
||||
onTouch:() => selectColor(1,1,0) }),
|
||||
ColorSelectionView('#FF00FF',{ common:StdSelectionView, id:'magenta',
|
||||
onTouch:() => selectColor(1,0,1) }),
|
||||
ColorSelectionView('#00FFFF',{ common:StdSelectionView, id:'cyan',
|
||||
onTouch:() => selectColor(0,1,1) }),
|
||||
] },
|
||||
{ height:4 },
|
||||
{ type:'h', c:[
|
||||
Button('Back', { common:halfWidthButton, onTouch:() => gotoScreen('DetailSelectionScreen') }),
|
||||
Button('Preview', { common:halfWidthButton, onTouch:() => gotoScreen('ThemePreviewScreen') })
|
||||
], filly:1 },
|
||||
]
|
||||
});
|
||||
|
||||
ScreenSet['ThemePreviewScreen'] = new Layout({
|
||||
type:'v', c:[
|
||||
Label('Theme Preview', { common:legible, bold:true, filly:1 }),
|
||||
{ type:'h', c:[
|
||||
Label('Normal', { common:MainLabel }),
|
||||
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'NormalTest' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('Accented', { common:MainLabel }),
|
||||
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'AccentedTest' }),
|
||||
] },
|
||||
{ type:'h', c:[
|
||||
Label('Hilighted', { common:MainLabel }),
|
||||
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'HilitedTest' }),
|
||||
] },
|
||||
{ height:4 },
|
||||
{ type:'h', c:[
|
||||
Button('Back', { common:legible, pad:4, onTouch:() => gotoScreen('ColorSelectionScreen') })
|
||||
], filly:1 }
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
/**** applyChanges ****/
|
||||
|
||||
function applyChanges () {
|
||||
let pendingBg = pendingTheme.bg;
|
||||
let R = ((pendingBg >> 11) & 0b11111) / 0b11111;
|
||||
let G = ((pendingBg >> 5) & 0b111111) / 0b111111;
|
||||
let B = (pendingBg & 0b11111) / 0b11111;
|
||||
pendingTheme.dark = (0.2126*R + 0.7152*G + 0.0722*B < 0.5);
|
||||
|
||||
activeTheme = Object.assign(activeTheme,pendingTheme);
|
||||
|
||||
let globalSettings = Object.assign(
|
||||
require('Storage').readJSON('setting.json', true) || {},
|
||||
{ theme:activeTheme }
|
||||
);
|
||||
require('Storage').writeJSON('setting.json', globalSettings);
|
||||
}
|
||||
|
||||
/**** configureDetail ****/
|
||||
|
||||
function configureDetail (Detail) {
|
||||
chosenDetail = Detail;
|
||||
gotoScreen('ColorSelectionScreen');
|
||||
}
|
||||
|
||||
/**** updateColorSelection ****/
|
||||
|
||||
function updateColorSelection () {
|
||||
let selectedColor = pendingTheme[chosenDetail];
|
||||
|
||||
for (let Key in normalizedColorSet) {
|
||||
if (normalizedColorSet.hasOwnProperty(Key)) {
|
||||
activeLayout[Key].selected = (selectedColor === normalizedColorSet[Key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**** selectColor ****/
|
||||
|
||||
function selectColor (R,G,B) {
|
||||
let selectedColor = g.toColor(R,G,B);
|
||||
pendingTheme[chosenDetail] = selectedColor;
|
||||
|
||||
updateColorSelection();
|
||||
g.clear();
|
||||
activeLayout.render();
|
||||
}
|
||||
|
||||
/**** gotoScreen ****/
|
||||
|
||||
let activeLayout;
|
||||
|
||||
function gotoScreen (ScreenName) {
|
||||
activeLayout = ScreenSet[ScreenName];
|
||||
|
||||
switch (ScreenName) {
|
||||
case 'MainScreen':
|
||||
activeLayout['NormalDemo'].fg = activeTheme.fg;
|
||||
activeLayout['NormalDemo'].bg = activeTheme.bg;
|
||||
activeLayout['AccentedDemo'].fg = activeTheme.fg2;
|
||||
activeLayout['AccentedDemo'].bg = activeTheme.bg2;
|
||||
activeLayout['HilitedDemo'].fg = activeTheme.fgH;
|
||||
activeLayout['HilitedDemo'].bg = activeTheme.bgH;
|
||||
break;
|
||||
case 'DetailSelectionScreen':
|
||||
activeLayout['fgView'].col = pendingTheme.fg;
|
||||
activeLayout['bgView'].col = pendingTheme.bg;
|
||||
activeLayout['fg2View'].col = pendingTheme.fg2;
|
||||
activeLayout['bg2View'].col = pendingTheme.bg2;
|
||||
activeLayout['fgHView'].col = pendingTheme.fgH;
|
||||
activeLayout['bgHView'].col = pendingTheme.bgH;
|
||||
break;
|
||||
case 'ColorSelectionScreen':
|
||||
updateColorSelection();
|
||||
break;
|
||||
case 'ThemePreviewScreen':
|
||||
activeLayout['NormalTest'].fg = pendingTheme.fg;
|
||||
activeLayout['NormalTest'].bg = pendingTheme.bg;
|
||||
activeLayout['AccentedTest'].fg = pendingTheme.fg2;
|
||||
activeLayout['AccentedTest'].bg = pendingTheme.bg2;
|
||||
activeLayout['HilitedTest'].fg = pendingTheme.fgH;
|
||||
activeLayout['HilitedTest'].bg = pendingTheme.bgH;
|
||||
}
|
||||
|
||||
g.setColor('#000000'); g.setBgColor('#FFFFFF'); // assert legibility
|
||||
g.clear();
|
||||
|
||||
activeLayout.render();
|
||||
}
|
||||
gotoScreen('MainScreen');
|
||||
|