mirror of https://github.com/espruino/BangleApps
Merge branch 'espruino:master' into master
commit
c892da146d
30
apps.json
30
apps.json
|
@ -282,7 +282,7 @@
|
|||
{
|
||||
"id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"version": "0.24",
|
||||
"version": "0.25",
|
||||
"description": "(NOT RECOMMENDED) Handles Gadgetbridge notifications from Android. This is now replaced by the 'Android' app.",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
|
@ -538,7 +538,7 @@
|
|||
"icon": "clock-impword.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -4829,9 +4829,10 @@
|
|||
"id": "ptlaunch",
|
||||
"name": "Pattern Launcher",
|
||||
"shortName": "Pattern Launcher",
|
||||
"version": "0.02",
|
||||
"version": "0.10",
|
||||
"description": "Directly launch apps from the clock screen with custom patterns.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.png"}],
|
||||
"tags": "tools",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
@ -4845,7 +4846,7 @@
|
|||
{ "id": "clicompleteclk",
|
||||
"name": "CLI complete clock",
|
||||
"shortName":"CLI cmplt clock",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Command line styled clock with lots of information",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
|
@ -4854,8 +4855,25 @@
|
|||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"clicompleteclk.app.js","url":"app.js"},
|
||||
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"clicompleteclk.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"clicompleteclk.json"}]
|
||||
},
|
||||
{
|
||||
"id":"awairmonitor",
|
||||
"name":"Awair Monitor",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"version":"0.01",
|
||||
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
|
||||
"tags": "tool,health",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"awairmonitor.app.js","url":"app.js"},
|
||||
{"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)
|
|
@ -0,0 +1,20 @@
|
|||
# Awair Monitor
|
||||
|
||||
Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.
|
||||
|
||||
* What you need:
|
||||
* A BangleJS 2
|
||||
* An Awair device [with local API enabled](https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature)
|
||||
* The web app [awair_to_bangle.html](awair_to_bangle.html) that will retrive the data from your Awair device and sent it to your BangleJS 2 through Chrome's Bluetooth LE connection
|
||||
* How to get started
|
||||
* Open awair_to_bangle.html with a text/code editor and input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
* Launch the Awair Monitor app on your BangleJS
|
||||
* Open awair_to_bangle.html on Chrome and click "Connect BangleJS" - it connects to your watch the same way as the Bangle app store
|
||||
* Once connected to the watch with the app running, the watch app is updated once per second
|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
||||
|
||||
Contributions are welcome, send me your Pull Requests!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP/AD38g4FD8EAAoeAgE/AoUD/EfAgP+AYMPDgQPBw4FB/F///DAoPwAQPjAQPBAQPxDgJVCAoP4gYaCCwIcBAoM/8P8h0HjEP8f4h0Gp0H4/44lj5+H4/54lzj/jx/5/lyDgIFDh/xAoQRBAoXsuY8Bx4jCAoeEkYFB447CAoRxBOAPxM4RmC8IFD4ZZD/8H/DHDh/+AoaSBUAIABCoYATVwS2Ct4FE84REXQQLCk4RJAo0XGxY="))
|
|
@ -0,0 +1,98 @@
|
|||
Graphics.prototype.setFontMichroma36 = function() {
|
||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
|
||||
};
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
function queueNextDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 1000 - (Date.now() % 1000));
|
||||
}
|
||||
|
||||
var locale = require("locale");
|
||||
|
||||
var bt_current_co2 = 0;
|
||||
var bt_current_voc = 0;
|
||||
var bt_current_pm25 = 0;
|
||||
var bt_current_humi = 0;
|
||||
var bt_current_temp = 0;
|
||||
var bt_last_update = 0;
|
||||
|
||||
var last_update = 0;
|
||||
var bt_co2_history = new Array(10).fill(0);
|
||||
var bt_voc_history = new Array(10).fill(0);
|
||||
var bt_pm25_history = new Array(10).fill(0);
|
||||
var bt_humi_history = new Array(10).fill(0);
|
||||
var bt_temp_history = new Array(10).fill(0);
|
||||
|
||||
var internal_last_update = -1;
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 56);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.drawString(locale.date(new Date(),1), g.getWidth()/2, 80);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.drawString("CO2", 20, 100);
|
||||
g.drawString("VOC", 55, 100);
|
||||
g.drawString("PM25", 90, 100);
|
||||
g.drawString("Humi", 125, 100);
|
||||
g.drawString("Temp", 160, 100);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(""+bt_current_co2, 18, 110);
|
||||
g.drawString(""+bt_current_voc, 53, 110);
|
||||
g.drawString(""+bt_current_pm25, 88, 110);
|
||||
g.drawString(""+bt_current_humi, 123, 110);
|
||||
g.drawString(""+bt_current_temp, 158, 110);
|
||||
|
||||
if (last_update != bt_last_update) {
|
||||
last_update = bt_last_update;
|
||||
internal_last_update = last_update;
|
||||
if (last_update % 10 == 0) {
|
||||
bt_co2_history.shift(); bt_co2_history.push(bt_current_co2);
|
||||
bt_voc_history.shift(); bt_voc_history.push(bt_current_voc);
|
||||
bt_pm25_history.shift(); bt_pm25_history.push(bt_current_pm25);
|
||||
bt_humi_history.shift(); bt_humi_history.push(bt_current_humi);
|
||||
bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (internal_last_update == -1) {
|
||||
g.drawString("Waiting for connection", 88, 164);
|
||||
} else if (internal_last_update > last_update + 5) {
|
||||
g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
// max height = 32
|
||||
g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
|
||||
g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
|
||||
g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+i*2, 150);
|
||||
g.drawLine(115+i*2, 150-(Math.min(Math.max(bt_humi_history[i],20), 60)-20)/1.25, 115+i*2, 150);
|
||||
g.drawLine(150+i*2, 150-(Math.min(Math.max(bt_temp_history[i],19), 27)-19)*4, 150+i*2, 150);
|
||||
|
||||
// target humidity level
|
||||
g.setColor("#00F").drawLine(115, 150-(40-20)/1.25, 115+18, 150-(40-20)/1.25);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
if (internal_last_update != -1) { internal_last_update++; }
|
||||
queueNextDraw();
|
||||
}
|
||||
|
||||
// init
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,195 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://puck-js.com/puck.js"></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_ip_1 = "192.168.2.2"; // <- INPUT YOUR AWAIR IP ADDRESS HERE
|
||||
const awair_name_1 = "Awair";
|
||||
|
||||
var bt_connection;
|
||||
var is_connected = false;
|
||||
var reconnect_counter = 5;
|
||||
var reconnect_attempt_counter = 1;
|
||||
|
||||
window.onload = function() {
|
||||
var chart_co2;
|
||||
var chart_voc;
|
||||
var chart_pm;
|
||||
var chart_temperature;
|
||||
var chart_humidity;
|
||||
var dataPoints_1 = [];
|
||||
var posx = 0;
|
||||
|
||||
$.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.isOpen) {
|
||||
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
||||
|
||||
console.log("Sent data through Bluetooth");
|
||||
} else if (is_connected && !bt_connection.isOpen) {
|
||||
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
||||
reconnect_counter--;
|
||||
|
||||
if (reconnect_counter <= 0) {
|
||||
reconnect_counter = 10 * reconnect_attempt_counter;
|
||||
reconnect_attempt_counter++;
|
||||
|
||||
console.log("Trying to reconnect");
|
||||
bt_connection.reconnect(function(c) {
|
||||
console.log("Reconnect callback");
|
||||
if (!c) {
|
||||
console.log("Couldn't reconnect");
|
||||
return;
|
||||
}
|
||||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setTimeout(function(){updateChart()}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function connectBT() {
|
||||
console.log("Connect BT");
|
||||
Puck.connect(function(c) {
|
||||
console.log("Connect callback");
|
||||
if (!c) {
|
||||
console.log("Couldn't connect");
|
||||
return;
|
||||
}
|
||||
bt_connection = c;
|
||||
is_connected = true;
|
||||
reconnect_attempt_counter = 1;
|
||||
});
|
||||
}
|
||||
|
||||
function disconnectBT() {
|
||||
if (is_connected && bt_connection) {
|
||||
bt_connection.close();
|
||||
is_connected = false;
|
||||
console.log("Closed Bluetooth connection");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="https://canvasjs.com/assets/script/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
||||
</head>
|
||||
<body style="background-color:#1A202C;">
|
||||
|
||||
<p style="color: #F7FAFC">
|
||||
<b>How to use</b>
|
||||
<br/><br/>
|
||||
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||
<br/><br/>
|
||||
Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||
<br/><br/>
|
||||
Step 3: Launch the Awair Monitor app on your BangleJS
|
||||
<br/><br/>
|
||||
Step 4: Click "Connect BangleJS"
|
||||
<br/><br/>
|
||||
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<button onclick="connectBT();">Connect BangleJS</button>
|
||||
<button onclick="disconnectBT();">Disconnect BangleJS</button>
|
||||
</center>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_voc" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||
<div id="chartContainer_pm" 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>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -3,9 +3,11 @@
|
|||
Command line styled clock with lots of information:
|
||||
|
||||
It can show the following (depending on availability) information:
|
||||
* Time data:
|
||||
* Time
|
||||
* Day of week
|
||||
* Date
|
||||
* Additional information (can be toggled via settings):
|
||||
* Weather conditions and temperature (requires app [Weather](https://banglejs.com/apps/#weather))
|
||||
* Steps (requires app [Health Tracking](https://banglejs.com/apps/#health%20tracking) or a step widget)
|
||||
* Heart rate (when screen is on and unlocked)
|
||||
|
|
|
@ -1,31 +1,60 @@
|
|||
const storage = require('Storage');
|
||||
const locale = require("locale");
|
||||
|
||||
const font = "12x20";
|
||||
const fontsize = 1;
|
||||
const font12 = g.getFonts().includes("12x20");
|
||||
const font = font12 ? "12x20" : "6x8";
|
||||
const fontsize = font12 ? 1: 2;
|
||||
const fontheight = 19;
|
||||
|
||||
const marginTop = 10;
|
||||
const marginTop = 5;
|
||||
const marginLeftTopic = 3; // margin of topics
|
||||
const marginLeftData = 68; // margin of data values
|
||||
const marginLeftData = font12 ? 64 : 75; // margin of data values
|
||||
|
||||
const topicColor = g.theme.dark ? "#fff" : "#000";
|
||||
const textColor = g.theme.dark ? "#0f0" : "#080";
|
||||
const textColorRed = g.theme.dark ? "#FF0000" : "#FF0000";
|
||||
|
||||
let hrtValue;
|
||||
let hrtValueIsOld = false;
|
||||
|
||||
let localTempValue;
|
||||
let weatherTempString;
|
||||
let lastHeartRateRowIndex;
|
||||
let lastStepsRowIndex;
|
||||
let i = 2;
|
||||
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = storage.readJSON('clicompleteclk.json', 1) || {};
|
||||
}
|
||||
|
||||
function setting(key) {
|
||||
if (!settings) { loadSettings(); }
|
||||
const DEFAULTS = {
|
||||
'battery': true,
|
||||
'batteryLvl': 30,
|
||||
'weather': true,
|
||||
'steps': true,
|
||||
'heartrate': true
|
||||
};
|
||||
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||
}
|
||||
|
||||
|
||||
let showBattery = setting('battery');
|
||||
let batteryWarnLevel = setting('batteryLvl');
|
||||
let showWeather = setting('weather');
|
||||
let showSteps = setting('steps');
|
||||
let showHeartRate = setting('heartrate');
|
||||
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
drawAll(false);
|
||||
drawAll(true);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
@ -42,15 +71,13 @@ function updateTime(now){
|
|||
if (!Bangle.isLCDOn()) return;
|
||||
writeLineTopic("TIME", 1);
|
||||
writeLine(locale.time(now,1),1);
|
||||
if(now.getMinutes() == 0)
|
||||
drawInfo(now);
|
||||
}
|
||||
|
||||
function drawInfo(now) {
|
||||
if (now == undefined)
|
||||
now = new Date();
|
||||
|
||||
let i = 2;
|
||||
i = 2;
|
||||
|
||||
writeLineTopic("DOWK", i);
|
||||
writeLine(locale.dow(now),i);
|
||||
|
@ -60,14 +87,28 @@ function drawInfo(now) {
|
|||
writeLine(locale.date(now,1),i);
|
||||
i++;
|
||||
|
||||
/*
|
||||
writeLineTopic("BAT", i);
|
||||
if (showBattery) {
|
||||
writeLineTopic("BATT", i);
|
||||
const b = E.getBattery();
|
||||
writeLine(b + "%", i); // TODO make bars
|
||||
writeLine(b + "%", i, b < batteryWarnLevel ? textColorRed : textColor);
|
||||
i++;
|
||||
*/
|
||||
}
|
||||
|
||||
// weather
|
||||
if (showWeather) {
|
||||
drawWeather();
|
||||
}
|
||||
|
||||
if (showSteps) {
|
||||
drawSteps(i);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (showHeartRate) {
|
||||
drawHeartRate(i);
|
||||
}
|
||||
}
|
||||
|
||||
function drawWeather() {
|
||||
const weatherJson = getWeather();
|
||||
if(weatherJson && weatherJson.weather){
|
||||
const currentWeather = weatherJson.weather;
|
||||
|
@ -82,19 +123,22 @@ function drawInfo(now) {
|
|||
writeLine(weatherTempValue,i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// steps
|
||||
function drawSteps(i) {
|
||||
if (!showSteps) return;
|
||||
if (i == undefined)
|
||||
i = lastStepsRowIndex;
|
||||
const steps = getSteps();
|
||||
if (steps != undefined) {
|
||||
writeLineTopic("STEP", i);
|
||||
writeLine(steps, i);
|
||||
i++;
|
||||
}
|
||||
|
||||
drawHeartRate(i);
|
||||
lastStepsRowIndex = i;
|
||||
}
|
||||
|
||||
function drawHeartRate(i) {
|
||||
if (!showHeartRate) return;
|
||||
if (i == undefined)
|
||||
i = lastHeartRateRowIndex;
|
||||
writeLineTopic("HRTM", i);
|
||||
|
@ -155,15 +199,21 @@ function getWeather() {
|
|||
// turn on HRM when the LCD is unlocked
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
if (!isLocked) {
|
||||
if (showHeartRate) {
|
||||
Bangle.setHRMPower(1,"clicompleteclk");
|
||||
if (hrtValue == undefined)
|
||||
hrtValue = "...";
|
||||
else
|
||||
hrtValueIsOld = true;
|
||||
}
|
||||
} else {
|
||||
if (showHeartRate) {
|
||||
hrtValueIsOld = true;
|
||||
Bangle.setHRMPower(0,"clicompleteclk");
|
||||
}
|
||||
}
|
||||
// Update steps and heart rate
|
||||
drawSteps();
|
||||
drawHeartRate();
|
||||
});
|
||||
|
||||
|
@ -171,12 +221,15 @@ Bangle.on('lcdPower',function(on) {
|
|||
if (on) {
|
||||
drawAll(true);
|
||||
} else {
|
||||
if (showHeartRate) {
|
||||
hrtValueIsOld = true;
|
||||
}
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
if (showHeartRate) {
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
//if(hrm.confidence > 90){
|
||||
hrtValueIsOld = false;
|
||||
|
@ -187,9 +240,11 @@ Bangle.on('HRM', function(hrm) {
|
|||
// hrtValue = undefined;
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
g.clear();
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
loadSettings();
|
||||
drawAll(true);
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
(function(back) {
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON('clicompleteclk.json', 1) || {};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write('clicompleteclk.json', settings);
|
||||
}
|
||||
E.showMenu({
|
||||
'': { 'title': 'CLI complete clk' },
|
||||
'Show battery': {
|
||||
value: "battery" in settings ? settings.battery : false,
|
||||
format: () => (settings.battery ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.battery = !settings.battery;
|
||||
save('battery', settings.battery);
|
||||
},
|
||||
},
|
||||
'Battery warn': {
|
||||
value: "batteryLvl" in settings ? settings.batteryLvl : 30,
|
||||
min: 0,
|
||||
max : 100,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x + "%";
|
||||
},
|
||||
onchange: x => save('batteryLvl', x),
|
||||
},
|
||||
'Show weather': {
|
||||
value: "weather" in settings ? settings.weather : false,
|
||||
format: () => (settings.weather ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.weather = !settings.weather;
|
||||
save('weather', settings.weather);
|
||||
},
|
||||
},
|
||||
'Show steps': {
|
||||
value: "steps" in settings ? settings.steps : false,
|
||||
format: () => (settings.steps ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.steps = !settings.steps;
|
||||
save('steps', settings.steps);
|
||||
},
|
||||
},
|
||||
'Show heartrate': {
|
||||
value: "heartrate" in settings ? settings.heartrate : false,
|
||||
format: () => (settings.heartrate ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.heartrate = !settings.heartrate;
|
||||
save('heartrate', settings.heartrate);
|
||||
},
|
||||
},
|
||||
'< Back': back,
|
||||
});
|
||||
});
|
|
@ -24,3 +24,5 @@
|
|||
0.22: Respect Quiet Mode
|
||||
0.23: Allow notification dismiss to remove from phone too
|
||||
0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799)
|
||||
0.25: workaround call notification
|
||||
Fix inflated step number
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
case "call":
|
||||
var note = { size: 55, title: event.name, id: "call",
|
||||
body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}
|
||||
if (event.cmd === "incoming") {
|
||||
if (event.cmd === "incoming" || event.cmd === "") {
|
||||
require("notify").show(note);
|
||||
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
|
||||
Bangle.buzz();
|
||||
|
@ -262,7 +262,7 @@
|
|||
// Send a summary of activity to Gadgetbridge
|
||||
function sendActivity(hrm) {
|
||||
var steps = currentSteps - lastSentSteps;
|
||||
lastSentSteps = 0;
|
||||
lastSentSteps = currentSteps;
|
||||
gbSend({ t: "act", stp: steps, hrm:hrm });
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Stopped watchface from flashing every interval
|
||||
0.03: Move to Bangle.setUI to launcher support
|
||||
0.04: Tweaks for compatibility with BangleJS2
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Imprecise Word Clock
|
||||
|
||||
This clock tells time in very rough approximation, as in "Late morning" or "Early afternoon." Good for vacations and weekends. Press button 1 to see the time in accurate, digital form. But do you really need to know the exact time?
|
||||
This clock tells time in very rough approximation, as in "Late morning" or "Early afternoon." Good for vacations and weekends. Touch the screen to see the time in accurate, digital form. But do you really need to know the exact time?
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
A remix of word clock
|
||||
by Gordon Williams https://github.com/gfwilliams
|
||||
- Changes the representation of time to be more general
|
||||
- Shows accurate digital time when button 1 is pressed
|
||||
- Toggles showing of accurate digital time when screen touched.
|
||||
*/
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
|
@ -34,14 +34,16 @@ const timeOfDay = {
|
|||
};
|
||||
|
||||
|
||||
var big = g.getWidth()>200;
|
||||
// offsets and increments
|
||||
const xs = 35;
|
||||
const ys = 31;
|
||||
const dy = 22;
|
||||
const dx = 25;
|
||||
const xs = big ? 35 : 20;
|
||||
const ys = big ? 31 : 28;
|
||||
const dx = big ? 25 : 20;
|
||||
const dy = big ? 22 : 16;
|
||||
|
||||
|
||||
// font size and color
|
||||
const fontSize = 3; // "6x8"
|
||||
const fontSize = big ? 3 : 2; // "6x8"
|
||||
const passivColor = 0x3186 /*grey*/ ;
|
||||
const activeColorNight = 0xF800 /*red*/ ;
|
||||
const activeColorDay = 0xFFFF /* white */;
|
||||
|
@ -115,6 +117,8 @@ function drawWordClock() {
|
|||
|
||||
// check whether we need to redraw the watchface
|
||||
if (hidx !== hidxPrev) {
|
||||
// Turn off showDigitalTime
|
||||
showDigitalTime = false;
|
||||
// draw allWords
|
||||
var c;
|
||||
var y = ys;
|
||||
|
@ -138,15 +142,14 @@ function drawWordClock() {
|
|||
hidxPrev = hidx;
|
||||
}
|
||||
|
||||
// Display digital time while button 1 is pressed
|
||||
g.clearRect(0, 215, 240, 240);
|
||||
// Display digital time when button is pressed or screen touched
|
||||
g.clearRect(0, big ? 215 : 160, big ? 240 : 176, big ? 240 : 176);
|
||||
if (showDigitalTime){
|
||||
g.setColor(activeColor);
|
||||
g.drawString(time, 120, 215);
|
||||
g.drawString(time, big ? 120 : 90, big ? 215 : 160);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawWordClock();
|
||||
});
|
||||
|
@ -157,17 +160,14 @@ Bangle.drawWidgets();
|
|||
setInterval(drawWordClock, 1E4);
|
||||
drawWordClock();
|
||||
|
||||
// Show digital time while top button is pressed (if we have physical buttons)
|
||||
if (global.BTN3) setWatch(function() {
|
||||
showDigitalTime = BTN1.read();
|
||||
drawWordClock();
|
||||
}, BTN1, {repeat:true,edge:"both"});
|
||||
|
||||
// If LCD pressed (on Bangle.js 2) draw digital time
|
||||
Bangle.on('drag',e=>{
|
||||
var pressed = e.b!=0;
|
||||
if (pressed!=showDigitalTime) {
|
||||
showDigitalTime = pressed;
|
||||
// If LCD pressed, toggle drawing digital time
|
||||
Bangle.on('touch',e=>{
|
||||
if (showDigitalTime){
|
||||
showDigitalTime = false;
|
||||
drawWordClock();
|
||||
} else {
|
||||
showDigitalTime = true;
|
||||
drawWordClock();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: Initial creation of the pattern launch app
|
||||
0.02: Turn on lcd when launching an app if the lock screen was disabled in the settings
|
||||
0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns.
|
||||
0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.
|
|
@ -8,25 +8,32 @@ Create patterns and link them to apps in the Pattern Launcher app.
|
|||
|
||||
Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
|
||||
|
||||
## Screenshots and detailed steps
|
||||
## Add Pattern Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Manage Pattern Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
## Detailed Steps
|
||||
|
||||
From the main menu you can:
|
||||
- Add a new pattern and link it to an app (first entry)
|
||||
- To create a new pattern first select "Add Pattern"
|
||||
- Now draw any pattern you like, this will later launch the linked app from the clock screen
|
||||
- You can also draw a single-circle pattern (meaning a single tap on one circle) instead of drawing a 'complex' pattern
|
||||
- If you don't like the pattern, simply re-draw it. The previous pattern will be discarded.
|
||||
- If you are happy with the pattern tap on screen or press the button to continue
|
||||
- Now select the app you want to launch with the pattern.
|
||||
- Note, you can bind multiple patterns to the same app.
|
||||
- Remove linked patterns (second entry)
|
||||
- To remove a pattern first select "Remove Pattern"
|
||||
- You will now see a list of apps that have patterns linked to them
|
||||
- Simply select the app that you want to unlink. This will remove the saved pattern, but not the app itself!
|
||||
- Note, that you can not actually preview the patterns. This makes removing patterns that are linked to the same app annoying. sorry!
|
||||
- Manage created patterns (second entry)
|
||||
- To manage your patterns first select "Manage Patterns"
|
||||
- You will now see a scrollabe list of patterns + linked apps
|
||||
- If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
|
||||
- Disable the lock screen on the clock screen from the settings (third entry)
|
||||
- To launch the app from the pattern on the clock screen the watch must be unlocked.
|
||||
- If this annoys you, you can disable the lock on the clock screen from the setting here
|
||||
|
|
|
@ -1,26 +1,6 @@
|
|||
var storage = require("Storage");
|
||||
|
||||
var DEBUG = false;
|
||||
var log = (message) => {
|
||||
if (DEBUG) {
|
||||
console.log(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
var CIRCLE_RADIUS = 25;
|
||||
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
|
||||
|
||||
var CIRCLES = [
|
||||
{ x: 25, y: 25, i: 0 },
|
||||
{ x: 87, y: 25, i: 1 },
|
||||
{ x: 150, y: 25, i: 2 },
|
||||
{ x: 25, y: 87, i: 3 },
|
||||
{ x: 87, y: 87, i: 4 },
|
||||
{ x: 150, y: 87, i: 5 },
|
||||
{ x: 25, y: 150, i: 6 },
|
||||
{ x: 87, y: 150, i: 7 },
|
||||
{ x: 150, y: 150, i: 8 },
|
||||
];
|
||||
var storage = require("Storage");
|
||||
|
||||
var showMainMenu = () => {
|
||||
log("loading patterns");
|
||||
|
@ -36,7 +16,7 @@ var showMainMenu = () => {
|
|||
},
|
||||
"Add Pattern": () => {
|
||||
log("creating pattern");
|
||||
createPattern().then((pattern) => {
|
||||
recognizeAndDrawPattern().then((pattern) => {
|
||||
log("got pattern");
|
||||
log(pattern);
|
||||
log(pattern.length);
|
||||
|
@ -73,17 +53,32 @@ var showMainMenu = () => {
|
|||
});
|
||||
});
|
||||
},
|
||||
"Remove Pattern": () => {
|
||||
"Manage Patterns": () => {
|
||||
log("selecting pattern through app");
|
||||
getStoredPatternViaApp(storedPatterns).then((pattern) => {
|
||||
showScrollerContainingAppsWithPatterns().then((selected) => {
|
||||
var pattern = selected.pattern;
|
||||
var appName = selected.appName;
|
||||
if (pattern === "back") {
|
||||
showMainMenu();
|
||||
} else {
|
||||
E.showPrompt(appName + "\n\npattern:\n" + pattern, {
|
||||
title: "Delete?",
|
||||
buttons: { Yes: true, No: false },
|
||||
}).then((confirm) => {
|
||||
if (confirm) {
|
||||
E.showMessage("Deleting...");
|
||||
delete storedPatterns[pattern];
|
||||
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
|
||||
showMainMenu();
|
||||
} else {
|
||||
showMainMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
Settings: () => {
|
||||
var settings = storedPatterns["settings"] || {};
|
||||
var settings = storedPatterns.settings || {};
|
||||
|
||||
var settingsmenu = {
|
||||
"": {
|
||||
|
@ -98,7 +93,7 @@ var showMainMenu = () => {
|
|||
if (settings.lockDisabled) {
|
||||
settingsmenu["Enable lock"] = () => {
|
||||
settings.lockDisabled = false;
|
||||
storedPatterns["settings"] = settings;
|
||||
storedPatterns.settings = settings;
|
||||
Bangle.setOptions({ lockTimeout: 1000 * 30 });
|
||||
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
|
||||
showMainMenu();
|
||||
|
@ -106,7 +101,7 @@ var showMainMenu = () => {
|
|||
} else {
|
||||
settingsmenu["Disable lock"] = () => {
|
||||
settings.lockDisabled = true;
|
||||
storedPatterns["settings"] = settings;
|
||||
storedPatterns.settings = settings;
|
||||
storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
|
||||
Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
|
||||
showMainMenu();
|
||||
|
@ -119,12 +114,8 @@ var showMainMenu = () => {
|
|||
E.showMenu(mainmenu);
|
||||
};
|
||||
|
||||
var drawCircle = (circle) => {
|
||||
g.fillCircle(circle.x, circle.y, CIRCLE_RADIUS);
|
||||
};
|
||||
|
||||
var positions = [];
|
||||
var createPattern = () => {
|
||||
var recognizeAndDrawPattern = () => {
|
||||
return new Promise((resolve) => {
|
||||
E.showMenu();
|
||||
g.clear();
|
||||
|
@ -147,13 +138,29 @@ var createPattern = () => {
|
|||
setWatch(() => finishHandler(), BTN);
|
||||
setTimeout(() => Bangle.on("tap", finishHandler), 250);
|
||||
|
||||
positions = [];
|
||||
var dragHandler = (position) => {
|
||||
log(position);
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
if (isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This might actually be a 'tap' event.
|
||||
// Use this check in addition to the actual tap handler to make it more reliable
|
||||
if (pattern.length > 0 && positions.length === 2) {
|
||||
if (
|
||||
positions[0].x === positions[1].x &&
|
||||
positions[0].y === positions[1].y
|
||||
) {
|
||||
finishHandler();
|
||||
positions = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
E.showMessage("Calculating...");
|
||||
var t0 = Date.now();
|
||||
|
||||
|
@ -269,18 +276,7 @@ var createPattern = () => {
|
|||
|
||||
log("redrawing");
|
||||
g.clear();
|
||||
g.setColor(0, 0, 0);
|
||||
CIRCLES.forEach((circle) => drawCircle(circle));
|
||||
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("6x8", 4);
|
||||
pattern.forEach((circleIndex, patternIndex) => {
|
||||
var circle = CIRCLES[circleIndex];
|
||||
g.drawString(patternIndex + 1, circle.x, circle.y);
|
||||
});
|
||||
var t2 = Date.now();
|
||||
log(t2 - t0);
|
||||
drawCirclesWithPattern(pattern);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -341,56 +337,256 @@ var getSelectedApp = () => {
|
|||
});
|
||||
};
|
||||
|
||||
var getStoredPatternViaApp = (storedPatterns) => {
|
||||
E.showMessage("Loading patterns...");
|
||||
log("getStoredPatternViaApp");
|
||||
//////
|
||||
// manage pattern related variables and functions
|
||||
// - draws all saved patterns and their linked app names
|
||||
// - uses the scroller to allow the user to browse through them
|
||||
//////
|
||||
|
||||
var scrollerFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||
|
||||
var drawBackButton = (r) => {
|
||||
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
|
||||
g.setFont(scrollerFont)
|
||||
.setFontAlign(-1, 0)
|
||||
.drawString("< Back", 64, r.y + 32);
|
||||
};
|
||||
|
||||
var drawAppWithPattern = (i, r, storedPatterns) => {
|
||||
log("draw app with pattern");
|
||||
log({ i: i, r: r, storedPatterns: storedPatterns });
|
||||
var storedPattern = storedPatterns[i];
|
||||
var pattern = storedPattern.pattern;
|
||||
var app = storedPattern.app;
|
||||
|
||||
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
|
||||
|
||||
g.drawLine(r.x, r.y, 176, r.y);
|
||||
|
||||
drawCirclesWithPattern(pattern, {
|
||||
enableCaching: true,
|
||||
scale: 0.33,
|
||||
offset: { x: 1, y: 3 + r.y },
|
||||
});
|
||||
|
||||
g.setColor(0, 0, 0);
|
||||
if (!storedPattern.wrappedAppName) {
|
||||
storedPattern.wrappedAppName = g
|
||||
.wrapString(app.name, g.getWidth() - 64)
|
||||
.join("\n");
|
||||
}
|
||||
log(g.getWidth());
|
||||
log(storedPattern.wrappedAppName);
|
||||
g.setFont(scrollerFont)
|
||||
.setFontAlign(-1, 0)
|
||||
.drawString(storedPattern.wrappedAppName, 64, r.y + 32);
|
||||
};
|
||||
|
||||
var showScrollerContainingAppsWithPatterns = () => {
|
||||
var storedPatternsArray = getStoredPatternsArray();
|
||||
log("drawing scroller for stored patterns");
|
||||
log(storedPatternsArray);
|
||||
log(storedPatternsArray.length);
|
||||
|
||||
g.clear();
|
||||
|
||||
var c = Math.max(storedPatternsArray.length + 1, 3);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
var selectPatternMenu = {
|
||||
"": {
|
||||
title: "Select App",
|
||||
E.showScroller({
|
||||
h: 64,
|
||||
c: c,
|
||||
draw: (i, r) => {
|
||||
log("draw");
|
||||
log({ i: i, r: r });
|
||||
if (i <= 0) {
|
||||
drawBackButton(r);
|
||||
} else if (i <= storedPatternsArray.length) {
|
||||
drawAppWithPattern(i - 1, r, storedPatternsArray);
|
||||
}
|
||||
},
|
||||
"< Cancel": () => {
|
||||
log("cancel");
|
||||
showMainMenu();
|
||||
select: (i) => {
|
||||
log("selected: " + i);
|
||||
var pattern = "back";
|
||||
var appName = "";
|
||||
if (i > 0) {
|
||||
var storedPattern = storedPatternsArray[i - 1];
|
||||
pattern = storedPattern.pattern.join("");
|
||||
appName = storedPattern.app.name;
|
||||
}
|
||||
clearCircleDrawingCache();
|
||||
resolve({ pattern: pattern, appName: appName });
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
log(storedPatterns);
|
||||
var patterns = Object.keys(storedPatterns);
|
||||
log(patterns);
|
||||
//////
|
||||
// storage related functions:
|
||||
// - stored patterns
|
||||
// - stored settings
|
||||
//////
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
if (pattern) {
|
||||
if (storedPatterns[pattern]) {
|
||||
var app = storedPatterns[pattern].app;
|
||||
if (!!app && !!app.name) {
|
||||
var appName = app.name;
|
||||
var i = 0;
|
||||
while (appName in selectPatternMenu[app.name]) {
|
||||
appName = app.name + i;
|
||||
i++;
|
||||
var getStoredPatternsMap = () => {
|
||||
log("loading stored patterns map");
|
||||
var storedPatternsMap = storage.readJSON("ptlaunch.patterns.json", 1) || {};
|
||||
delete storedPatternsMap.settings;
|
||||
log(storedPatternsMap);
|
||||
return storedPatternsMap;
|
||||
};
|
||||
|
||||
var getStoredPatternsArray = () => {
|
||||
var storedPatternsMap = getStoredPatternsMap();
|
||||
log("converting stored patterns map to array");
|
||||
var patterns = Object.keys(storedPatternsMap);
|
||||
var storedPatternsArray = [];
|
||||
for (var i = 0; i < patterns.length; i++) {
|
||||
var pattern = "" + patterns[i];
|
||||
storedPatternsArray.push({
|
||||
pattern: pattern
|
||||
.split("")
|
||||
.map((circleIndex) => parseInt(circleIndex, 10)),
|
||||
app: storedPatternsMap[pattern].app,
|
||||
});
|
||||
}
|
||||
selectPatternMenu[appName] = () => {
|
||||
log("pattern via app selected");
|
||||
log(storedPatternsArray);
|
||||
return storedPatternsArray;
|
||||
};
|
||||
|
||||
//////
|
||||
// circle related variables and functions:
|
||||
// - the circle array itself
|
||||
// - the radius and the squared radius of the circles
|
||||
// - circle draw function
|
||||
//////
|
||||
|
||||
var CIRCLE_RADIUS = 25;
|
||||
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
|
||||
|
||||
var CIRCLES = [
|
||||
{ x: 25, y: 25, i: 0 },
|
||||
{ x: 87, y: 25, i: 1 },
|
||||
{ x: 150, y: 25, i: 2 },
|
||||
{ x: 25, y: 87, i: 3 },
|
||||
{ x: 87, y: 87, i: 4 },
|
||||
{ x: 150, y: 87, i: 5 },
|
||||
{ x: 25, y: 150, i: 6 },
|
||||
{ x: 87, y: 150, i: 7 },
|
||||
{ x: 150, y: 150, i: 8 },
|
||||
];
|
||||
|
||||
var drawCircle = (circle, drawBuffer, scale) => {
|
||||
if (!drawBuffer) {
|
||||
drawBuffer = g;
|
||||
}
|
||||
if (!scale) {
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
var x = circle.x * scale;
|
||||
var y = circle.y * scale;
|
||||
var r = CIRCLE_RADIUS * scale;
|
||||
|
||||
log("drawing circle");
|
||||
log({ x: x, y: y, r: r });
|
||||
|
||||
drawBuffer.fillCircle(x, y, r);
|
||||
};
|
||||
|
||||
var cachedCirclesDrawings = {};
|
||||
|
||||
var clearCircleDrawingCache = () => {
|
||||
cachedCirclesDrawings = {};
|
||||
};
|
||||
|
||||
var drawCirclesWithPattern = (pattern, options) => {
|
||||
if (!pattern || pattern.length === 0) {
|
||||
pattern = [];
|
||||
}
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
var enableCaching = options.enableCaching;
|
||||
var scale = options.scale;
|
||||
var offset = options.offset;
|
||||
if (!enableCaching) {
|
||||
enableCaching = false;
|
||||
}
|
||||
if (!scale) {
|
||||
scale = 1;
|
||||
}
|
||||
if (!offset) {
|
||||
offset = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
log("drawing circles with pattern, scale and offset");
|
||||
log(pattern);
|
||||
log(app);
|
||||
resolve(pattern);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
log(scale);
|
||||
log(offset);
|
||||
|
||||
// cache drawn patterns. especially useful for the manage pattern menu
|
||||
var image = cachedCirclesDrawings[pattern.join("")];
|
||||
if (!image) {
|
||||
log("circle image not cached");
|
||||
var drawBuffer = Graphics.createArrayBuffer(
|
||||
g.getWidth() * scale,
|
||||
g.getHeight() * scale,
|
||||
1,
|
||||
{ msb: true }
|
||||
);
|
||||
|
||||
drawBuffer.setColor(1);
|
||||
CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
|
||||
|
||||
drawBuffer.setColor(0);
|
||||
drawBuffer.setFontAlign(0, 0);
|
||||
drawBuffer.setFont("6x8", 4 * scale);
|
||||
pattern.forEach((circleIndex, patternIndex) => {
|
||||
var circle = CIRCLES[circleIndex];
|
||||
drawBuffer.drawString(
|
||||
patternIndex + 1,
|
||||
circle.x * scale,
|
||||
circle.y * scale
|
||||
);
|
||||
});
|
||||
|
||||
E.showMenu(selectPatternMenu);
|
||||
});
|
||||
image = {
|
||||
width: drawBuffer.getWidth(),
|
||||
height: drawBuffer.getHeight(),
|
||||
bpp: 1,
|
||||
buffer: drawBuffer.buffer,
|
||||
};
|
||||
|
||||
showMainMenu();
|
||||
if (enableCaching) {
|
||||
cachedCirclesDrawings[pattern.join("")] = image;
|
||||
}
|
||||
} else {
|
||||
log("using cached circle image");
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
//////
|
||||
// lib functions
|
||||
// misc lib functions
|
||||
//////
|
||||
|
||||
var log = (message) => {
|
||||
if (DEBUG) {
|
||||
console.log(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
var debounceTimeoutId;
|
||||
var debounce = (delay) => {
|
||||
if (debounceTimeoutId) {
|
||||
|
@ -405,12 +601,8 @@ var debounce = (delay) => {
|
|||
});
|
||||
};
|
||||
|
||||
var cloneCirclesArray = () => {
|
||||
var circlesClone = Array(CIRCLES.length);
|
||||
//////
|
||||
// run main function
|
||||
//////
|
||||
|
||||
for (var i = 0; i < CIRCLES.length; i++) {
|
||||
circlesClone[i] = CIRCLES[i];
|
||||
}
|
||||
|
||||
return circlesClone;
|
||||
};
|
||||
showMainMenu();
|
||||
|
|
|
@ -5,10 +5,18 @@ var log = (message) => {
|
|||
}
|
||||
};
|
||||
|
||||
var storedPatterns;
|
||||
var positions = [];
|
||||
var dragHandler = (position) => {
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
log(positions.length);
|
||||
|
||||
var CIRCLE_RADIUS = 25;
|
||||
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
|
||||
|
||||
var CIRCLES = [
|
||||
var circles = [
|
||||
{ x: 25, y: 25, i: 0 },
|
||||
{ x: 87, y: 25, i: 1 },
|
||||
{ x: 150, y: 25, i: 2 },
|
||||
|
@ -19,16 +27,6 @@ var CIRCLES = [
|
|||
{ x: 87, y: 150, i: 7 },
|
||||
{ x: 150, y: 150, i: 8 },
|
||||
];
|
||||
|
||||
var storedPatterns;
|
||||
var positions = [];
|
||||
var dragHandler = (position) => {
|
||||
positions.push(position);
|
||||
|
||||
debounce().then(() => {
|
||||
log(positions.length);
|
||||
|
||||
var circlesClone = cloneCirclesArray();
|
||||
var pattern = [];
|
||||
|
||||
var step = Math.floor(positions.length / 100) + 1;
|
||||
|
@ -38,92 +36,92 @@ var dragHandler = (position) => {
|
|||
for (var i = 0; i < positions.length; i += step) {
|
||||
p = positions[i];
|
||||
|
||||
circle = circlesClone[0];
|
||||
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);
|
||||
circlesClone.splice(0, 1);
|
||||
circles.splice(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[1];
|
||||
circle = circles[1];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(1, 1);
|
||||
circles.splice(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[2];
|
||||
circle = circles[2];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(2, 1);
|
||||
circles.splice(2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[3];
|
||||
circle = circles[3];
|
||||
if (circle) {
|
||||
a = p.x - circle.x;
|
||||
b = p.y - circle.y;
|
||||
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
|
||||
pattern.push(circle.i);
|
||||
circlesClone.splice(3, 1);
|
||||
circles.splice(3, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[4];
|
||||
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);
|
||||
circlesClone.splice(4, 1);
|
||||
circles.splice(4, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[5];
|
||||
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);
|
||||
circlesClone.splice(5, 1);
|
||||
circles.splice(5, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[6];
|
||||
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);
|
||||
circlesClone.splice(6, 1);
|
||||
circles.splice(6, 1);
|
||||
}
|
||||
}
|
||||
circle = circlesClone[7];
|
||||
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);
|
||||
circlesClone.splice(7, 1);
|
||||
circles.splice(7, 1);
|
||||
}
|
||||
}
|
||||
|
||||
circle = circlesClone[8];
|
||||
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);
|
||||
circlesClone.splice(8, 1);
|
||||
circles.splice(8, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,16 +161,6 @@ var debounce = (delay) => {
|
|||
});
|
||||
};
|
||||
|
||||
var cloneCirclesArray = () => {
|
||||
var circlesClone = Array(CIRCLES.length);
|
||||
|
||||
for (var i = 0; i < CIRCLES.length; i++) {
|
||||
circlesClone[i] = CIRCLES[i];
|
||||
}
|
||||
|
||||
return circlesClone;
|
||||
};
|
||||
|
||||
(function () {
|
||||
var sui = Bangle.setUI;
|
||||
Bangle.setUI = function (mode, cb) {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Loading…
Reference in New Issue