Merge branch 'master' of github.com:espruino/BangleApps into grocery-bjs2

pull/1209/head
Adam Schmalhofer 2022-01-04 23:24:57 +01:00
commit c13716c923
182 changed files with 6683 additions and 847 deletions

341
apps.json
View File

@ -77,7 +77,7 @@
{ {
"id": "messages", "id": "messages",
"name": "Messages", "name": "Messages",
"version": "0.14", "version": "0.16",
"description": "App to display notifications from iOS and Gadgetbridge", "description": "App to display notifications from iOS and Gadgetbridge",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
@ -167,7 +167,7 @@
{ {
"id": "setting", "id": "setting",
"name": "Settings", "name": "Settings",
"version": "0.38", "version": "0.39",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"icon": "settings.png", "icon": "settings.png",
"tags": "tool,system", "tags": "tool,system",
@ -768,7 +768,7 @@
"id": "recorder", "id": "recorder",
"name": "Recorder (BETA)", "name": "Recorder (BETA)",
"shortName": "Recorder", "shortName": "Recorder",
"version": "0.04", "version": "0.05",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.", "description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors,gps,widget", "tags": "tool,outdoors,gps,widget",
@ -845,7 +845,7 @@
{ {
"id": "weather", "id": "weather",
"name": "Weather", "name": "Weather",
"version": "0.13", "version": "0.14",
"description": "Show Gadgetbridge weather report", "description": "Show Gadgetbridge weather report",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],
@ -971,7 +971,7 @@
{ {
"id": "widbt", "id": "widbt",
"name": "Bluetooth Widget", "name": "Bluetooth Widget",
"version": "0.07", "version": "0.08",
"description": "Show the current Bluetooth connection status in the top right of the clock", "description": "Show the current Bluetooth connection status in the top right of the clock",
"icon": "widget.png", "icon": "widget.png",
"type": "widget", "type": "widget",
@ -1148,7 +1148,7 @@
{ {
"id": "qrcode", "id": "qrcode",
"name": "Custom QR Code", "name": "Custom QR Code",
"version": "0.04", "version": "0.05",
"description": "Use this to upload a customised QR code to Bangle.js", "description": "Use this to upload a customised QR code to Bangle.js",
"icon": "app.png", "icon": "app.png",
"tags": "qrcode", "tags": "qrcode",
@ -1501,7 +1501,7 @@
{ {
"id": "gpsinfo", "id": "gpsinfo",
"name": "GPS Info", "name": "GPS Info",
"version": "0.05", "version": "0.06",
"description": "An application that displays information about altitude, lat/lon, satellites and time", "description": "An application that displays information about altitude, lat/lon, satellites and time",
"icon": "gps-info.png", "icon": "gps-info.png",
"type": "app", "type": "app",
@ -2028,7 +2028,7 @@
"id": "chronowid", "id": "chronowid",
"name": "Chrono Widget", "name": "Chrono Widget",
"shortName": "Chrono Widget", "shortName": "Chrono Widget",
"version": "0.04", "version": "0.05",
"description": "Chronometer (timer) which runs as widget.", "description": "Chronometer (timer) which runs as widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,widget", "tags": "tool,widget",
@ -2185,7 +2185,7 @@
"id": "calculator", "id": "calculator",
"name": "Calculator", "name": "Calculator",
"shortName": "Calculator", "shortName": "Calculator",
"version": "0.04", "version": "0.05",
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
"icon": "calculator.png", "icon": "calculator.png",
"screenshots": [{"url":"screenshot_calculator.png"}], "screenshots": [{"url":"screenshot_calculator.png"}],
@ -2430,7 +2430,7 @@
{ {
"id": "calendar", "id": "calendar",
"name": "Calendar", "name": "Calendar",
"version": "0.04", "version": "0.05",
"description": "Simple calendar", "description": "Simple calendar",
"icon": "calendar.png", "icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}], "screenshots": [{"url":"screenshot_calendar.png"}],
@ -4065,7 +4065,7 @@
{ {
"id": "hcclock", "id": "hcclock",
"name": "Hi-Contrast Clock", "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.", "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", "icon": "hcclock-icon.png",
"type": "clock", "type": "clock",
@ -4488,7 +4488,7 @@
"name": "LCARS Clock", "name": "LCARS Clock",
"shortName":"LCARS", "shortName":"LCARS",
"icon": "lcars.png", "icon": "lcars.png",
"version":"0.07", "version":"0.08",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.", "description": "Library Computer Access Retrieval System (LCARS) clock.",
@ -4653,7 +4653,7 @@
"id": "sensible", "id": "sensible",
"name": "SensiBLE", "name": "SensiBLE",
"shortName": "SensiBLE", "shortName": "SensiBLE",
"version": "0.04", "version": "0.05",
"description": "Collect, display and advertise real-time sensor data.", "description": "Collect, display and advertise real-time sensor data.",
"icon": "sensible.png", "icon": "sensible.png",
"screenshots": [ "screenshots": [
@ -4698,6 +4698,8 @@
"tags": "tool,timer", "tags": "tool,timer",
"readme":"README.md", "readme":"README.md",
"supports":["BANGLEJS2"], "supports":["BANGLEJS2"],
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
"allow_emulator": true,
"storage": [ "storage": [
{"name":"a_speech_timer.app.js","url":"app.js"}, {"name":"a_speech_timer.app.js","url":"app.js"},
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true} {"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
@ -4779,7 +4781,7 @@
{ {
"id": "weatherClock", "id": "weatherClock",
"name": "Weather Clock", "name": "Weather Clock",
"version": "0.04", "version": "0.05",
"description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).", "description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screens/screen1.png"}], "screenshots": [{"url":"screens/screen1.png"}],
@ -4839,6 +4841,25 @@
{"name": "flow.img", "url": "app-icon.js","evaluate": true } {"name": "flow.img", "url": "app-icon.js","evaluate": true }
] ]
}, },
{ "id": "tinydraw",
"name": "TinyDraw",
"shortName":"TinyDraw",
"version":"0.01",
"type": "app",
"description": "Draw stuff in your wrist",
"icon": "app.png",
"allow_emulator": true,
"tags": "tools, keyboard, text, scribble",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"tinydraw.app.js","url":"app.js"},
{"name":"tinydraw.img","url":"app-icon.js","evaluate":true}
],
"screenshots":[
{ "url":"screenshot.png" }
]
},
{ "id": "scribble", { "id": "scribble",
"name": "Scribble", "name": "Scribble",
"shortName":"Scribble", "shortName":"Scribble",
@ -4862,7 +4883,7 @@
"id": "ptlaunch", "id": "ptlaunch",
"name": "Pattern Launcher", "name": "Pattern Launcher",
"shortName": "Pattern Launcher", "shortName": "Pattern Launcher",
"version": "0.11", "version": "0.13",
"description": "Directly launch apps from the clock screen with custom patterns.", "description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"manage_patterns_light.png"}], "screenshots": [{"url":"manage_patterns_light.png"}],
@ -4876,6 +4897,20 @@
], ],
"data": [{"name":"ptlaunch.patterns.json"}] "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", "id": "rebble",
"name": "Rebble Clock", "name": "Rebble Clock",
@ -4932,10 +4967,12 @@
"id":"awairmonitor", "id":"awairmonitor",
"name":"Awair Monitor", "name":"Awair Monitor",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"allow_emulator": true, "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.", "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", "readme":"README.md",
"supports":["BANGLEJS2"], "supports":["BANGLEJS2"],
"storage": [ "storage": [
@ -4964,8 +5001,8 @@
}, },
{ {
"id": "coretemp", "id": "coretemp",
"name": "Core Temp Display", "name": "CoreTemp",
"version": "0.01", "version": "0.02",
"description": "Display CoreTemp device sensor data", "description": "Display CoreTemp device sensor data",
"icon": "coretemp.png", "icon": "coretemp.png",
"type": "app", "type": "app",
@ -4973,10 +5010,14 @@
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"storage": [ "storage": [
{"name":"coretemp.boot.js","url":"boot.js"}, {"name":"coretemp.wid.js","url":"widget.js"},
{"name":"coretemp.app.js","url":"coretemp.js"}, {"name":"coretemp.app.js","url":"coretemp.js"},
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true} {"name":"coretemp.settings.js","url":"settings.js"},
] {"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
{"name":"coretemp.boot.js","url":"boot.js"}
],
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
"screenshots": [{"url":"screenshot.png"}]
}, },
{ {
"id": "showimg", "id": "showimg",
@ -5030,9 +5071,10 @@
{ "id": "circlesclock", { "id": "circlesclock",
"name": "Circles clock", "name": "Circles clock",
"shortName":"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", "description": "A clock with circles for different data at the bottom in a probably familiar style",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"dependencies": {"widpedom":"app"}, "dependencies": {"widpedom":"app"},
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",
@ -5048,6 +5090,22 @@
{"name":"circlesclock.json"} {"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", "id": "ltherm",
"name": "Localized Thermometer", "name": "Localized Thermometer",
@ -5063,5 +5121,242 @@
{"name":"ltherm.app.js","url":"app.js"}, {"name":"ltherm.app.js","url":"app.js"},
{"name":"ltherm.img","url":"icon.js","evaluate":true} {"name":"ltherm.img","url":"icon.js","evaluate":true}
] ]
},
{
"id": "slash",
"name": "Slash Watch",
"shortName":"Slash",
"icon": "slash.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.01",
"description": "Slash Watch based on Pebble watch face by Nikki.",
"tags": "clock",
"type": "clock",
"supports":["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"slash.app.js","url":"app.js"},
{"name":"slash.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "promenu",
"name": "Pro Menu",
"version": "0.01",
"description": "Replace Bangle.js 1's built in menu function.",
"icon": "icon.png",
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS"],
"screenshots": [{"url":"pro-menu-screenshot.png"}],
"storage": [
{"name":"promenu.boot.js","url":"boot.js"},
{"name":"promenu.img","url":"promenuIcon.js","evaluate":true}
]
},
{
"id": "touchtimer",
"name": "Touch Timer",
"shortName": "Touch Timer",
"version": "0.02",
"description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.",
"icon": "app.png",
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"0_light_timer_edit.png"},{"url":"1_light_timer_ready.png"},{"url":"2_light_timer_running.png"},{"url":"3_light_timer_finished.png"}],
"storage": [
{ "name": "touchtimer.app.js", "url": "app.js" },
{ "name":"touchtimer.settings.js", "url":"settings.js"},
{ "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true }
],
"data": [{"name":"touchtimer.data.json"}]
},
{
"id": "teatimer",
"name": "Tea Timer",
"version": "1.00",
"description": "A simple timer. You can easyly set up the time.",
"icon": "teatimer.png",
"type": "app",
"tags": "tool",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"teatimer.app.js","url":"app.js"},
{"name":"teatimer.img","url":"app-icon.js","evaluate":true}
],
"screenshots": [
{"url":"TeatimerStart.jpg"},
{"url":"TeatimerHelp.jpg"},
{"url":"TeatimerRun.jpg"},
{"url":"TeatimerUp.jpg"}
]
},
{
"id": "swp2clk",
"name": "Swipe back to the Clock",
"shortName": "Swipe to Clock",
"version": "0.01",
"description": "Let's you swipe from left to right on any app to return back to the clock face. Please configure in the settings app after installing to activate, since its disabled by default.",
"icon": "app.png",
"type": "boot",
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name": "swp2clk.boot.js", "url": "boot.js" },
{"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 }
]
} }
] ]

4
apps/andark/ChangeLog Normal file
View File

@ -0,0 +1,4 @@
0.01: Release
0.02: Rename app
0.03: Add type "clock"
0.04: changed update cylce, when locked

10
apps/andark/README.md Normal file
View File

@ -0,0 +1,10 @@
# Analog Clock
## Features
* second hand
* date
* battery percantage
* no widgets
![logo](andark_screen.png)

BIN
apps/andark/andark_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

125
apps/andark/app.js Normal file
View File

@ -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");

1
apps/andark/app_icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIEBoUAiAKCgUCBQUEColEAYUQhAmKCwgeCAAcCgEDjwEBkEAg8TBocNgYFDh8GAYMDxkPjEA8EAwkHJgIcBAoPfAoYWCBYYFIgfvAoX4FYRJEAp9gAomYNAOAArPwAogAC4AFiRoIFJLgIFJuADCg//Q4U//4FDj4FEAAV4Aoi0CSxBsCA=="))

View File

@ -1 +1,3 @@
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11) 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)

View File

@ -5,11 +5,10 @@ Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awai
* What you need: * What you need:
* A BangleJS 2 * A BangleJS 2
* An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature) * 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 * 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 * 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 * Once connected to the watch with the app running, the watch app is updated once per second
![](screenshot.png) ![](screenshot.png)

View File

@ -30,6 +30,8 @@ var bt_temp_history = new Array(10).fill(0);
var internal_last_update = -1; var internal_last_update = -1;
var display_frozen = false;
function draw() { function draw() {
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()); g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
@ -47,14 +49,8 @@ function draw() {
g.drawString("Humi", 125, 100); g.drawString("Humi", 125, 100);
g.drawString("Temp", 160, 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) { if (last_update != bt_last_update) {
display_frozen = false;
last_update = bt_last_update; last_update = bt_last_update;
internal_last_update = last_update; internal_last_update = last_update;
if (last_update % 10 == 0) { if (last_update % 10 == 0) {
@ -68,12 +64,25 @@ function draw() {
if (internal_last_update == -1) { if (internal_last_update == -1) {
g.drawString("Waiting for connection", 88, 164); 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); 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++) { for (i = 0; i < 10; i++) {
if (display_frozen) { g.setColor("#888"); }
// max height = 32 // 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(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(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
@ -91,6 +100,7 @@ function draw() {
} }
// init // init
Bangle.setUI("clock");
require("FontHaxorNarrow7x17").add(Graphics); require("FontHaxorNarrow7x17").add(Graphics);
g.clear(); g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();

View File

@ -7,15 +7,15 @@
// Don't forget to enable the Local API on your Awair before using this // 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 // 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"; const awair_name_1 = "Awair";
var bt_connection; var bt_connection;
var is_connected = false; var is_connected = false;
var reconnect_counter = 5; var reconnect_counter = 5;
var reconnect_attempt_counter = 1; var reconnect_attempt_counter = 1;
var is_chart_started = false;
window.onload = function() { function initChart() {
var chart_co2; var chart_co2;
var chart_voc; var chart_voc;
var chart_pm; var chart_pm;
@ -24,6 +24,8 @@ window.onload = function() {
var dataPoints_1 = []; var dataPoints_1 = [];
var posx = 0; var posx = 0;
var awair_ip_1 = document.getElementById('inputawairip').value;
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) { $.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
$.each(data, function(key, value){ $.each(data, function(key, value){
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; } if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
@ -105,11 +107,12 @@ window.onload = function() {
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y; let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y; let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
let last_update = dataPoints_1['temp'].length-1; 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'); 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"); 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); console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
reconnect_counter--; reconnect_counter--;
@ -131,7 +134,6 @@ window.onload = function() {
} }
} }
setTimeout(function(){updateChart()}, 1000); setTimeout(function(){updateChart()}, 1000);
}); });
} }
@ -148,10 +150,16 @@ function connectBT() {
bt_connection = c; bt_connection = c;
is_connected = true; is_connected = true;
reconnect_attempt_counter = 1; reconnect_attempt_counter = 1;
if (!is_chart_started) {
initChart();
is_chart_started = true;
}
}); });
} }
function disconnectBT() { function disconnectBT() {
console.log("Disconnect Bluetooth button pressed. bt_connection value below.")
console.log(bt_connection);
if (is_connected && bt_connection) { if (is_connected && bt_connection) {
bt_connection.close(); bt_connection.close();
is_connected = false; is_connected = false;
@ -167,23 +175,21 @@ function disconnectBT() {
<p style="color: #F7FAFC"> <p style="color: #F7FAFC">
<b>How to use</b> <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 Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
<br/><br/> <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") Step 2: Launch the Awair Monitor app on your BangleJS
<br/><br/> <br><br>
Step 3: Launch the Awair Monitor app on your BangleJS Step 3: Input your Awair IP address and click the Connect button:
<br/><br/> <input type="text" id="inputawairip" value="192.168.2.1">
Step 4: Click "Connect BangleJS" <input type="button" value="Connect BangleJS" onclick="connectBT();">
<br/><br/> <br><br>
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs Step 4: Optionally, open the web inspector's console (Right click &gt; Inspector &gt; 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> </p>
<center>
<button onclick="connectBT();">Connect BangleJS</button>
<button onclick="disconnectBT();">Disconnect BangleJS</button>
</center>
<br/><br/> <br/><br/>
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div> <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_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> <div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
</body> </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_

View File

@ -68,7 +68,7 @@ ${track.map(pt=>` <gx:value>${pt.distance}</gx:value>\n`).join("")}
function saveGPX(track, title) { function saveGPX(track, title) {
var gpx = `<?xml version="1.0" encoding="UTF-8"?> var gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"> <gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<metadata> <metadata>
<time>${track[0].date.toISOString()}</time> <time>${track[0].date.toISOString()}</time>
</metadata> </metadata>

View File

@ -1,15 +0,0 @@
// Create an entry in apps.json as follows:
{ "id": "bluetoothdock",
"name": "Bluetooth Dock",
"shortName":"Dock",
"icon": "app.png",
"version":"0.01",
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
"tags": "bluetooth",
"readme": "README.md",
"storage": [
{"name":"bluetoothdock.app.js","url":"app.js"},
{"name":"bluetoothdock.boot.js","url":"boot.js"},
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -2,3 +2,4 @@
0.02: fix precision rounding issue + no reset when equals pressed 0.02: fix precision rounding issue + no reset when equals pressed
0.03: Support for different screen sizes and touchscreen 0.03: Support for different screen sizes and touchscreen
0.04: Display current operation on LHS 0.04: Display current operation on LHS
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)

View File

@ -8,7 +8,7 @@
g.clear(); g.clear();
require("Font7x11Numeric7Seg").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics);
var DEFAULT_SELECTION = '5'; var DEFAULT_SELECTION_NUMBERS = '5', DEFAULT_SELECTION_OPERATORS = '=', DEFAULT_SELECTION_SPECIALS = 'R';
var RIGHT_MARGIN = 20; var RIGHT_MARGIN = 20;
var RESULT_HEIGHT = 40; var RESULT_HEIGHT = 40;
var COLORS = { var COLORS = {
@ -18,97 +18,45 @@ var COLORS = {
SPECIAL: ['#65686C', '#7F8183'] SPECIAL: ['#65686C', '#7F8183']
}; };
var keys = { var KEY_AREA = [0, RESULT_HEIGHT, g.getWidth(), g.getHeight()];
'0': {
xy: [0, 200, 120, 240], var screen, screenColor;
trbl: '2.00' var globalGrid = [4, 5];
}, var swipeEnabled;
'.': {
xy: [120, 200, 180, 240], var numbersGrid = [3, 4];
trbl: '3=.0' var numbers = {
}, '0': {grid: [1, 3], globalGrid: [1, 4], trbl: '2.00'},
'=': { '.': {grid: [2, 3], globalGrid: [2, 4], trbl: '3=.0'},
xy: [181, 200, 240, 240], '1': {grid: [0, 2], globalGrid: [0, 3], trbl: '4201'},
trbl: '+==.', '2': {grid: [1, 2], globalGrid: [1, 3], trbl: '5301'},
color: COLORS.OPERATOR '3': {grid: [2, 2], globalGrid: [2, 3], trbl: '6+.2'},
}, '4': {grid: [0, 1], globalGrid: [0, 2], trbl: '7514'},
'1': { '5': {grid: [1, 1], globalGrid: [1, 2], trbl: '8624'},
xy: [0, 160, 60, 200], '6': {grid: [2, 1], globalGrid: [2, 2], trbl: '9-35'},
trbl: '4201' '7': {grid: [0, 0], globalGrid: [0, 1], trbl: 'R847'},
}, '8': {grid: [1, 0], globalGrid: [1, 1], trbl: 'N957'},
'2': { '9': {grid: [2, 0], globalGrid: [2, 1], trbl: '%*68'},
xy: [60, 160, 120, 200],
trbl: '5301'
},
'3': {
xy: [120, 160, 180, 200],
trbl: '6+.2'
},
'+': {
xy: [181, 160, 240, 200],
trbl: '-+=3',
color: COLORS.OPERATOR
},
'4': {
xy: [0, 120, 60, 160],
trbl: '7514'
},
'5': {
xy: [60, 120, 120, 160],
trbl: '8624'
},
'6': {
xy: [120, 120, 180, 160],
trbl: '9-35'
},
'-': {
xy: [181, 120, 240, 160],
trbl: '*-+6',
color: COLORS.OPERATOR
},
'7': {
xy: [0, 80, 60, 120],
trbl: 'R847'
},
'8': {
xy: [60, 80, 120, 120],
trbl: 'N957'
},
'9': {
xy: [120, 80, 180, 120],
trbl: '%*68'
},
'*': {
xy: [181, 80, 240, 120],
trbl: '/*-9',
color: COLORS.OPERATOR
},
'R': {
xy: [0, 40, 60, 79],
trbl: 'RN7R',
color: COLORS.SPECIAL,
val: 'AC'
},
'N': {
xy: [60, 40, 120, 79],
trbl: 'N%8R',
color: COLORS.SPECIAL,
val: '+/-'
},
'%': {
xy: [120, 40, 180, 79],
trbl: '%/9N',
color: COLORS.SPECIAL
},
'/': {
xy: [181, 40, 240, 79],
trbl: '//*%',
color: COLORS.OPERATOR
}
}; };
var selected = DEFAULT_SELECTION; var operatorsGrid = [2, 3];
var prevSelected = DEFAULT_SELECTION; var operators = {
'+': {grid: [0, 0], globalGrid: [3, 3], trbl: '-+=3'},
'-': {grid: [1, 0], globalGrid: [3, 2], trbl: '*-+6'},
'*': {grid: [0, 1], globalGrid: [3, 1], trbl: '/*-9'},
'/': {grid: [1, 1], globalGrid: [3, 0], trbl: '//*%'},
'=': {grid: [1, 2], globalGrid: [3, 4], trbl: '+==.'},
};
var specialsGrid = [2, 2];
var specials = {
'R': {grid: [0, 0], globalGrid: [0, 0], trbl: 'RN7R', val: 'AC'},
'N': {grid: [1, 0], globalGrid: [1, 0], trbl: 'N%8R', val: '+/-'},
'%': {grid: [0, 1], globalGrid: [2, 0], trbl: '%/9N'},
};
var selected = DEFAULT_SELECTION_NUMBERS;
var prevSelected = DEFAULT_SELECTION_NUMBERS;
var prevNumber = null; var prevNumber = null;
var currNumber = null; var currNumber = null;
var operator = null; var operator = null;
@ -116,6 +64,27 @@ var results = null;
var isDecimal = false; var isDecimal = false;
var hasPressedEquals = false; var hasPressedEquals = false;
function prepareScreen(screen, grid, defaultColor) {
for (var k in screen) {
if (screen.hasOwnProperty(k)) {
screen[k].color = screen[k].color || defaultColor;
var position = [];
var xGrid = (KEY_AREA[2]-KEY_AREA[0])/grid[0];
var yGrid = (KEY_AREA[3]-KEY_AREA[1])/grid[1];
if (swipeEnabled) {
position[0] = KEY_AREA[0]+xGrid*screen[k].grid[0];
position[1] = KEY_AREA[1]+yGrid*screen[k].grid[1];
} else {
position[0] = KEY_AREA[0]+xGrid*screen[k].globalGrid[0];
position[1] = KEY_AREA[1]+yGrid*screen[k].globalGrid[1];
}
position[2] = position[0]+xGrid-1;
position[3] = position[1]+yGrid-1;
screen[k].xy = position;
}
}
}
function drawKey(name, k, selected) { function drawKey(name, k, selected) {
var rMargin = 0; var rMargin = 0;
var bMargin = 0; var bMargin = 0;
@ -142,6 +111,56 @@ function drawKey(name, k, selected) {
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2); g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
} }
function drawKeys() {
g.setColor(screenColor[0]);
g.fillRect(KEY_AREA[0], KEY_AREA[1], KEY_AREA[2], KEY_AREA[3]);
for (var k in screen) {
if (screen.hasOwnProperty(k)) {
drawKey(k, screen[k], k == selected);
}
}
}
function drawGlobal() {
screen = {};
screenColor = COLORS.DEFAULT;
prepareScreen(numbers, globalGrid, COLORS.DEFAULT);
for (var k in numbers) {
screen[k] = numbers[k];
}
prepareScreen(operators, globalGrid, COLORS.OPERATOR);
for (var k in operators) {
screen[k] = operators[k];
}
prepareScreen(specials, globalGrid, COLORS.SPECIAL);
for (var k in specials) {
screen[k] = specials[k];
}
drawKeys();
var selected = DEFAULT_SELECTION_NUMBERS;
var prevSelected = DEFAULT_SELECTION_NUMBERS;
}
function drawNumbers() {
screen = numbers;
screenColor = COLORS.DEFAULT;
drawKeys();
var selected = DEFAULT_SELECTION_NUMBERS;
var prevSelected = DEFAULT_SELECTION_NUMBERS;
}
function drawOperators() {
screen = operators;
screenColor =COLORS.OPERATOR;
drawKeys();
var selected = DEFAULT_SELECTION_OPERATORS;
var prevSelected = DEFAULT_SELECTION_OPERATORS;
}
function drawSpecials() {
screen = specials;
screenColor = COLORS.SPECIAL;
drawKeys();
var selected = DEFAULT_SELECTION_SPECIALS;
var prevSelected = DEFAULT_SELECTION_SPECIALS;
}
function getIntWithPrecision(x) { function getIntWithPrecision(x) {
var xStr = x.toString(); var xStr = x.toString();
var xRadix = xStr.indexOf('.'); var xRadix = xStr.indexOf('.');
@ -218,8 +237,8 @@ function displayOutput(num) {
hasPressedEquals = false; hasPressedEquals = false;
prevNumber = null; prevNumber = null;
operator = null; operator = null;
keys.R.val = 'AC'; specials.R.val = 'AC';
drawKey('R', keys.R); if (!swipeEnabled) drawKey('R', specials.R);
g.setFont('Vector', 22); g.setFont('Vector', 22);
} else { } else {
// might not be a number due to display of dot "." // might not be a number due to display of dot "."
@ -299,12 +318,12 @@ function buttonPress(val) {
results = null; results = null;
isDecimal = false; isDecimal = false;
hasPressedEquals = false; hasPressedEquals = false;
if (keys.R.val == 'AC') { if (specials.R.val == 'AC') {
prevNumber = null; prevNumber = null;
operator = null; operator = null;
} else { } else {
keys.R.val = 'AC'; specials.R.val = 'AC';
drawKey('R', keys.R, true); drawKey('R', specials.R, true);
} }
wasPressedEquals = false; wasPressedEquals = false;
hasPressedNumber = false; hasPressedNumber = false;
@ -331,10 +350,11 @@ function buttonPress(val) {
case '+': case '+':
calculatorLogic(val); calculatorLogic(val);
hasPressedNumber = false; hasPressedNumber = false;
if (swipeEnabled) drawNumbers();
break; break;
case '.': case '.':
keys.R.val = 'C'; specials.R.val = 'C';
drawKey('R', keys.R); if (!swipeEnabled) drawKey('R', specials.R);
isDecimal = true; isDecimal = true;
displayOutput(currNumber == null ? 0 + '.' : currNumber + '.'); displayOutput(currNumber == null ? 0 + '.' : currNumber + '.');
break; break;
@ -348,8 +368,8 @@ function buttonPress(val) {
hasPressedNumber = false; hasPressedNumber = false;
break; break;
default: default:
keys.R.val = 'C'; specials.R.val = 'C';
drawKey('R', keys.R); if (!swipeEnabled) drawKey('R', specials.R);
const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity); const is0Negative = (currNumber === 0 && 1/currNumber === -Infinity);
if (isDecimal) { if (isDecimal) {
currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val; currNumber = currNumber == null || hasPressedEquals === 1 ? 0 + '.' + val : currNumber + '.' + val;
@ -367,23 +387,31 @@ function buttonPress(val) {
} }
function moveDirection(d) { function moveDirection(d) {
drawKey(selected, keys[selected]); drawKey(selected, screen[selected]);
prevSelected = selected; prevSelected = selected;
selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : keys[selected].trbl[d]; selected = (d === 0 && selected == '0' && prevSelected === '1') ? '1' : screen[selected].trbl[d];
drawKey(selected, keys[selected], true); drawKey(selected, screen[selected], true);
} }
if (global.BTN4) { if (process.env.HWVERSION==1) {
setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100}); setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100});
setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100}); setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100});
setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100}); setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100});
setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100}); setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100});
setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100}); setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100});
swipeEnabled = false;
drawGlobal();
} else { // touchscreen? } else { // touchscreen?
selected = "NONE"; selected = "NONE";
swipeEnabled = true;
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
drawNumbers();
Bangle.on('touch',(n,e)=>{ Bangle.on('touch',(n,e)=>{
for (var key in keys) { for (var key in screen) {
var r = keys[key].xy; if (typeof screen[key] == "undefined") break;
var r = screen[key].xy;
if (e.x>=r[0] && e.y>=r[1] && if (e.x>=r[0] && e.y>=r[1] &&
e.x<r[2] && e.y<r[3]) { e.x<r[2] && e.y<r[3]) {
//print("Press "+key); //print("Press "+key);
@ -391,19 +419,26 @@ if (global.BTN4) {
} }
} }
}); });
var lastX = 0, lastY = 0;
Bangle.on('drag', (e) => {
if (!e.b) {
if (lastX > 50) { // right
drawSpecials();
} else if (lastX < -50) { // left
drawOperators();
} else if (lastY > 50) { // down
drawNumbers();
} else if (lastY < -50) { // up
drawNumbers();
}
lastX = 0;
lastY = 0;
} else {
lastX = lastX + e.dx;
lastY = lastY + e.dy;
}
});
} }
// rescale for non-240px screens
if (g.getWidth()!=240) {
RESULT_HEIGHT = RESULT_HEIGHT*g.getWidth()/240;
for (var k in keys) {
keys[k].xy = keys[k].xy.map(n => n*g.getWidth()/240);
}
}
// draw keys
for (var k in keys) {
if (keys.hasOwnProperty(k)) {
drawKey(k, keys[k], k == selected);
}
}
displayOutput(0); displayOutput(0);

View File

@ -2,3 +2,4 @@
0.02: Make Bangle 2 compatible 0.02: Make Bangle 2 compatible
0.03: Add setting to start week on Sunday 0.03: Add setting to start week on Sunday
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed). 0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
0.05: Update calendar weekend colors for start on Sunday

View File

@ -110,10 +110,19 @@ function drawCalendar(date) {
g.clearRect(0, 0, maxX, maxY); g.clearRect(0, 0, maxX, maxY);
g.setBgColor(bgColorMonth); g.setBgColor(bgColorMonth);
g.clearRect(0, 0, maxX, headerH); g.clearRect(0, 0, maxX, headerH);
if (settings.startOnSun){
g.setBgColor(bgColorWeekend);
g.clearRect(0, headerH + rowH, colW, maxY);
g.setBgColor(bgColorDow);
g.clearRect(0, headerH, maxX, headerH + rowH);
g.setBgColor(bgColorWeekend);
g.clearRect(colW * 6, headerH + rowH, maxX, maxY);
} else {
g.setBgColor(bgColorDow); g.setBgColor(bgColorDow);
g.clearRect(0, headerH, maxX, headerH + rowH); g.clearRect(0, headerH, maxX, headerH + rowH);
g.setBgColor(bgColorWeekend); g.setBgColor(bgColorWeekend);
g.clearRect(colW * 5, headerH + rowH, maxX, maxY); g.clearRect(colW * 5, headerH + rowH, maxX, maxY);
}
for (let y = headerH; y < maxY; y += rowH) { for (let y = headerH; y < maxY; y += rowH) {
g.drawLine(0, y, maxX, y); g.drawLine(0, y, maxX, y);
} }

View File

@ -3,3 +3,4 @@
0.03: Display only minutes:seconds when less than 1 hour left 0.03: Display only minutes:seconds when less than 1 hour left
0.04: Change to 7 segment font, move to top widget bar 0.04: Change to 7 segment font, move to top widget bar
Better auto-update behaviour, less RAM used Better auto-update behaviour, less RAM used
0.05: Fix error running app on new firmwares (fix #1140)

View File

@ -36,13 +36,7 @@ E.on('kill', () => {
function showMenu() { function showMenu() {
const timerMenu = { const timerMenu = {
'': { '': {
'title': 'Set timer', 'title': 'Set timer'
'predraw': function() {
timerMenu.hours.value = settingsChronowid.hours;
timerMenu.minutes.value = settingsChronowid.minutes;
timerMenu.seconds.value = settingsChronowid.seconds;
timerMenu.started.value = settingsChronowid.started;
}
}, },
'< Back' : ()=>{load();}, '< Back' : ()=>{load();},
'Reset values': function() { 'Reset values': function() {

View File

@ -1,2 +1,3 @@
0.01: New clock 0.01: New clock
0.02: Fix icon & add battery warn functionality 0.02: Fix icon & add battery warn functionality
0.03: Theming support & minor fixes

View File

@ -13,6 +13,8 @@ It shows besides time, date and day of week the following information:
## TODO ## TODO
* Show weather information * Show weather information
* Configure which information to show in each circle
* Configure visibility of widgets
## Creator ## Creator
Marco ([myxor](https://github.com/myxor)) Marco ([myxor](https://github.com/myxor))

View File

@ -7,19 +7,23 @@ const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbB
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI")); const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA")); const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
const SETTINGS_FILE = "circlesclock.json";
let settings; let settings;
function loadSettings() { function loadSettings() {
settings = require("Storage").readJSON(SETTINGS_FILE, 1) || { settings = require("Storage").readJSON("circlesclock.json", 1) || {
'maxHR': 200, 'maxHR': 200,
'stepGoal': 10000, 'stepGoal': 10000,
'batteryWarn': 30 '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 colorFg = g.theme.dark ? '#fff' : '#000';
const colorBg = '#000'; const colorBg = g.theme.dark ? '#000' : '#fff';
const colorGrey = '#808080'; const colorGrey = '#808080';
const colorRed = '#ff0000'; const colorRed = '#ff0000';
const colorGreen = '#00ff00'; const colorGreen = '#00ff00';
@ -73,7 +77,7 @@ function drawSteps() {
g.setColor(colorGrey); g.setColor(colorGrey);
g.fillCircle(w1, h3, radiusOuter); g.fillCircle(w1, h3, radiusOuter);
const stepGoal = settings.stepGoal; const stepGoal = settings.stepGoal || 10000;
if (stepGoal > 0) { if (stepGoal > 0) {
let percent = steps / stepGoal; let percent = steps / stepGoal;
if (stepGoal < steps) percent = 1; if (stepGoal < steps) percent = 1;
@ -97,8 +101,9 @@ function drawHeartRate() {
g.setColor(colorGrey); g.setColor(colorGrey);
g.fillCircle(w2, h3, radiusOuter); g.fillCircle(w2, h3, radiusOuter);
if (hrtValue != undefined) { if (hrtValue != undefined && hrtValue > 0) {
const percent = hrtValue / settings.maxHR; const minHR = 40;
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
drawGauge(w2, h3, percent, colorRed); drawGauge(w2, h3, percent, colorRed);
} }
@ -156,25 +161,26 @@ function radians(a) {
return a * Math.PI / 180; return a * Math.PI / 180;
} }
function drawGauge(cx, cy, percent, color) { function drawGauge(cx, cy, percent, color) {
let offset = 30; let offset = 30;
let end = 300; let end = 300;
var i = 0; var i = 0;
var r = radiusInner + 3; var r = radiusInner + 3;
if (percent <= 0) return;
if (percent > 1) percent = 1; if (percent > 1) percent = 1;
var startrot = -offset; var startrot = -offset;
var endrot = startrot - ((end - offset) * percent); var endrot = startrot - ((end - offset) * percent) - 15;
g.setColor(color); g.setColor(color);
const size = 4;
// draw gauge // draw gauge
for (i = startrot; i > endrot; i -= 4) { for (i = startrot; i > endrot - size; i -= size) {
x = cx + r * Math.sin(radians(i)); x = cx + r * Math.sin(radians(i));
y = cy + r * Math.cos(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) { Bangle.on('lock', function(isLocked) {
if (!isLocked) { if (!isLocked) {
Bangle.setHRMPower(1, "watch"); Bangle.setHRMPower(1, "watch");
if (hrtValue == undefined) {
hrtValue = '...';
drawHeartRate();
}
} else { } else {
Bangle.setHRMPower(0, "watch"); Bangle.setHRMPower(0, "watch");
} }
@ -218,6 +228,10 @@ Bangle.on('HRM', function(hrm) {
//} //}
}); });
Bangle.on('charging', function(charging) {
drawBattery();
});
g.clear(); g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();
/* /*
@ -225,10 +239,12 @@ Bangle.loadWidgets();
* so we will blank out the draw() functions of each widget and change the * so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared. * area to the top bar doesn't get cleared.
*/ */
if (typeof WIDGETS === "object") {
for (let wd of WIDGETS) { for (let wd of WIDGETS) {
wd.draw = () => {}; wd.draw = () => {};
wd.area = ""; wd.area = "";
} }
}
loadSettings(); loadSettings();
setInterval(draw, 60000); setInterval(draw, 60000);
draw(); draw();

View File

@ -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).
![](app-screenshot.png)
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)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgZC/AB0BkmCCBsShEAiFBkARLwEBkGSgEECBQdBCIMSAwMYCBEKCgeQgE2gFgCA0C6Moiw1Dk0AhoRGikACIIHDgzECCI5ECg/gEYOACI+ggMti2ACIUkCIImCABARCAAMNCYIADgu0hdACI1twHACQm0rdoCI0BEYsoilRCI9sUgkBoG2rY1JgYjDCINLCI4fCa5ARHAAggCfYIjLgUB0AECCIy7BFwUCR4IKChIRFm1ACJAjGgwRL+AFDiwREI4YABn41FI4hxFn6IJPoh1B/AQFUI4ABh4RGUIsEyARC4ALEwAjECIl/CIkECIsICId+EQkkwEIA4gRDAAojBLwwHFexAADhaFDgETBw6UChdgA4cbCIKuGggCBCIMDCIkQCI8BEwMbCgMSAQIRGgGQQoQRCEYJrGAAMGIgZKDmBzIjARFTwpuHAARoGAAsMwQVCzARLAAPbtq5KAH4AEA"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

247
apps/colorful_clock/app.js Normal file
View File

@ -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');

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

21
apps/colorwheel/LICENSE Normal file
View File

@ -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.

12
apps/colorwheel/README.md Normal file
View File

@ -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
![](BangleJS2-ColorWheel-2.png)
![](BangleJS2-ColorWheel-3.png)
Please note: you may also tap outside the wheel (for black) or inside it (for white).
## License ##
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgYtr4cEiAQMku27ckyVICBUDlmy5YRBpMkCBEE7dtEYYRBwARHm3LEY3QCA0BEAIjFk3boARFhoOBEYs06dNCIogCEYoHCNAoOCEYlNEYUgCIcbEZekCIYODtgjHmgRHtu3///yQrESQfTBIYQBAAPNEYU2SQUGEYd/CIf9EYYRCDAYRF/4KBCIioBAwPfCAn/+wKC7QjEmgRG/xADZAIyBAwIQFCIgjFmoRKEYL4DRgQAFGoojKCIoje7Nly1ZEYLzCkojLNYIRCNZAjIkm/EZ4RH/1ZEYYRMWYpZDy4jJrARBggRBlrYG+VJEYgRBDIVfCIgtCy1QCIZhDCAfkKIW24ARBgJeBEYNbvoQBvOkCIQjDgE2EYYCD2gRCyQQCgEGEYYRBzVp0wRCyAREEY+2CIWAEY4OCEYoQDAAMbEY/SpMgCAkCjIjHzVJEQoABWYIjF7VICI8BBwYjDe4IAHSQ3QCBBuBLgQjCCBIAChu26dMCBgAdA"))

80
apps/colorwheel/app.js Normal file
View File

@ -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);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/ABsH4/wv/H/EMlkMsF4hkYmEEwEwg0gmHCwEh4VAmPi/0j8Vkkcj4MjkU8kckocx4UEmPMoUQgkEEYNGnAFBnEGxFwg0Ek/jzFh8UEkEjkOikUcnFH8MiFIM3wnA8PisEwhnAkECAoMc4EYgk///3//n/Cl/AFYA="))

54
apps/contourclock/app.js Normal file
View File

@ -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();

BIN
apps/contourclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +1,2 @@
0.01: New app 0.01: New app
0.02: Cleanup interface and add settings, widget, add skin temp reporting.

View File

@ -1,19 +1,18 @@
# CoreTemp display # CoreTemp display
Basic bare-bones example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current body core temperature readings. Basic example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current skin and body core temperature readings.
## Usage ## Usage
On startup connects to a CoreTemp device (1809/2A1C) and emits a "Core, temp" value for each reading. Background task connects to any CoreTemp device (2100/2101) and emits a CoreTemp signal value for each reading.
The app simply displays these readings on screen. Application contains three components, one is a background task that monitors the sensor and emits a 'CoreTemp' signal on activity if activated in settings.
The widget shows when the sensor is enabled with a mini value and blinks on use.
The app listens for 'CoreTemp' signals and shows the current skin and core temperatures in large numbers.
## TODO ## TODO
* Integrate with other tracking/sports apps to log data. * Integrate with other tracking/sports apps to log data.
* Add device selection * Add specific device selection
* Provide enable/disable option
* Check status, add Retry/reconnect
* Also provide skin temp reading
## Creator ## Creator

View File

@ -0,0 +1,3 @@
{
"enabled":false
}

View File

@ -1,23 +1,114 @@
//
// If enabled in settings run constantly in background
//
(function() { (function() {
var log = function() {};//print
var settings = {};
var device;
var gatt; var gatt;
var service;
var characteristic;
//Would it be better to scan by uuid rather than name? class CoreSensor {
NRF.requestDevice({ timeout: 20000, filters: [{ name: 'CORE [a]' }] }).then(function(device) { constructor() {
return device.gatt.connect(); this.unit = "";
}).then(function(g) { this.core = -1;
gatt = g; this.skin = -1;
return gatt.getPrimaryService("1809"); this.battery = 0;
}).then(function(service) { }
return service.getCharacteristic("2A1C");
}).then(function(characteristic) { updateSensor(event) {
characteristic.on('characteristicvaluechanged', function(event) { if (event.target.uuid == "00002101-5b1e-4347-b07c-97b514dae121") {
var dv = event.target.value; var dv = event.target.value;
var core = (dv.buffer[2]*256+dv.buffer[1])/100; var flags = dv.buffer[0];
Bangle.emit('Core',{
temp:core if (flags & 8) {
}); this.unit = "F";
} else {
this.unit = "C";
}
if (flags & 1) {
this.skin = (dv.buffer[4] * 256 + dv.buffer[3]) / 100;
} else {
this.skin = 0;
}
if (flags & 2) {
this.core = (dv.buffer[2] * 256 + dv.buffer[1]) / 100;
} else {
this.core = 0;
}
Bangle.emit('CoreTemp',
{core : this.core, skin : this.skin, unit : this.unit});
}
}
updateBatteryLevel(event) {
if (event.target.uuid == "0x2a19")
this.battery = event.target.value.getUint8(0);
}
}
var mySensor = new CoreSensor();
function getSensorBatteryLevel(gatt) {
gatt.getPrimaryService("180f")
.then(function(s) { return s.getCharacteristic("2a19"); })
.then(function(c) {
c.on('characteristicvaluechanged',
(event) => mySensor.updateBatteryLevel(event));
return c.startNotifications();
}); });
}
function connection_setup() {
log("Scanning for CoreTemp sensor...");
NRF.requestDevice({active:true,timeout : 20000, filters : [ {namePrefix : 'CORE'} ]})
.then(function(d) {
device = d;
log("Found device");
return device.gatt.connect();
})
.then(function(g) {
gatt = g;
return gatt.getPrimaryService('00002100-5b1e-4347-b07c-97b514dae121');
})
.then(function(s) {
service = s;
return service.getCharacteristic(
'00002101-5b1e-4347-b07c-97b514dae121');
})
.then(function(c) {
characteristic = c;
characteristic.on('characteristicvaluechanged',
(event) => mySensor.updateSensor(event));
return characteristic.startNotifications(); return characteristic.startNotifications();
}).then(function() { })
.then(function() {
log("Done!");
// getSensorBatteryLevel(gatt);
})
.catch(function(e) {
log(e.toString(), "ERROR");
log(e);
}); });
}
function connection_end() {
if (gatt != undefined)
gatt.disconnect();
}
settings = require("Storage").readJSON("coretemp.json", 1) || {};
log("Settings:");
log(settings);
if (settings.enabled) {
connection_setup();
NRF.on('disconnect', connection_setup);
}
E.on('kill', () => { connection_end(); });
})(); })();

View File

@ -1,19 +1,66 @@
Bangle.setLCDPower(1); // Simply listen for core events and show data
Bangle.setLCDTimeout(0);
var btm = g.getHeight() - 1; var btm = g.getHeight() - 1;
var px = g.getWidth() / 2;
// Dark or light logo
var col = (process.env.HWVERSION == 1) ? 65535 : 0;
var corelogo = {
width : 146,
height : 48,
bpp : 4,
transparent : 0,
palette : new Uint16Array([ col, col, 2854, 1419 ]),
buffer :
require("heatshrink")
.decompress(atob(
"AEUDmczmBD/I4xJ/AAMCkBHFAAJG8kQABJAJHFSVURAAUQRphHCkQGBJAySngJHDJRhHEJALZDAgiSBEQ0RPBIAKHAwQQI4xIEaoQFEEZpIULSRHFkDZDBwZIMEYhITa44SKSAxIDSARIDJ4IjKJCpHNEoiQGJDA2CJCQSOCYaQGJDBsCGiKQGTZIJCI4xBEJBAAEFpQAPDQoMGBQyOGIJJPGF6AALC5glCbJAQEgZCEAoowTSBypJBwKQMIQaSBAgZIJWw5ITB5RTDSBLbEAAjDOPRIVabIiQFJBCQKPYhIVCRxIEBg7WDSBpIVbJ5IQJIqQBgZIiCh7ZLJIriDbhJI3JoxIebIZITI6BIjCZ5IRI4RIPHAYAJJH4AIUAJIzHIhI/SAwzBJH6QGJH5HIHApI2HCIAJL4pITkATOJQJIMHCJeFJD8zaZCQHJCEBJCUCJCKPBJBhWGJEcia5oACJBSfHJB4QMJA6SLI4ZIKPAg3QJCUAJCbbBJETbPJAbbKbIhIBYJpIQbZ5UDbZzZFPBxIVSRIOBJA5JISAhIIF4ZIUfQpJHEwQKDJAhJHbJbBJJCIZECY4KGSQoABBIZOBSBbbIJC6IEBQqSJJoyQLbZBIRbYoAKJAaSHJAjbCF541RSRISLSRkgJAKQKbY5ISJJyQDSRyQMbYxITChhHFSRhGMbY5IUCpRHHJJZITiBIVbpBHJbpJHPFhBITfI4ANIwcgI6AAV"))
};
function onCore(c) { function onCore(c) {
var px = g.getWidth()/2; // Large or small font
g.setFontAlign(0,0); var sz = ((process.env.HWVERSION == 1) ? 3 : 2);
g.clearRect(0,24,g.getWidth(),80);
var str = c.temp + "C";
g.setFontVector(40).drawString(str,px,45);
}
Bangle.on('Core', onCore);
g.setFontAlign(0, 0);
g.clearRect(0, 32 + 48, g.getWidth(), 32 + 48 + 24 * 4);
g.setColor(g.theme.dark ? "#CCC" : "#333"); // gray
g.setFont("6x8", sz).drawString(
"Core: " + ((c.core < 327) ? (c.core + c.unit) : 'n/a'), px, 48 + 48);
g.setFont("6x8", sz).drawString("Skin: " + c.skin + c.unit, px, 48 + 48 + 24);
}
// Background task will activate once settings are enabled.
function enableSensor() {
settings = require("Storage").readJSON("coretemp.json", 1) || {};
if (!settings.enabled) {
settings.enabled = true;
require("Storage").write("coretemp.json", settings);
drawBackground("Waiting for\ndata...");
}
}
function drawBackground(message) {
g.clear(); g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
g.reset().setFont("6x8", 2).setFontAlign(0, 0); g.reset().setFont("6x8", 2).setFontAlign(0, 0);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); g.drawImage(corelogo, px - 146 / 2, 30);
g.drawString(message, g.getWidth() / 2, g.getHeight() / 2 + 16);
}
Bangle.on('CoreTemp', onCore);
settings = require("Storage").readJSON("coretemp.json", 1) || {};
if (!settings.enabled) {
drawBackground("Sensor off\nBTN" +
((process.env.HWVERSION == 1) ? '2' : '1') + " to enable");
} else {
drawBackground("Waiting for\ndata...");
}
setWatch(() => { enableSensor(); }, (process.env.HWVERSION == 1) ? BTN2 : BTN1,
{repeat : false});

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

47
apps/coretemp/settings.js Normal file
View File

@ -0,0 +1,47 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'coretemp.json'
// initialize with default settings...
let s = {
'enabled': true,
}
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage')
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
for (const key in saved) {
s[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function (value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
}
}
function updateSettings() {
require("Storage").write("coretemp.json", s);
if (WIDGETS["coretemp"])
WIDGETS["coretemp"].reload();
return;
}
const menu = {
'' : {'title' : 'CoreTemp sensor'},
'< Back' : back,
'Enabled' : {
value : !!s.enabled,
format : v => v ? "Yes" : "No",
onchange : v => {
s.enabled = v;
updateSettings();
}
}
}
E.showMenu(menu);
})

66
apps/coretemp/widget.js Normal file
View File

@ -0,0 +1,66 @@
// TODO Change to a generic multiple sensor widget?
(() => {
var settings = {};
var count = 0;
var core = 0;
// draw your widget
function draw() {
if (!settings.enabled)
return;
g.reset();
g.setFont("6x8", 1).setFontAlign(0, 0);
g.setFontAlign(0, 0);
g.clearRect(this.x, this.y, this.x + 23, this.y + 23);
if (count & 1) {
g.setColor("#0f0"); // green
} else {
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
}
g.drawImage(
atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),
this.x+(24-12)/2,this.y+1);
g.setColor(g.theme.fg);
g.drawString(parseInt(core)+"\n."+parseInt((core*100)%100), this.x + 24 / 2, this.y + 18);
g.setColor(-1);
}
// Set a listener to 'blink'
function onTemp(temp) {
count = count + 1;
core = temp.core;
WIDGETS["coretemp"].draw();
}
// Called by sensor app to update status
function reload() {
settings = require("Storage").readJSON("coretemp.json", 1) || {};
Bangle.removeListener('CoreTemp', onTemp);
if (settings.enabled) {
WIDGETS["coretemp"].width = 24;
Bangle.on('CoreTemp', onTemp);
} else {
WIDGETS["coretemp"].width = 0;
count = 0;
}
}
// add the widget
WIDGETS["coretemp"] = {
area : "tl",
width : 24,
draw : draw,
reload : function() {
reload();
Bangle.drawWidgets(); // relayout all widgets
}
};
// load settings, set correct widget width
reload();
})()

1
apps/diract/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

28
apps/diract/README.md Normal file
View File

@ -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.

View File

@ -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"))

548
apps/diract/diract.js Normal file
View File

@ -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();

BIN
apps/diract/diract.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,8 +1,18 @@
# Desktop style App Launcher # Desktop style App Launcher
Bangle 1:
![](screenshot.jpg) ![](screenshot.jpg)
In the picture above, the Settings app is selected. In the picture above, the Settings app is selected.
Bangle 2:
![shot1](https://user-images.githubusercontent.com/89286474/146471756-ec6d16de-6916-4fde-b991-ba88c2c8fa1a.png)
![shot2](https://user-images.githubusercontent.com/89286474/146471758-3a9fce80-e047-4c7a-829a-e780c764b921.png)
![shot3](https://user-images.githubusercontent.com/89286474/146471760-5497fd1b-8e82-4fd5-a4e3-4734701a7dbd.png)
## Controls- Bangle ## Controls- Bangle
**BTN1** - move backward through app icons on a page **BTN1** - move backward through app icons on a page

View File

@ -2,3 +2,4 @@
0.03: Show number of satellites while waiting for fix 0.03: Show number of satellites while waiting for fix
0.04: Add Maidenhead readout of GPS location 0.04: Add Maidenhead readout of GPS location
0.05: Refactor to use 'layout' library for multi-device support 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

View File

@ -16,14 +16,20 @@ var lastFix = {
time: 0, time: 0,
satellites: 0 satellites: 0
}; };
var nofix = 0; var SATinView = 0;
var nofBD = 0;
var nofGP = 0;
function formatTime(now) { function formatTime(now) {
if (now == undefined) {
return "no GPS time available";
} else {
var fd = now.toUTCString().split(" "); var fd = now.toUTCString().split(" ");
var time = fd[4].substr(0, 5); var time = fd[4].substr(0, 5);
var date = [fd[0], fd[1], fd[2]].join(" "); var date = [fd[0], fd[1], fd[2]].join(" ");
return time + " - " + date; return time + " - " + date;
} }
}
function getMaidenHead(param1,param2){ function getMaidenHead(param1,param2){
var lat=-100.0; var lat=-100.0;
var lon=0.0; var lon=0.0;
@ -77,9 +83,9 @@ function onGPS(fix) {
{type:"txt", font:"6x8", label:"Waiting for GPS" }, {type:"txt", font:"6x8", label:"Waiting for GPS" },
{type:"h", c: [ {type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, {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}); ]},{lazy:true});
} }
g.clearRect(0,24,g.getWidth(),g.getHeight()); g.clearRect(0,24,g.getWidth(),g.getHeight());
@ -87,7 +93,6 @@ function onGPS(fix) {
} }
lastFix = fix; lastFix = fix;
if (fix.fix) { if (fix.fix) {
nofix = 0;
var locale = require("locale"); var locale = require("locale");
var satellites = fix.satellites; var satellites = fix.satellites;
var maidenhead = getMaidenHead(fix.lat,fix.lon); var maidenhead = getMaidenHead(fix.lat,fix.lon);
@ -100,12 +105,21 @@ function onGPS(fix) {
layout.maidenhead.label = "Maidenhead: "+maidenhead; layout.maidenhead.label = "Maidenhead: "+maidenhead;
} else { } else {
layout.sat.label = fix.satellites; layout.sat.label = fix.satellites;
nofix = (nofix+1) % 4; layout.progress.label = "in view: " + SATinView;
layout.progress.label = ".".repeat(nofix) + " ".repeat(4-nofix);
} }
layout.render(); 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.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
Bangle.on('GPS', onGPS); Bangle.on('GPS', onGPS);
Bangle.on('GPS-raw', onGPSraw);

View File

@ -1,2 +1,3 @@
0.01: base code 0.01: Base code
0.02: saved settings when switching color scheme 0.02: Saved settings when switching color scheme
0.03: Added Button 3 opening messages (if app is installed)

View File

@ -5,6 +5,7 @@ A High-contrast, black-on-white or white-on-black clock displaying huge pixel di
## Usage ## Usage
* BTN 1 switches between the two modes : black-on-white or white-on-black * 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! * That's it!
## Issues and Requests ## Issues and Requests

View File

@ -129,6 +129,7 @@ function updateTime()
g.setFontAlign(0, -1, 0); g.setFontAlign(0, -1, 0);
g.drawString(fmtDate(d,mo,y,hour), 120, 120); g.drawString(fmtDate(d,mo,y,hour), 120, 120);
} }
drawMessages();
} }
function drawDigits(x, value) function drawDigits(x, value)
@ -222,6 +223,55 @@ function flipColors()
setColorScheme(0); 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() // MAIN FUNCTION()
@ -238,6 +288,7 @@ setInterval(updateTime, interval);
// Handle Button Press // Handle Button Press
setWatch(flipColors, BTN1, true); setWatch(flipColors, BTN1, true);
setWatch(Bangle.showLauncher, BTN2, false); setWatch(Bangle.showLauncher, BTN2, false);
setWatch(handleMessages, BTN3, true);
// Handle redraw on LCD on / fullscreen notifications dismissed // Handle redraw on LCD on / fullscreen notifications dismissed
Bangle.on('lcdPower', (on) => { if(on) redraw(); }); Bangle.on('lcdPower', (on) => { if(on) redraw(); });

View File

@ -5,3 +5,4 @@
0.05: Additional icons for (1) charging and (2) bat < 30%. 0.05: Additional icons for (1) charging and (2) bat < 30%.
0.06: Fix - Alarm disabled, if clock was closed. 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.

View File

@ -1,18 +1,33 @@
# LCARS clock # LCARS clock
A simple LCARS inspired clock. A simple LCARS inspired clock.
Note: To display the steps, its necessary to install Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown.
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget). To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps)
## Features ## Features
* LCARS Style watch face * LCARS Style watch face.
* Shows satate (charging, out of battery etc.) * Full screen mode - widgets are still loaded.
* SHows data that can be configured (steps, HRM, temperature etc.) * Supports multiple screens with different data.
* Swipe left/right to activate an alarm * [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
![](screenshot.png)
![](screenshot_2.png)
## Icons ## 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> <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 ## Contributors
Made by [David Peer](https://github.com/peerdavid) - Creator: [David Peer](https://github.com/peerdavid).
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
apps/lcars/bg_left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

BIN
apps/lcars/bg_right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -15,85 +15,96 @@ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) { for (const key in saved_settings) {
settings[key] = saved_settings[key] 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; let hrmValue = 0;
var connected = NRF.getSecurityStatus().connected;
var plotWeek = false;
/* /*
* Requirements and globals * Requirements and globals
*/ */
const locale = require('locale'); const locale = require('locale');
var backgroundImage = { var bgLeft = {
width : 176, height : 151, bpp : 3, width : 27, height : 176, bpp : 3,
transparent : 2, transparent : 0,
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")) 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 = { var iconEarth = {
text: "EARTH",
width : 50, height : 50, bpp : 3, 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=")) 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 = { var iconSaturn = {
text: "SATURN",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 1, transparent : 1,
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA")) buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
} };
var iconMoon = { var iconMoon = {
text: "MOON",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 1, 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==")) 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 = { var iconMars = {
text: "MARS",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 1, transparent : 1,
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA==")) buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
} };
var iconSatellite = { var iconSatellite = {
text: "GPS ON",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 2, 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=")) 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 = { var iconCharging = {
text: "CHARGE",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 5, transparent : 5,
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A")) buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
} };
var iconNoBattery = { var iconNoBattery = {
text: "NO BAT", text: "NO BAT",
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
transparent : 1, transparent : 1,
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA")) buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
} };
// Font to use: // Font to use:
// <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet"> // <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet">
Graphics.prototype.setFontAntonioSmall = function(scale) { Graphics.prototype.setFontAntonioMedium = function(scale) {
// Actual height 18 (17 - 0) // Actual height 20 (19 - 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)); 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) { Graphics.prototype.setFontAntonioLarge = function(scale) {
// Actual height 34 (34 - 1) // Actual height 39 (39 - 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)); 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 * Draw watch face
@ -108,117 +119,299 @@ function queueDraw() {
} }
function printData(key, y){ function printData(key, y, c){
g.setFontAlign(-1,-1,0); g.setFontAlign(-1,-1,0);
var text = "ERR";
var value = "NOT FOUND";
if(key == "Battery"){ if(key == "Battery"){
var bat = E.getBattery(); text = "BAT";
g.drawString("BAT:", 30, y); value = E.getBattery() + "%";
g.drawString(bat+ "%", 68, y);
} else if(key == "Steps"){ } else if(key == "Steps"){
var steps = getSteps(); text = "STEP";
g.drawString("STEP:", 30, y); value = getSteps();
g.drawString(steps, 68, y);
} else if(key == "Temp."){ } else if(key == "Temp."){
var temperature = Math.floor(E.getTemperature()); text = "TEMP";
g.drawString("TEMP:", 30, y); value = Math.floor(E.getTemperature()) + "C";
g.drawString(temperature + "C", 69, y);
} else if(key == "HRM"){ } else if(key == "HRM"){
g.drawString("HRM:", 30, y); text = "HRM";
g.drawString(hrmValue, 69, y); 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 { } 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(){ function draw(){
// First handle alarm to show this correctly afterwards // First handle alarm to show this correctly afterwards
handleAlarm(); handleAlarm();
// Next draw the watch face // Next draw the watch face
g.reset(); g.reset();
g.clearRect(0, 24, g.getWidth(), g.getHeight()); g.clearRect(0, 0, g.getWidth(), g.getHeight());
// Draw background image // Draw current lcars position
g.drawImage(backgroundImage, 0, 24); if(lcarsViewPos == 0){
drawPosition0();
// Draw symbol } else if (lcarsViewPos == 1) {
var bat = E.getBattery(); drawPosition1();
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);
} }
// 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 // Queue draw in one minute
queueDraw(); queueDraw();
} }
/* /*
* Step counter via widget * Step counter via widget
*/ */
function getSteps() { function getSteps() {
if (stepsWidget() !== undefined) var steps = 0
return stepsWidget().getSteps(); try {
return "???"; health = require("health");
} catch(ex) {
return steps;
} }
function stepsWidget() { health.readDay(new Date(), h=>steps+=h.steps);
if (WIDGETS.activepedom !== undefined) { return steps;
return WIDGETS.activepedom;
} else if (WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom;
}
return undefined;
} }
/*
* HRM Listener
*/
Bangle.on('HRM', function (hrm) {
hrmValue = hrm.bpm;
});
/* /*
* Handle alarm * Handle alarm
@ -228,7 +421,7 @@ function getCurrentTimeInMinutes(){
} }
function isAlarmEnabled(){ function isAlarmEnabled(){
return settings.alarm > 0; return settings.alarm >= 0;
} }
function getAlarmMinutes(){ function getAlarmMinutes(){
@ -253,65 +446,130 @@ function handleAlarm(){
.then(() => new Promise(resolve => setTimeout(resolve, t))) .then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1)) .then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t))) .then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1)); .then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
.then(() => {
// Update alarm state to disabled // Update alarm state to disabled
settings.alarm = -1; settings.alarm = -1;
Storage.writeJSON(SETTINGS_FILE, settings); Storage.writeJSON(SETTINGS_FILE, settings);
});
} }
/* /*
* Swipe to set an alarm * Listeners
*/ */
Bangle.on('swipe',function(dir) { Bangle.on('lcdPower',on=>{
// Increase alarm if (on) {
if(dir == -1){ // 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()){ if(isAlarmEnabled()){
settings.alarm += 5; settings.alarm += 5;
} else { } else {
settings.alarm = getCurrentTimeInMinutes() + 5; settings.alarm = getCurrentTimeInMinutes() + 5;
} }
Storage.writeJSON(SETTINGS_FILE, settings);
} }
// Decrease alarm
if(dir == +1){ function decreaseAlarm(){
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){ if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
settings.alarm -= 5; settings.alarm -= 5;
} else { } else {
settings.alarm = -1; settings.alarm = -1;
} }
Storage.writeJSON(SETTINGS_FILE, settings);
} }
// Update UI
draw();
// Update alarm state // Thanks to the app "gbmusic" for this code to detect swipes in all 4 directions.
Storage.writeJSON(SETTINGS_FILE, settings); 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();
}
}); });
/* /*
* Stop updates when LCD is off, restart when on * Lets start widgets, listen for btn etc.
*/ */
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI("clock");
// Load widgets - needed by draw
Bangle.loadWidgets(); 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 // Clear the screen once, at startup and draw clock
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
draw(); draw();
// After drawing the watch face, we can draw the widgets // After drawing the watch face, we can draw the widgets
Bangle.drawWidgets(); // Bangle.drawWidgets();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -18,14 +18,14 @@
storage.write(SETTINGS_FILE, settings) storage.write(SETTINGS_FILE, settings)
} }
var data_options = ['Battery', 'Steps', 'Temp.', "HRM"]; var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF"];
E.showMenu({ E.showMenu({
'': { 'title': 'LCARS Clock' }, '': { 'title': 'LCARS Clock' },
'< Back': back, '< Back': back,
'Row 1': { 'Row 1': {
value: 0 | data_options.indexOf(settings.dataRow1), value: 0 | data_options.indexOf(settings.dataRow1),
min: 0, max: 3, min: 0, max: 4,
format: v => data_options[v], format: v => data_options[v],
onchange: v => { onchange: v => {
settings.dataRow1 = data_options[v]; settings.dataRow1 = data_options[v];
@ -34,7 +34,7 @@
}, },
'Row 2': { 'Row 2': {
value: 0 | data_options.indexOf(settings.dataRow2), value: 0 | data_options.indexOf(settings.dataRow2),
min: 0, max: 3, min: 0, max: 4,
format: v => data_options[v], format: v => data_options[v],
onchange: v => { onchange: v => {
settings.dataRow2 = data_options[v]; settings.dataRow2 = data_options[v];
@ -43,7 +43,7 @@
}, },
'Row 3': { 'Row 3': {
value: 0 | data_options.indexOf(settings.dataRow3), value: 0 | data_options.indexOf(settings.dataRow3),
min: 0, max: 3, min: 0, max: 4,
format: v => data_options[v], format: v => data_options[v],
onchange: v => { onchange: v => {
settings.dataRow3 = data_options[v]; settings.dataRow3 = data_options[v];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
apps/lcars/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -457,8 +457,8 @@ var locales = {
distance: { "0": "m", "1": "km" }, distance: { "0": "m", "1": "km" },
temperature: '°C', temperature: '°C',
ampm: { 0: "", 1: "" }, ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00 timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020 datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
abday: "dom,lun,mar,mer,gio,ven,sab", abday: "dom,lun,mar,mer,gio,ven,sab",
@ -475,8 +475,8 @@ var locales = {
distance: { "0": "m", "1": "km" }, distance: { "0": "m", "1": "km" },
temperature: '°C', temperature: '°C',
ampm: { 0: "", 1: "" }, ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00 timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020 datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
abday: "dom,lun,mar,mer,gio,ven,sab", abday: "dom,lun,mar,mer,gio,ven,sab",
@ -589,7 +589,7 @@ var locales = {
month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec", month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec",
abday: "ne,po,út,st,čt,pá,so", abday: "ne,po,út,st,čt,pá,so",
day: "neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota", 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": { "sl_SI": {
lang: "sl_SI", lang: "sl_SI",

View File

@ -21,3 +21,5 @@
Add 'Delete All' option to message options Add 'Delete All' option to message options
Now update correctly when 'require("messages").clearAll()' is called Now update correctly when 'require("messages").clearAll()' is called
0.14: Hide widget when all unread notifications are dismissed from phone 0.14: Hide widget when all unread notifications are dismissed from phone
0.15: Don't buzz when Quiet Mode is active
0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147)

View File

@ -52,7 +52,7 @@ var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
if (!Array.isArray(MESSAGES)) MESSAGES=[]; if (!Array.isArray(MESSAGES)) MESSAGES=[];
var onMessagesModified = function(msg) { var onMessagesModified = function(msg) {
// TODO: if new, show this new one // 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(); if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
else Bangle.buzz(); else Bangle.buzz();
} }
@ -243,7 +243,8 @@ function showMessage(msgid) {
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1}); checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1});
}}); }});
} }
lines = g.wrapString(msg.body, g.getWidth()-10); var bodyFont = fontMedium;
lines = g.setFont(bodyFont).wrapString(msg.body, g.getWidth()-10);
var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n"); var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n");
layout = new Layout({ type:"v", c: [ layout = new Layout({ type:"v", c: [
{type:"h", fillx:1, bgCol:colBg, c: [ {type:"h", fillx:1, bgCol:colBg, c: [
@ -256,7 +257,7 @@ function showMessage(msgid) {
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{}, title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
]}, ]},
]}, ]},
{type:"txt", font:fontMedium, label:body, fillx:1, filly:1, pad:2 }, {type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2 },
{type:"h",fillx:1, c: buttons} {type:"h",fillx:1, c: buttons}
]}); ]});
g.clearRect(Bangle.appRect); g.clearRect(Bangle.appRect);

View File

@ -43,7 +43,8 @@ exports.pushMessage = function(event) {
// otherwise load messages/show widget // otherwise load messages/show widget
var loadMessages = Bangle.CLOCK || event.important; var loadMessages = Bangle.CLOCK || event.important;
// first, buzz // 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(); WIDGETS.messages.buzz();
// after a delay load the app, to ensure we have all the messages // after a delay load the app, to ensure we have all the messages
if (exports.messageTimeout) clearTimeout(exports.messageTimeout); if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
@ -51,7 +52,7 @@ exports.pushMessage = function(event) {
exports.messageTimeout = undefined; exports.messageTimeout = undefined;
// if we're in a clock or it's important, go straight to messages app // if we're in a clock or it's important, go straight to messages app
if (loadMessages) return load("messages.app.js"); 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(); WIDGETS.messages.show();
}, 500); }, 500);
} }

View File

@ -26,6 +26,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
WIDGETS["messages"].width=0; WIDGETS["messages"].width=0;
Bangle.drawWidgets(); Bangle.drawWidgets();
},buzz:function() { },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 || "."; let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
function b() { function b() {
var c = v[0]; var c = v[0];

View File

@ -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.

View File

@ -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).
![](app-screenshot.png)
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)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AEBhCjgCBgeAgF8AoXggHwCIXwgfADAX8h4TBAAM+jwkDj/4AocPDwIACgdgBYgoCAAMEuB+/AH4="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

230
apps/minimal_clock/app.js Normal file
View File

@ -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');

1
apps/promenu/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

1
apps/promenu/README.md Normal file
View File

@ -0,0 +1 @@

176
apps/promenu/boot.js Normal file
View File

@ -0,0 +1,176 @@
E.showMenu = function(items) {
function RectRnd(x1,y1,x2,y2,r) {
pp = [];
pp.push.apply(pp,g.quadraticBezier([x2-r,y1, x2,y1,x2,y1+r]));
pp.push.apply(pp,g.quadraticBezier([x2,y2-r,x2,y2,x2-r,y2]));
pp.push.apply(pp,g.quadraticBezier([x1+r,y2,x1,y2,x1,y2-r]));
pp.push.apply(pp,g.quadraticBezier([x1,y1+r,x1,y1,x1+r,y1]));
return pp;
}
function fillRectRnd(x1,y1,x2,y2,r,c) {
g.setColor(c);
g.fillPoly(RectRnd(x1,y1,x2,y2,r),1);
g.setColor(255,255,255);
}
function drawRectRnd(x1,y1,x2,y2,r,c) {
g.setColor(c);
g.drawPoly(RectRnd(x1,y1,x2,y2,r),1);
g.setColor(255,255,255);
}
g.reset().clearRect(Bangle.appRect); // clear if no menu supplied
Bangle.setLCDPower(1); // ensure screen is on
if (!items) {
Bangle.setUI();
return;
}
var menuItems = Object.keys(items);
var options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
options.fontHeight = options.fontHeight||35;
if (options.selected === undefined)
options.selected = 0;
if (!options.fontHeight)
options.fontHeight = 6;
var ar = Bangle.appRect;
var x = ar.x;
var x2 = ar.x2-11; // padding at side for up/down
var y = ar.y;
var y2 = ar.y2 - 20; // padding at end for arrow
if (options.title)
y += options.fontHeight+2;
var loc = require("locale");
var l = {
lastIdx : 0,
draw : function(rowmin,rowmax) {
var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length);
var idx = E.clip(options.selected-(rows>>1),0,menuItems.length-rows);
if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled
l.lastIdx = idx;
var iy = y-5;
g.reset().setFont('6x8',2).setFontAlign(0,-1,0);
if (options.predraw) options.predraw(g);
if (rowmin===undefined && options.title) {
g.drawString(options.title,(x+x2)/2,y-options.fontHeight+5);
g.drawLine(x,y-7,x2,y-7);
}
if (rowmin!==undefined) {
if (idx<rowmin) {
iy += options.fontHeight*(rowmin-idx);
idx=rowmin;
}
if (idx+rows>rowmax) {
rows = 1+rowmax-rowmin;
}
}
while (rows--) {
var name = menuItems[idx];
var item = items[name];
var hl = (idx==options.selected && !l.selectEdit);
if(g.theme.dark){
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+20);
}else{
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-10);
}
g.setColor(hl ? g.theme.fgH : g.theme.fg);
g.setFontAlign(-1,-1);
if(loc.translate(name).length >= 11 && "object" == typeof item){
var v = item.value;
if (item.format) v=item.format(v);
v = loc.translate(""+v);
g.drawString(loc.translate(name).substring(0, 15-v.length)+"...",x+8,iy+7);
}else{
if(loc.translate(name).length >= 15){
g.drawString(loc.translate(name).substring(0, 15)+"...",x+8,iy+7);
}else{
g.drawString(loc.translate(name),x+8,iy+7);
}
}
if ("object" == typeof item) {
var xo = x2;
var v = item.value;
if (item.format) v=item.format(v);
v = loc.translate(""+v);
if (l.selectEdit && idx==options.selected) {
xo -= 24 + 1;
g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2});
}
g.setFontAlign(1,-1);
g.drawString(v,xo-4,iy+8.5);
}
g.setColor(g.theme.fg);
iy += options.fontHeight;
idx++;
}
g.setFontAlign(-1,-1);
var more = idx<menuItems.length;
g.drawImage("\b\b\x01\x108|\xFE\x10\x10\x10\x10"/*E.toString(8,8,1,
0b00010000,
0b00111000,
0b01111100,
0b11111110,
0b00010000,
0b00010000,
0b00010000,
0b00010000
)*/,x2+2,40);
g.drawImage("\b\b\x01\x10\x10\x10\x10\xFE|8\x10"/*E.toString(8,8,1,
0b00010000,
0b00010000,
0b00010000,
0b00010000,
0b11111110,
0b01111100,
0b00111000,
0b00010000
)*/,x2+2,194);
g.drawImage("\b\b\x01\x00\b\f\x0E\xFF\x0E\f\b"/*E.toString(8,8,1,
0b00000000,
0b00001000,
0b00001100,
0b00001110,
0b11111111,
0b00001110,
0b00001100,
0b00001000
)*/,x2+2,116);
g.setColor(more?g.theme.fg:g.theme.bg).fillPoly([104,220,136,220,120,228]);
g.flip();
},
select : function() {
var item = items[menuItems[options.selected]];
if ("function" == typeof item) item(l);
else if ("object" == typeof item) {
// if a number, go into 'edit mode'
if ("number" == typeof item.value)
l.selectEdit = l.selectEdit?undefined:item;
else { // else just toggle bools
if ("boolean" == typeof item.value) item.value=!item.value;
if (item.onchange) item.onchange(item.value);
}
l.draw();
}
},
move : function(dir) {
var item = l.selectEdit;
if (item) {
item = l.selectEdit;
item.value -= (dir||1)*(item.step||1);
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
if (item.onchange) item.onchange(item.value);
l.draw(options.selected,options.selected);
} else {
var lastSelected=options.selected;
options.selected = (dir+options.selected+menuItems.length)%menuItems.length;
l.draw(Math.min(lastSelected,options.selected), Math.max(lastSelected,options.selected));
}
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
return l;
};

BIN
apps/promenu/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwyBC/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFE+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AAM+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AAM+rwBSAAU2j02js1/oBC7oBBlu9AIWdluclt8AIMlrgBCngBCjgBBjn7AIXbAIW7AIMUzYBFLv5ddMJ5d/LqJfeLv5fdLv5fQMJhd/LqQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ADIA=="))

View File

@ -3,3 +3,5 @@
0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns. 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.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/

View File

@ -29,12 +29,13 @@ Then launch the linked apps directly from the clock screen by simply drawing the
## Detailed Steps ## Detailed Steps
From the main menu you can: From the main menu you can:
- Add a new pattern and link it to an app (first entry) - Add a new pattern and link it to an app (first entry)
- To create a new pattern first select "Add Pattern" - 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 - 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 - 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 don't like the pattern, simply re-draw it. The previous pattern will be discarded.
- If you are happy with the pattern tap on screen or press the button to continue - If you are happy with the pattern press the button to continue
- Now select the app you want to launch with the pattern. - Now select the app you want to launch with the pattern.
- Note, you can bind multiple patterns to the same app. - Note, you can bind multiple patterns to the same app.
- Manage created patterns (second entry) - Manage created patterns (second entry)
@ -47,14 +48,22 @@ From the main menu you can:
## FAQ ## 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. 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). 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! 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/)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -114,7 +114,6 @@ var showMainMenu = () => {
E.showMenu(mainmenu); E.showMenu(mainmenu);
}; };
var positions = [];
var recognizeAndDrawPattern = () => { var recognizeAndDrawPattern = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
E.showMenu(); E.showMenu();
@ -135,150 +134,55 @@ var recognizeAndDrawPattern = () => {
resolve(pattern.join("")); resolve(pattern.join(""));
}; };
setWatch(() => finishHandler(), BTN); 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) => { var dragHandler = (position) => {
log(position);
positions.push(position); positions.push(position);
if (position.b === 0 || positions.length >= 200) {
debounce().then(() => { pattern = getPattern(positions).split("");
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");
g.clear(); g.clear();
drawCirclesWithPattern(pattern); drawCirclesWithPattern(pattern);
}); positions = [];
}
}; };
Bangle.on("drag", dragHandler); Bangle.on("drag", dragHandler);
}); });
}; };
@ -488,7 +392,8 @@ var drawCircle = (circle, drawBuffer, scale) => {
log("drawing circle"); log("drawing circle");
log({ x: x, y: y, r: r }); log({ x: x, y: y, r: r });
drawBuffer.drawCircle(x, y, r); drawBuffer.setColor(0);
drawBuffer.fillCircle(x, y, r);
}; };
var cachedCirclesDrawings = {}; var cachedCirclesDrawings = {};
@ -533,8 +438,11 @@ var drawCirclesWithPattern = (pattern, options) => {
{ msb: true } { 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.setFontAlign(0, 0);
drawBuffer.setFont("Vector", 40 * scale); drawBuffer.setFont("Vector", 40 * scale);
pattern.forEach((circleIndex, patternIndex) => { pattern.forEach((circleIndex, patternIndex) => {
@ -545,12 +453,12 @@ var drawCirclesWithPattern = (pattern, options) => {
circle.y * scale circle.y * scale
); );
}); });
image = { image = {
width: drawBuffer.getWidth(), width: drawBuffer.getWidth(),
height: drawBuffer.getHeight(), height: drawBuffer.getHeight(),
bpp: 1, bpp: 1,
buffer: drawBuffer.buffer, buffer: drawBuffer.buffer,
palette: new Uint16Array([g.theme.fg, g.theme.bg], 0, 1),
}; };
if (enableCaching) { if (enableCaching) {
@ -563,16 +471,6 @@ var drawCirclesWithPattern = (pattern, options) => {
g.drawImage(image, offset.x, offset.y); 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 // 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 // run main function
////// //////

View File

@ -7,16 +7,10 @@
}; };
var storedPatterns; var storedPatterns;
var positions = [];
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
log(positions.length);
var CIRCLE_RADIUS = 25; var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS; var CIRCLE_RADIUS_2 = Math.pow(CIRCLE_RADIUS, 2);
var positions = [];
var getPattern = (positions) => {
var circles = [ var circles = [
{ x: 25, y: 25, i: 0 }, { x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 }, { x: 87, y: 25, i: 1 },
@ -28,107 +22,36 @@
{ x: 87, y: 150, i: 7 }, { x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 }, { x: 150, y: 150, i: 8 },
]; ];
var pattern = []; return positions.reduce((pattern, p, i, arr) => {
var idx = circles.findIndex((c) => {
var step = Math.floor(positions.length / 100) + 1; var dx = p.x > c.x ? p.x - c.x : c.x - p.x;
if (dx > CIRCLE_RADIUS) {
var p, a, b, circle; return false;
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);
} }
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) {
circle = circles[1]; return true;
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);
} }
return dx * dx + dy * dy <= CIRCLE_RADIUS_2;
});
if (idx >= 0) {
pattern += circles[idx].i;
circles.splice(idx, 1);
} }
if (circles.length === 0) {
circle = circles[2]; arr.splice(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(2, 1);
} }
} return pattern;
}, "");
circle = circles[3]; };
if (circle) { var dragHandler = (position) => {
a = p.x - circle.x; positions.push(position);
b = p.y - circle.y; if (position.b === 0 || positions.length >= 200) {
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { var pattern = getPattern(positions);
pattern.push(circle.i); log(pattern);
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 (pattern) { if (pattern) {
if (storedPatterns[pattern]) { if (storedPatterns[pattern]) {
@ -145,21 +68,9 @@
} }
} }
} }
});
};
var debounceTimeoutId; positions = [];
var debounce = (delay) => {
if (debounceTimeoutId) {
clearTimeout(debounceTimeoutId);
} }
return new Promise((resolve) => {
debounceTimeoutId = setTimeout(() => {
debounceTimeoutId = undefined;
resolve();
}, delay || 500);
});
}; };
var sui = Bangle.setUI; var sui = Bangle.setUI;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -2,3 +2,4 @@
0.02: Add posibillity to generate Wifi code. 0.02: Add posibillity to generate Wifi code.
0.03: Forces integer scaling and adds more configuration (error correction, description, display) 0.03: Forces integer scaling and adds more configuration (error correction, description, display)
0.04: Allow scanning of QR codes from camera or file 0.04: Allow scanning of QR codes from camera or file
0.05: Change brightness on touch

View File

@ -72,8 +72,10 @@
<p>Additional options:</p> <p>Additional options:</p>
<input type="checkbox" id="preventIntegerScaling" name="preventIntegerScaling"/> <input type="checkbox" id="preventIntegerScaling" name="preventIntegerScaling"/>
<label for="preventIntegerScaling">Prevent integer scaling</label></br> <label for="preventIntegerScaling">Prevent integer scaling</label></br>
<input type="checkbox" id="preventBrightnessChangeOnTouch" name="preventBrightnessChangeOnTouch"/>
<label for="preventBrightnessChangeOnTouch">Prevent brightness change on touch</label></br>
<input type="checkbox" id="boostBacklight" name="boostBacklight"/> <input type="checkbox" id="boostBacklight" name="boostBacklight"/>
<label for="boostBacklight">Set backlight to max. while QR is shown</label></br> <label for="boostBacklight">Set initial backlight to max. while QR is shown</label></br>
<input type="checkbox" id="stayOn" name="stayOn"/> <input type="checkbox" id="stayOn" name="stayOn"/>
<label for="stayOn">Do not lock or dim while showing QR</label></br> <label for="stayOn">Do not lock or dim while showing QR</label></br>
<input type="checkbox" id="hideDescription" name="hideDescription"/> <input type="checkbox" id="hideDescription" name="hideDescription"/>
@ -287,6 +289,14 @@
} }
var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true}); var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true});
var app = `var img = ${img}; var app = `var img = ${img};
${ document.getElementById("preventBrightnessChangeOnTouch").checked ? '' : `var backlight = 0;
Bangle.on('touch', function(button, xy) {
backlight += 0.3;
if (backlight > 1) backlight = 0;
Bangle.setLCDBrightness(backlight);
});
`}
${document.getElementById("boostBacklight").checked ? 'Bangle.setLCDBrightness(1);' : ''} ${document.getElementById("boostBacklight").checked ? 'Bangle.setLCDBrightness(1);' : ''}
${document.getElementById("stayOn").checked ? 'Bangle.setLCDTimeout(0);' : ''} ${document.getElementById("stayOn").checked ? 'Bangle.setLCDTimeout(0);' : ''}
${document.getElementById("hideDescription").checked ? '' : `var content = ${JSON.stringify(content)};`} ${document.getElementById("hideDescription").checked ? '' : `var content = ${JSON.stringify(content)};`}
@ -294,7 +304,7 @@ g.clear(1).setColor(1,1,1).setBgColor(0,0,0);
g.fillRect(0,0,g.getWidth()-1,g.getHeight()-1); g.fillRect(0,0,g.getWidth()-1,g.getHeight()-1);
g.drawImage(img,(g.getWidth()-img[0])/2,(g.getHeight()-img[1])/2); g.drawImage(img,(g.getWidth()-img[0])/2,(g.getHeight()-img[1])/2);
${ document.getElementById("hideDescription").checked ? '' : `g.setFontAlign(0,0).setFont("6x8").setColor(0,0,0); ${ document.getElementById("hideDescription").checked ? '' : `g.setFontAlign(0,0).setFont("6x8").setColor(0,0,0);
g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4)); g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4);
`} `}
g.setColor(1,1,1); g.setColor(1,1,1);
`; `;

View File

@ -3,3 +3,4 @@
Fix interface.html Fix interface.html
0.03: Fix theme and maps/graphing if no GPS 0.03: Fix theme and maps/graphing if no GPS
0.04: Multiple bugfixes 0.04: Multiple bugfixes
0.05: Add recording for coresensor

View File

@ -17,6 +17,7 @@ You can record
* **GPS** GPS Latitude, Longitude and Altitude * **GPS** GPS Latitude, Longitude and Altitude
* **Steps** Steps counted by the step counter * **Steps** Steps counted by the step counter
* **HR** Heart rate * **HR** Heart rate
* **Core** CoreTemp body temperature
**Note:** It is possible for other apps to record information using this app **Note:** It is possible for other apps to record information using this app
as well. They need to define a `foobar.recorder.js` file - see the `getRecorders` as well. They need to define a `foobar.recorder.js` file - see the `getRecorders`

View File

@ -18,8 +18,15 @@ ${track[0].Heartrate!==undefined ? `<gx:SimpleArrayField name="heartrate" type="
<displayName>Heart Rate</displayName> <displayName>Heart Rate</displayName>
</gx:SimpleArrayField>`:``} </gx:SimpleArrayField>`:``}
${track[0].Steps!==undefined ? `<gx:SimpleArrayField name="steps" type="int"> ${track[0].Steps!==undefined ? `<gx:SimpleArrayField name="steps" type="int">
<displayName>Step Count</displayName>`:``} <displayName>Step Count</displayName>
</gx:SimpleArrayField> </gx:SimpleArrayField>`:``}
${track[0].Core!==undefined ? `<gx:SimpleArrayField name="core" type="int">
<displayName>Core Temp</displayName>
</gx:SimpleArrayField>`:``}
${track[0].Skin!==undefined ? `<gx:SimpleArrayField name="skin" type="int">
<displayName>Skin Temp</displayName>
</gx:SimpleArrayField>`:``}
</Schema> </Schema>
<Folder> <Folder>
<name>Tracks</name> <name>Tracks</name>
@ -37,6 +44,12 @@ ${track.map(pt=>` <gx:value>${0|pt.Heartrate}</gx:value>\n`).join("")
${track[0].Steps!==undefined ? `<gx:SimpleArrayData name="steps"> ${track[0].Steps!==undefined ? `<gx:SimpleArrayData name="steps">
${track.map(pt=>` <gx:value>${0|pt.Steps}</gx:value>\n`).join("")} ${track.map(pt=>` <gx:value>${0|pt.Steps}</gx:value>\n`).join("")}
</gx:SimpleArrayData>`:``} </gx:SimpleArrayData>`:``}
${track[0].Core!==undefined ? `<gx:SimpleArrayData name="core">
${track.map(pt=>` <gx:value>${0|pt.Core}</gx:value>\n`).join("")}
</gx:SimpleArrayData>`:``}
${track[0].Skin!==undefined ? `<gx:SimpleArrayData name="skin">
${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
</gx:SimpleArrayData>`:``}
</SchemaData> </SchemaData>
</ExtendedData> </ExtendedData>
</gx:Track> </gx:Track>
@ -59,8 +72,7 @@ ${track.map(pt=>` <gx:value>${0|pt.Steps}</gx:value>\n`).join("")}
function saveGPX(track, title) { function saveGPX(track, title) {
var gpx = `<?xml version="1.0" encoding="UTF-8"?> var gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"> <gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<gpx creator="Bangle.js" version="1.1">
<metadata> <metadata>
<time>${track[0].Time.toISOString()}</time> <time>${track[0].Time.toISOString()}</time>
</metadata> </metadata>

View File

@ -48,7 +48,7 @@
Bangle.removeListener('GPS', onGPS); Bangle.removeListener('GPS', onGPS);
Bangle.setGPSPower(0,"recorder"); Bangle.setGPSPower(0,"recorder");
}, },
draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) draw : (x,y) => g.setColor(hasFix?"#0ff":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y)
}; };
}, },
hrm:function() { hrm:function() {
@ -82,6 +82,33 @@
draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y)
}; };
}, },
temp:function() {
var core = 0, skin = 0;
var hasCore = false;
function onCore(c) {
core=c.core;
skin=c.skin;
hasCore = true;
}
return {
name : "Core",
fields : ["Core","Skin"],
getValues : () => {
var r = [core,skin];
return r;
},
start : () => {
hasCore = false;
Bangle.on('CoreTemp', onCore);
},
stop : () => {
hasCore = false;
Bangle.removeListener('CoreTemp', onCore);
},
draw : (x,y) => g.setColor(hasCore?"#0f0":"#888").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
};
},
steps:function() { steps:function() {
var lastSteps = 0; var lastSteps = 0;
return { return {

View File

@ -1,14 +0,0 @@
{ "id": "scribble",
"name": "Scribble",
"shortName":"Scribble",
"version":"0.01",
"description": "A keyboard on your wrist!",
"icon": "app.png",
"tags": "keyboard, text, scribble",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"scribble.app.js","url":"app.js"},
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
]
}

Some files were not shown because too many files have changed in this diff Show More