Merge branch 'master' into master

master
Gordon Williams 2021-12-13 10:15:46 +00:00 committed by GitHub
commit 16917939c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1663 additions and 450 deletions

View File

@ -282,7 +282,7 @@
{ {
"id": "gbridge", "id": "gbridge",
"name": "Gadgetbridge", "name": "Gadgetbridge",
"version": "0.24", "version": "0.25",
"description": "(NOT RECOMMENDED) Handles Gadgetbridge notifications from Android. This is now replaced by the 'Android' app.", "description": "(NOT RECOMMENDED) Handles Gadgetbridge notifications from Android. This is now replaced by the 'Android' app.",
"icon": "app.png", "icon": "app.png",
"type": "widget", "type": "widget",
@ -538,7 +538,7 @@
"icon": "clock-impword.png", "icon": "clock-impword.png",
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}], "screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
"allow_emulator": true, "allow_emulator": true,
"storage": [ "storage": [
@ -824,7 +824,7 @@
{ {
"id": "weather", "id": "weather",
"name": "Weather", "name": "Weather",
"version": "0.12", "version": "0.13",
"description": "Show Gadgetbridge weather report", "description": "Show Gadgetbridge weather report",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],
@ -1127,7 +1127,7 @@
{ {
"id": "qrcode", "id": "qrcode",
"name": "Custom QR Code", "name": "Custom QR Code",
"version": "0.03", "version": "0.04",
"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",
@ -3870,7 +3870,7 @@
"id": "qmsched", "id": "qmsched",
"name": "Quiet Mode Schedule and Widget", "name": "Quiet Mode Schedule and Widget",
"shortName": "Quiet Mode", "shortName": "Quiet Mode",
"version": "0.04", "version": "0.05",
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.", "description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"}, "screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
@ -4117,14 +4117,17 @@
{ {
"id": "vectorclock", "id": "vectorclock",
"name": "Vector Clock", "name": "Vector Clock",
"version": "0.02", "version": "0.03",
"description": "A digital clock that uses the built-in vector font.", "description": "A digital clock that uses the built-in vector font.",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true, "allow_emulator": true,
"screenshots": [{"url":"bangle1-vector-clock-screenshot.png"}], "screenshots": [
{"url":"bangle2-vector-clock-screenshot.png"},
{"url":"bangle1-vector-clock-screenshot.png"}
],
"storage": [ "storage": [
{"name":"vectorclock.app.js","url":"app.js"}, {"name":"vectorclock.app.js","url":"app.js"},
{"name":"vectorclock.img","url":"app-icon.js","evaluate":true} {"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
@ -4442,9 +4445,9 @@
"name": "A Battery Widget (with percentage)", "name": "A Battery Widget (with percentage)",
"shortName":"A Battery Widget", "shortName":"A Battery Widget",
"icon": "widget.png", "icon": "widget.png",
"version":"1.01", "version":"1.02",
"type": "widget", "type": "widget",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"description": "Simple and slim battery widget with charge status and percentage", "description": "Simple and slim battery widget with charge status and percentage",
"tags": "widget,battery", "tags": "widget,battery",
@ -4711,7 +4714,7 @@
{ "id": "pooqroman", { "id": "pooqroman",
"name": "pooq Roman watch face", "name": "pooq Roman watch face",
"shortName":"pooq Roman", "shortName":"pooq Roman",
"version":"0.0.0", "version":"0.02",
"description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!", "description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",
@ -4746,7 +4749,7 @@
{ {
"id": "weatherClock", "id": "weatherClock",
"name": "Weather Clock", "name": "Weather Clock",
"version": "0.03", "version": "0.04",
"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"}],
@ -4829,9 +4832,10 @@
"id": "ptlaunch", "id": "ptlaunch",
"name": "Pattern Launcher", "name": "Pattern Launcher",
"shortName": "Pattern Launcher", "shortName": "Pattern Launcher",
"version": "0.02", "version": "0.10",
"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":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.png"}],
"tags": "tools", "tags": "tools",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
@ -4842,22 +4846,6 @@
], ],
"data": [{"name":"ptlaunch.patterns.json"}] "data": [{"name":"ptlaunch.patterns.json"}]
}, },
{ "id": "clicompleteclk",
"name": "CLI complete clock",
"shortName":"CLI cmplt clock",
"version":"0.02",
"description": "Command line styled clock with lots of information",
"icon": "app.png",
"allow_emulator": true,
"type": "clock",
"tags": "clock,cli,command,bash,shell,weather,hrt",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"clicompleteclk.app.js","url":"app.js"},
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true}
]
},
{ {
"id": "rebble", "id": "rebble",
"name": "Rebble Clock", "name": "Rebble Clock",
@ -4876,5 +4864,52 @@
{"name":"rebble.settings.js","url":"rebble.settings.js"}, {"name":"rebble.settings.js","url":"rebble.settings.js"},
{"name":"rebble.img","url":"rebble.icon.js","evaluate":true} {"name":"rebble.img","url":"rebble.icon.js","evaluate":true}
] ]
},
{ "id": "snaky",
"name": "Snaky",
"shortName":"Snaky",
"version":"0.01",
"description": "The classic snake game. Eat apples and don't bite your tail. Control the snake with the touch screen.",
"tags": "game,fun",
"icon": "snaky.png",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"snaky.app.js","url":"snaky.js"},
{"name":"snaky.img","url":"snaky-icon.js","evaluate":true}
]
},
{
"id": "clicompleteclk",
"name": "CLI complete clock",
"shortName":"CLI cmplt clock",
"version":"0.03",
"description": "Command line styled clock with lots of information",
"icon": "app.png",
"allow_emulator": true,
"type": "clock",
"tags": "clock,cli,command,bash,shell,weather,hrt",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"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}
]
} }
] ]

View File

@ -0,0 +1 @@
0.01: Beta version for Bangle 2 paired with Chrome (2021/12/11)

View File

@ -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
![](screenshot.png)
## Creator
[@alainsaas](https://github.com/alainsaas)
Contributions are welcome, send me your Pull Requests!

View File

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

98
apps/awairmonitor/app.js Normal file
View File

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

BIN
apps/awairmonitor/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

View File

@ -3,12 +3,14 @@
Command line styled clock with lots of information: Command line styled clock with lots of information:
It can show the following (depending on availability) information: It can show the following (depending on availability) information:
* Time * Time data:
* Day of week * Time
* Date * Day of week
* Weather conditions and temperature (requires app [Weather](https://banglejs.com/apps/#weather)) * Date
* Steps (requires app [Health Tracking](https://banglejs.com/apps/#health%20tracking) or a step widget) * Additional information (can be toggled via settings):
* Heart rate (when screen is on and unlocked) * 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)
## TODO ## TODO
* Make time font bigger * Make time font bigger

View File

@ -1,31 +1,60 @@
const storage = require('Storage'); const storage = require('Storage');
const locale = require("locale"); const locale = require("locale");
const font = "12x20"; const font12 = g.getFonts().includes("12x20");
const fontsize = 1; const font = font12 ? "12x20" : "6x8";
const fontsize = font12 ? 1: 2;
const fontheight = 19; const fontheight = 19;
const marginTop = 10; const marginTop = 5;
const marginLeftTopic = 3; // margin of topics 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 topicColor = g.theme.dark ? "#fff" : "#000";
const textColor = g.theme.dark ? "#0f0" : "#080"; const textColor = g.theme.dark ? "#0f0" : "#080";
const textColorRed = g.theme.dark ? "#FF0000" : "#FF0000";
let hrtValue; let hrtValue;
let hrtValueIsOld = false; let hrtValueIsOld = false;
let localTempValue; let localTempValue;
let weatherTempString; let weatherTempString;
let lastHeartRateRowIndex; 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; var drawTimeout;
// schedule a draw for the next minute
function queueDraw() { function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() { drawTimeout = setTimeout(function() {
drawTimeout = undefined; drawTimeout = undefined;
drawAll(false); drawAll(true);
}, 60000 - (Date.now() % 60000)); }, 60000 - (Date.now() % 60000));
} }
@ -42,15 +71,13 @@ function updateTime(now){
if (!Bangle.isLCDOn()) return; if (!Bangle.isLCDOn()) return;
writeLineTopic("TIME", 1); writeLineTopic("TIME", 1);
writeLine(locale.time(now,1),1); writeLine(locale.time(now,1),1);
if(now.getMinutes() == 0)
drawInfo(now);
} }
function drawInfo(now) { function drawInfo(now) {
if (now == undefined) if (now == undefined)
now = new Date(); now = new Date();
let i = 2; i = 2;
writeLineTopic("DOWK", i); writeLineTopic("DOWK", i);
writeLine(locale.dow(now),i); writeLine(locale.dow(now),i);
@ -60,14 +87,28 @@ function drawInfo(now) {
writeLine(locale.date(now,1),i); writeLine(locale.date(now,1),i);
i++; i++;
/* if (showBattery) {
writeLineTopic("BAT", i); writeLineTopic("BATT", i);
const b = E.getBattery(); const b = E.getBattery();
writeLine(b + "%", i); // TODO make bars writeLine(b + "%", i, b < batteryWarnLevel ? textColorRed : textColor);
i++; i++;
*/ }
// weather if (showWeather) {
drawWeather();
}
if (showSteps) {
drawSteps(i);
i++;
}
if (showHeartRate) {
drawHeartRate(i);
}
}
function drawWeather() {
const weatherJson = getWeather(); const weatherJson = getWeather();
if(weatherJson && weatherJson.weather){ if(weatherJson && weatherJson.weather){
const currentWeather = weatherJson.weather; const currentWeather = weatherJson.weather;
@ -82,19 +123,22 @@ function drawInfo(now) {
writeLine(weatherTempValue,i); writeLine(weatherTempValue,i);
i++; i++;
} }
}
// steps function drawSteps(i) {
if (!showSteps) return;
if (i == undefined)
i = lastStepsRowIndex;
const steps = getSteps(); const steps = getSteps();
if (steps != undefined) { if (steps != undefined) {
writeLineTopic("STEP", i); writeLineTopic("STEP", i);
writeLine(steps, i); writeLine(steps, i);
i++;
} }
lastStepsRowIndex = i;
drawHeartRate(i);
} }
function drawHeartRate(i) { function drawHeartRate(i) {
if (!showHeartRate) return;
if (i == undefined) if (i == undefined)
i = lastHeartRateRowIndex; i = lastHeartRateRowIndex;
writeLineTopic("HRTM", i); writeLineTopic("HRTM", i);
@ -155,15 +199,21 @@ function getWeather() {
// turn on HRM when the LCD is unlocked // turn on HRM when the LCD is unlocked
Bangle.on('lock', function(isLocked) { Bangle.on('lock', function(isLocked) {
if (!isLocked) { if (!isLocked) {
if (showHeartRate) {
Bangle.setHRMPower(1,"clicompleteclk"); Bangle.setHRMPower(1,"clicompleteclk");
if (hrtValue == undefined) if (hrtValue == undefined)
hrtValue = "..."; hrtValue = "...";
else else
hrtValueIsOld = true; hrtValueIsOld = true;
}
} else { } else {
if (showHeartRate) {
hrtValueIsOld = true; hrtValueIsOld = true;
Bangle.setHRMPower(0,"clicompleteclk"); Bangle.setHRMPower(0,"clicompleteclk");
} }
}
// Update steps and heart rate
drawSteps();
drawHeartRate(); drawHeartRate();
}); });
@ -171,13 +221,16 @@ Bangle.on('lcdPower',function(on) {
if (on) { if (on) {
drawAll(true); drawAll(true);
} else { } else {
if (showHeartRate) {
hrtValueIsOld = true; hrtValueIsOld = true;
}
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
} }
}); });
Bangle.on('HRM', function(hrm) { if (showHeartRate) {
Bangle.on('HRM', function(hrm) {
//if(hrm.confidence > 90){ //if(hrm.confidence > 90){
hrtValueIsOld = false; hrtValueIsOld = false;
hrtValue = hrm.bpm; hrtValue = hrm.bpm;
@ -186,10 +239,12 @@ Bangle.on('HRM', function(hrm) {
//} else { //} else {
// hrtValue = undefined; // hrtValue = undefined;
//} //}
}); });
}
g.clear(); g.clear();
Bangle.setUI("clock"); Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
loadSettings();
drawAll(true); drawAll(true);

View File

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

View File

@ -24,3 +24,5 @@
0.22: Respect Quiet Mode 0.22: Respect Quiet Mode
0.23: Allow notification dismiss to remove from phone too 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.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

View File

@ -184,7 +184,7 @@
case "call": case "call":
var note = { size: 55, title: event.name, id: "call", var note = { size: 55, title: event.name, id: "call",
body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))} body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}
if (event.cmd === "incoming") { if (event.cmd === "incoming" || event.cmd === "") {
require("notify").show(note); require("notify").show(note);
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz(); Bangle.buzz();
@ -262,7 +262,7 @@
// Send a summary of activity to Gadgetbridge // Send a summary of activity to Gadgetbridge
function sendActivity(hrm) { function sendActivity(hrm) {
var steps = currentSteps - lastSentSteps; var steps = currentSteps - lastSentSteps;
lastSentSteps = 0; lastSentSteps = currentSteps;
gbSend({ t: "act", stp: steps, hrm:hrm }); gbSend({ t: "act", stp: steps, hrm:hrm });
} }

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Stopped watchface from flashing every interval 0.02: Stopped watchface from flashing every interval
0.03: Move to Bangle.setUI to launcher support 0.03: Move to Bangle.setUI to launcher support
0.04: Tweaks for compatibility with BangleJS2

View File

@ -1,4 +1,4 @@
# Imprecise Word Clock # 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?

View File

@ -2,7 +2,7 @@
A remix of word clock A remix of word clock
by Gordon Williams https://github.com/gfwilliams by Gordon Williams https://github.com/gfwilliams
- Changes the representation of time to be more general - 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 */ /* jshint esversion: 6 */
@ -34,14 +34,16 @@ const timeOfDay = {
}; };
var big = g.getWidth()>200;
// offsets and increments // offsets and increments
const xs = 35; const xs = big ? 35 : 20;
const ys = 31; const ys = big ? 31 : 28;
const dy = 22; const dx = big ? 25 : 20;
const dx = 25; const dy = big ? 22 : 16;
// font size and color // font size and color
const fontSize = 3; // "6x8" const fontSize = big ? 3 : 2; // "6x8"
const passivColor = 0x3186 /*grey*/ ; const passivColor = 0x3186 /*grey*/ ;
const activeColorNight = 0xF800 /*red*/ ; const activeColorNight = 0xF800 /*red*/ ;
const activeColorDay = 0xFFFF /* white */; const activeColorDay = 0xFFFF /* white */;
@ -115,6 +117,8 @@ function drawWordClock() {
// check whether we need to redraw the watchface // check whether we need to redraw the watchface
if (hidx !== hidxPrev) { if (hidx !== hidxPrev) {
// Turn off showDigitalTime
showDigitalTime = false;
// draw allWords // draw allWords
var c; var c;
var y = ys; var y = ys;
@ -138,15 +142,14 @@ function drawWordClock() {
hidxPrev = hidx; hidxPrev = hidx;
} }
// Display digital time while button 1 is pressed // Display digital time when button is pressed or screen touched
g.clearRect(0, 215, 240, 240); g.clearRect(0, big ? 215 : 160, big ? 240 : 176, big ? 240 : 176);
if (showDigitalTime){ if (showDigitalTime){
g.setColor(activeColor); g.setColor(activeColor);
g.drawString(time, 120, 215); g.drawString(time, big ? 120 : 90, big ? 215 : 160);
} }
} }
Bangle.on('lcdPower', function(on) { Bangle.on('lcdPower', function(on) {
if (on) drawWordClock(); if (on) drawWordClock();
}); });
@ -157,17 +160,14 @@ Bangle.drawWidgets();
setInterval(drawWordClock, 1E4); setInterval(drawWordClock, 1E4);
drawWordClock(); 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 // If LCD pressed, toggle drawing digital time
Bangle.on('drag',e=>{ Bangle.on('touch',e=>{
var pressed = e.b!=0; if (showDigitalTime){
if (pressed!=showDigitalTime) { showDigitalTime = false;
showDigitalTime = pressed; drawWordClock();
} else {
showDigitalTime = true;
drawWordClock(); drawWordClock();
} }
}); });

View File

@ -40,7 +40,16 @@ const charFallbacks = {
"č":"c", "č":"c",
"ř":"r", "ř":"r",
"ő":"o", "ő":"o",
"ě":"e" "ě":"e",
"ę":"e",
"ą":"a",
"ó":"o",
"ż":"z",
"ź":"z",
"ń":"n",
"ł":"l",
"ś":"s",
"ć":"c",
}; };
/* /*
@ -603,6 +612,24 @@ var locales = {
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado", day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "ok", on: "on", off: "off" } trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "ok", on: "on", off: "off" }
}, },
"pl_PL": {
lang: "pl_PL",
decimal_point: ",",
thousands_sep: " ",
currency_symbol: "zł",
int_curr_symbol: "PLN",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2021 // 01.03.2021
abmonth: "Sty,Lut,Mar,Kwi,Maj,Cze,Lip,Sie,Wrz,Paź,Lis,Gru",
month: "Styczeń,Luty,Marzec,Kwiecień,Maj,Czerwiec,Lipiec,Sierpień,Wrzesień,Październik,Listopad,Grudzień",
abday: "Ndz,Pon,Wt,Śr,Czw,Pt,Sob",
day: "Niedziela,Poniedziałek,Wtorek,Środa,Czwartek,Piątek,Sobota",
trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "on", off: "off", "< Back": "< Wstecz" }
},
/*, /*,
"he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399 "he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399
codePage : "ISO8859-8", codePage : "ISO8859-8",

2
apps/pooqroman/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Make internal menu time out + small fixes

View File

@ -1,3 +1,4 @@
/* -*- mode: Javascript; c-basic-offset: 2; indent-tabs-mode: nil; coding: latin-1 -*- */
// pooqRoman // pooqRoman
// //
// Copyright (c) 2021 Stephen P Spackman // Copyright (c) 2021 Stephen P Spackman
@ -54,8 +55,9 @@ class Options {
this.id = this.constructor.id; this.id = this.constructor.id;
this.file = `${this.id}.json`; this.file = `${this.id}.json`;
this.backing = storage.readJSON(this.file, true) || {}; this.backing = storage.readJSON(this.file, true) || {};
this.defaults = this.constructor.defaults; Object.setPrototypeOf(this.backing, this.constructor.defaults);
Object.keys(this.defaults).forEach(k => this.bless(k)); this.reactivator = _ => this.active();
Object.keys(this.constructor.defaults).forEach(k => this.bless(k));
} }
writeBack(delay) { writeBack(delay) {
@ -71,7 +73,7 @@ class Options {
bless(k) { bless(k) {
Object.defineProperty(this, k, { Object.defineProperty(this, k, {
get: () => this.backing[k] == null ? this.defaults[k] : this.backing[k], get: () => this.backing[k],
set: v => { set: v => {
this.backing[k] = v; this.backing[k] = v;
// Ten second writeback delay, since the user will roll values up and down. // Ten second writeback delay, since the user will roll values up and down.
@ -81,19 +83,31 @@ class Options {
} }
showMenu(m) { showMenu(m) {
if (m instanceof Function) m = m();
if (m) { if (m) {
for (const k in m) if ('init' in m[k]) m[k].value = m[k].init(); for (const k in m) if ('init' in m[k]) m[k].value = m[k].init();
m[''].selected = -1; // Workaround for self-selection bug. m[''].selected = -1; // Workaround for self-selection bug.
Bangle.on('drag', this.reactivator);
this.active();
} else {
if (this.bored) clearTimeout(this.bored);
this.bored = null;
Bangle.removeListener('drag', this.reactivator);
this.emit('done');
} }
g.clear(true);
E.showMenu(m); E.showMenu(m);
} }
reset() { active() {
this.backing = {}; if (this.bored) clearTimeout(this.bored);
this.writeBack(0); this.bored = setTimeout(_ => this.showMenu(), 15000);
} }
interact() {this.showMenu(this.menu);} reset() {
this.backing = {__proto__: this.constructor.defaults};
this.writeBack(0);
}
} }
class RomanOptions extends Options { class RomanOptions extends Options {
@ -101,7 +115,7 @@ class RomanOptions extends Options {
super(); super();
this.menu = { this.menu = {
'': {title: '* face options *'}, '': {title: '* face options *'},
'< Back': _ => {this.showMenu(); this.emit('done');}, '< Back': _ => this.showMenu(),
Ticks: { Ticks: {
init: _ => this.resolution, init: _ => this.resolution,
min: 0, max: 3, min: 0, max: 3,
@ -124,9 +138,11 @@ class RomanOptions extends Options {
onchange: x => this.calendric = x, onchange: x => this.calendric = x,
format: x => ['none', 'day', 'date'][x] format: x => ['none', 'day', 'date'][x]
}, },
Defaults: _ => {this.reset();} Defaults: _ => {this.reset(); this.interact();}
}; };
} }
interact() {this.showMenu(this.menu);}
} }
RomanOptions.id = 'pooqroman'; RomanOptions.id = 'pooqroman';
@ -147,7 +163,7 @@ RomanOptions.defaults = {
hubFg: g.theme.fg, hubFg: g.theme.fg,
alarmFg: '#f00', alarmFg: '#f00',
timerFg: '#0f0', timerFg: '#0f0',
active: g.theme.fg2, activeFg: g.theme.fg2,
}; };
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -434,7 +450,7 @@ class Sidebar {
} }
static gpsColour(o) { static gpsColour(o) {
const fix = Bangle.getGPSFix(); const fix = Bangle.getGPSFix();
return fix && fix.fix ? o.active : o.barFg; return fix && fix.fix ? o.activeFg : o.barFg;
} }
doPower() { doPower() {
const c = Bangle.isCharging(); const c = Bangle.isCharging();
@ -455,7 +471,7 @@ class Sidebar {
if (Bangle.isCompassOn()) { if (Bangle.isCompassOn()) {
const c = Bangle.getCompass(); const c = Bangle.getCompass();
const a = c && this.rate <= 1000; const a = c && this.rate <= 1000;
this.g.setColor(a ? this.options.active : this.options.barFg).drawImage( this.g.setColor(a ? this.options.activeFg : this.options.barFg).drawImage(
compassI, compassI,
this.x + 4 + imageWidth(compassI) / 2, this.x + 4 + imageWidth(compassI) / 2,
this.y + 4 + imageHeight(compassI) / 2, this.y + 4 + imageHeight(compassI) / 2,
@ -470,7 +486,7 @@ class Sidebar {
class Roman { class Roman {
constructor(g, events) { constructor(g, events) {
this.g = g; this.g = g;
this.state = {}; this.state = null;
const options = this.options = new RomanOptions(); const options = this.options = new RomanOptions();
this.events = events.loadFromSystem(this.options); this.events = events.loadFromSystem(this.options);
this.timescales = [1000, [1000, 60000], 60000, 3600000]; this.timescales = [1000, [1000, 60000], 60000, 3600000];
@ -480,7 +496,7 @@ class Roman {
this.seconds = Roman.hand(g, 1, 0.9, 60, _ => options.secondFg); this.seconds = Roman.hand(g, 1, 0.9, 60, _ => options.secondFg);
} }
reset() {this.state = {}; this.g.clear(true);} reset() {this.state = null;}
doIcons(which) {this.state.iconsOk = null;} doIcons(which) {this.state.iconsOk = null;}
@ -544,7 +560,7 @@ class Roman {
render(d, rate) { render(d, rate) {
const g = this.g; const g = this.g;
const state = this.state; const state = this.state || (g.clear(true), this.state = {});
const options = this.options; const options = this.options;
const events = this.events; const events = this.events;
events.clean(d, -39600000); // 11h events.clean(d, -39600000); // 11h
@ -654,8 +670,8 @@ class Clock {
drag: e => { drag: e => {
if (this.t0) { if (this.t0) {
if (e.b) { if (e.b) {
e.x > this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x); e.x < this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x);
e.y > this.yN && (this.yN = e.y) || e.y > this.yX && (this.xY = e.y); e.y < this.yN && (this.yN = e.y) || e.y > this.yX && (this.yX = e.y);
} else if (this.xX - this.xN < 20) { } else if (this.xX - this.xN < 20) {
if (e.y - this.e0.y < -50) { if (e.y - this.e0.y < -50) {
this.options.resolution > 0 && this.options.resolution--; this.options.resolution > 0 && this.options.resolution--;
@ -697,6 +713,7 @@ class Clock {
this.exception && clearTimeout(this.exception); this.exception && clearTimeout(this.exception);
this.interval && clearInterval(this.interval); this.interval && clearInterval(this.interval);
this.timeout = this.exception = this.interval = this.rate = null; this.timeout = this.exception = this.interval = this.rate = null;
this.face.reset(); // Cancel any ongoing background rendering
return this; return this;
} }

View File

@ -1,2 +1,4 @@
0.01: Initial creation of the pattern launch app 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.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.

View File

@ -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. Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
## Screenshots and detailed steps ## Add Pattern Screenshots
![](main_menu.png) ![](main_menu_add.png)
![](add_pattern.png) ![](add_pattern.png)
![](select_app.png) ![](select_app.png)
## Manage Pattern Screenshots
![](main_menu_manage.png)
![](manage_patterns.png)
## 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
- 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 tap on screen or 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.
- Remove linked patterns (second entry) - Manage created patterns (second entry)
- To remove a pattern first select "Remove Pattern" - To manage your patterns first select "Manage Patterns"
- You will now see a list of apps that have patterns linked to them - You will now see a scrollabe list of patterns + linked apps
- Simply select the app that you want to unlink. This will remove the saved pattern, but not the app itself! - If you want to deletion a pattern (and unlink the app) simply tap on it, and confirm the deletion
- Note, that you can not actually preview the patterns. This makes removing patterns that are linked to the same app annoying. sorry!
- Disable the lock screen on the clock screen from the settings (third entry) - 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. - 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 - If this annoys you, you can disable the lock on the clock screen from the setting here

View File

@ -1,26 +1,6 @@
var storage = require("Storage");
var DEBUG = false; var DEBUG = false;
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var CIRCLE_RADIUS = 25; var storage = require("Storage");
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 showMainMenu = () => { var showMainMenu = () => {
log("loading patterns"); log("loading patterns");
@ -36,7 +16,7 @@ var showMainMenu = () => {
}, },
"Add Pattern": () => { "Add Pattern": () => {
log("creating pattern"); log("creating pattern");
createPattern().then((pattern) => { recognizeAndDrawPattern().then((pattern) => {
log("got pattern"); log("got pattern");
log(pattern); log(pattern);
log(pattern.length); log(pattern.length);
@ -73,17 +53,32 @@ var showMainMenu = () => {
}); });
}); });
}, },
"Remove Pattern": () => { "Manage Patterns": () => {
log("selecting pattern through app"); 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..."); E.showMessage("Deleting...");
delete storedPatterns[pattern]; delete storedPatterns[pattern];
storage.writeJSON("ptlaunch.patterns.json", storedPatterns); storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu(); showMainMenu();
} else {
showMainMenu();
}
});
}
}); });
}, },
Settings: () => { Settings: () => {
var settings = storedPatterns["settings"] || {}; var settings = storedPatterns.settings || {};
var settingsmenu = { var settingsmenu = {
"": { "": {
@ -98,7 +93,7 @@ var showMainMenu = () => {
if (settings.lockDisabled) { if (settings.lockDisabled) {
settingsmenu["Enable lock"] = () => { settingsmenu["Enable lock"] = () => {
settings.lockDisabled = false; settings.lockDisabled = false;
storedPatterns["settings"] = settings; storedPatterns.settings = settings;
Bangle.setOptions({ lockTimeout: 1000 * 30 }); Bangle.setOptions({ lockTimeout: 1000 * 30 });
storage.writeJSON("ptlaunch.patterns.json", storedPatterns); storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
showMainMenu(); showMainMenu();
@ -106,7 +101,7 @@ var showMainMenu = () => {
} else { } else {
settingsmenu["Disable lock"] = () => { settingsmenu["Disable lock"] = () => {
settings.lockDisabled = true; settings.lockDisabled = true;
storedPatterns["settings"] = settings; storedPatterns.settings = settings;
storage.writeJSON("ptlaunch.patterns.json", storedPatterns); storage.writeJSON("ptlaunch.patterns.json", storedPatterns);
Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 }); Bangle.setOptions({ lockTimeout: 1000 * 60 * 60 * 24 * 365 });
showMainMenu(); showMainMenu();
@ -119,12 +114,8 @@ var showMainMenu = () => {
E.showMenu(mainmenu); E.showMenu(mainmenu);
}; };
var drawCircle = (circle) => {
g.fillCircle(circle.x, circle.y, CIRCLE_RADIUS);
};
var positions = []; var positions = [];
var createPattern = () => { var recognizeAndDrawPattern = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
E.showMenu(); E.showMenu();
g.clear(); g.clear();
@ -147,13 +138,29 @@ var createPattern = () => {
setWatch(() => finishHandler(), BTN); setWatch(() => finishHandler(), BTN);
setTimeout(() => Bangle.on("tap", finishHandler), 250); setTimeout(() => Bangle.on("tap", finishHandler), 250);
positions = [];
var dragHandler = (position) => { var dragHandler = (position) => {
log(position);
positions.push(position); positions.push(position);
debounce().then(() => { debounce().then(() => {
if (isFinished) { if (isFinished) {
return; 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..."); E.showMessage("Calculating...");
var t0 = Date.now(); var t0 = Date.now();
@ -269,18 +276,7 @@ var createPattern = () => {
log("redrawing"); log("redrawing");
g.clear(); g.clear();
g.setColor(0, 0, 0); drawCirclesWithPattern(pattern);
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);
}); });
}; };
@ -341,56 +337,256 @@ var getSelectedApp = () => {
}); });
}; };
var getStoredPatternViaApp = (storedPatterns) => { //////
E.showMessage("Loading patterns..."); // manage pattern related variables and functions
log("getStoredPatternViaApp"); // - draws all saved patterns and their linked app names
return new Promise((resolve) => { // - uses the scroller to allow the user to browse through them
var selectPatternMenu = { //////
"": {
title: "Select App",
},
"< Cancel": () => {
log("cancel");
showMainMenu();
},
};
log(storedPatterns); var scrollerFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
var patterns = Object.keys(storedPatterns);
log(patterns);
patterns.forEach((pattern) => { var drawBackButton = (r) => {
if (pattern) { g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
if (storedPatterns[pattern]) { g.setFont(scrollerFont)
var app = storedPatterns[pattern].app; .setFontAlign(-1, 0)
if (!!app && !!app.name) { .drawString("< Back", 64, r.y + 32);
var appName = app.name; };
var i = 0;
while (appName in selectPatternMenu[app.name]) { var drawAppWithPattern = (i, r, storedPatterns) => {
appName = app.name + i; log("draw app with pattern");
i++; log({ i: i, r: r, storedPatterns: storedPatterns });
} var storedPattern = storedPatterns[i];
selectPatternMenu[appName] = () => { var pattern = storedPattern.pattern;
log("pattern via app selected"); var app = storedPattern.app;
log(pattern);
log(app); g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
resolve(pattern);
}; g.drawLine(r.x, r.y, 176, r.y);
}
} drawCirclesWithPattern(pattern, {
} enableCaching: true,
scale: 0.33,
offset: { x: 1, y: 3 + r.y },
}); });
E.showMenu(selectPatternMenu); 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) => {
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);
}
},
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 });
},
});
}); });
}; };
showMainMenu(); //////
// storage related functions:
// - stored patterns
// - stored settings
//////
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,
});
}
log(storedPatternsArray);
return storedPatternsArray;
};
////// //////
// lib functions // 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(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
);
});
image = {
width: drawBuffer.getWidth(),
height: drawBuffer.getHeight(),
bpp: 1,
buffer: drawBuffer.buffer,
};
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;
};
//////
// misc lib functions
//////
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var debounceTimeoutId; var debounceTimeoutId;
var debounce = (delay) => { var debounce = (delay) => {
if (debounceTimeoutId) { 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++) { showMainMenu();
circlesClone[i] = CIRCLES[i];
}
return circlesClone;
};

View File

@ -5,21 +5,6 @@ var log = (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 storedPatterns; var storedPatterns;
var positions = []; var positions = [];
var dragHandler = (position) => { var dragHandler = (position) => {
@ -28,7 +13,20 @@ var dragHandler = (position) => {
debounce().then(() => { debounce().then(() => {
log(positions.length); log(positions.length);
var circlesClone = cloneCirclesArray(); var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var circles = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
{ x: 25, y: 87, i: 3 },
{ x: 87, y: 87, i: 4 },
{ x: 150, y: 87, i: 5 },
{ x: 25, y: 150, i: 6 },
{ x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 },
];
var pattern = []; var pattern = [];
var step = Math.floor(positions.length / 100) + 1; var step = Math.floor(positions.length / 100) + 1;
@ -38,92 +36,92 @@ var dragHandler = (position) => {
for (var i = 0; i < positions.length; i += step) { for (var i = 0; i < positions.length; i += step) {
p = positions[i]; p = positions[i];
circle = circlesClone[0]; circle = circles[0];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(0, 1); circles.splice(0, 1);
} }
} }
circle = circlesClone[1]; circle = circles[1];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(1, 1); circles.splice(1, 1);
} }
} }
circle = circlesClone[2]; circle = circles[2];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(2, 1); circles.splice(2, 1);
} }
} }
circle = circlesClone[3]; circle = circles[3];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(3, 1); circles.splice(3, 1);
} }
} }
circle = circlesClone[4]; circle = circles[4];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(4, 1); circles.splice(4, 1);
} }
} }
circle = circlesClone[5]; circle = circles[5];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(5, 1); circles.splice(5, 1);
} }
} }
circle = circlesClone[6]; circle = circles[6];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(6, 1); circles.splice(6, 1);
} }
} }
circle = circlesClone[7]; circle = circles[7];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); pattern.push(circle.i);
circlesClone.splice(7, 1); circles.splice(7, 1);
} }
} }
circle = circlesClone[8]; circle = circles[8];
if (circle) { if (circle) {
a = p.x - circle.x; a = p.x - circle.x;
b = p.y - circle.y; b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) { if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i); 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 () { (function () {
var sui = Bangle.setUI; var sui = Bangle.setUI;
Bangle.setUI = function (mode, cb) { 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

View File

@ -2,3 +2,4 @@
0.02: Add widget 0.02: Add widget
0.03: Bangle.js 2 support 0.03: Bangle.js 2 support
0.04: Move Quiet Mode LCD options from global settings to this app 0.04: Move Quiet Mode LCD options from global settings to this app
0.05: Avoid immediately redrawing widgets on load

View File

@ -1,5 +1,8 @@
WIDGETS["qmsched"] = { (function() {
area: "tl", width: 24, draw: function() { WIDGETS["qmsched"] = {
area: "tl",
width: ((require("Storage").readJSON("setting.json", 1) || {}).quiet|0) ? 24 : 0,
draw: function() {
const mode = (require("Storage").readJSON("setting.json", 1) || {}).quiet|0; const mode = (require("Storage").readJSON("setting.json", 1) || {}).quiet|0;
if (mode===0) { // Off if (mode===0) { // Off
if (this.width!==0) { if (this.width!==0) {
@ -29,4 +32,5 @@ WIDGETS["qmsched"] = {
.fillRect(x-1, y-5, x+1, y+5).drawLine(x, y-6, x, y+6) // top+bottom .fillRect(x-1, y-5, x+1, y+5).drawLine(x, y-6, x, y+6) // top+bottom
.drawLine(x+5, y-3, x+3, y-5).drawLine(x-5, y-3, x-3, y-5); // wriggles .drawLine(x+5, y-3, x+3, y-5).drawLine(x-5, y-3, x-3, y-5); // wriggles
}, },
}; };
})();

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
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

View File

@ -3,14 +3,49 @@
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
</head> </head>
<body> <body>
<b>Datasource: </b></br>
<input type="radio" id="useTEXT" name="mode" checked/> <input type="radio" id="useTEXT" name="mode" checked/>
<label for="useTEXT">Use text (for example an URL):</label> <label for="useTEXT">Text</label></br>
<input type="text" id="text" class="form-input" value="www.espruino.com"> <input type="radio" id="useWIFI" name="mode"/>
<label for="useWIFI">Wifi Credentials</label></br>
<input type="radio" id="useFILE" name="mode"/>
<label for="useFILE">QR image</label></br>
<input type="radio" id="useCAM" name="mode"/>
<label for="useCAM">QR scan</label></br>
<hr> <hr>
<input type="radio" id="useWIFI" name="mode"/> <div id="srcText">
<label for="useWIFI">Use Wifi Credentials:</label> <p>Text/URL: <input type="text" id="text" class="form-input" value="http://www.espruino.com"></p>
<input type="text" id="ssid" class="form-input" value=""> </div>
<div id="srcScanCam">
<div>
<video id="qrVideo" align="center" width="50%"></video>
</div>
<div>
<select id="camList">
</select>
</div>
<div>
<button id="flashToggle">Flash: <span id="flashState">off</span></button>
</div>
<br>
Detected QR code:
<span id="camQrResult">None</span>
<br>
<button id="startButton" class="btn btn-primary">Start</button>
<button id="stopButton" class="btn btn-primary">Stop</button>
</div>
<div id="srcScanFile">
<input type="file" id="fileSelector">
<br>
Detected QR code:
<span id="fileQrResult">None</span>
</div>
<div id="srcWifi">
<p>Wifi name: <input type="text" id="ssid" class="form-input" value=""></p>
<p>Wifi password: <input type="password" id="password" class="form-input" value=""></p> <p>Wifi password: <input type="password" id="password" class="form-input" value=""></p>
<div class="form-group"> <div class="form-group">
<label for="encryption" class="control-label">Encryption</label> <label for="encryption" class="control-label">Encryption</label>
@ -21,11 +56,13 @@
<option value="nopass">None</option> <option value="nopass">None</option>
</select> </select>
</div> </div>
</div> </div>
<div> <div>
<input type="checkbox" id="hidden" name="hidden"/> <input type="checkbox" id="hidden" name="hidden"/>
<label for="hidden">Wifi is hidden</label> <label for="hidden">Wifi is hidden</label>
</div> </div>
</div>
<hr> <hr>
<p id="errors" style="color:Tomato;"></p> <p id="errors" style="color:Tomato;"></p>
@ -59,23 +96,73 @@
<script src="../../core/lib/qrcode.min.js"></script><!-- https://davidshimjs.github.io/qrcodejs/ --> <script src="../../core/lib/qrcode.min.js"></script><!-- https://davidshimjs.github.io/qrcodejs/ -->
<script src="../../core/lib/heatshrink.js"></script> <script src="../../core/lib/heatshrink.js"></script>
<script src="../../core/lib/imageconverter.js"></script> <script src="../../core/lib/imageconverter.js"></script>
<script src="./qr-scanner.umd.min.js"></script><!-- https://github.com/nimiq/qr-scanner -->
<script> <script>
var targetSize = 200; var targetSize = 200;
var deviceWidth = targetSize;
var deviceHeight = targetSize;
var border = 4;
var scanner = null;
var qrcode = null;
function onInit(device) { function onInit(device) {
console.info("onInit" + device);
if (device && device.info && device.info.g) { if (device && device.info && device.info.g) {
border = 4; deviceWidth = device.info.g.width;
targetSize = Math.min(device.info.g.width - border, device.info.g.height - border); deviceHeight = device.info.g.height;
} }
qrcode = new QRCode("qrcode", {
text: document.getElementById("text").value,
colorDark : "#000000",
colorLight : "#ffffff",
});
refreshQRCode(); refreshQRCode();
} }
const updateFlashAvailability = () => {
scanner.hasFlash().then(hasFlash => {
document.getElementById('flashToggle').style.display = hasFlash ? 'inline-block' : 'none';
});
};
function setResult(label, result) {
console.info("setResult " + result);
label.textContent = result;
scanner.stop();
refreshQRCode();
}
function initQrScanner() {
console.info("initQrScanner");
QrScanner.WORKER_PATH = './qr-scanner-worker.min.js';
if (scanner == null) {
scanner = new QrScanner(document.getElementById('qrVideo'), result => setResult(document.getElementById('camQrResult'), result), error => {
document.getElementById('camQrResult').textContent = error;
document.getElementById('camQrResult').style.color = 'inherit';
});
}
}
function initQrCam(){
scanner.start().then(() => {
updateFlashAvailability();
QrScanner.listCameras(true).then(cameras => cameras.forEach(camera => {
const option = document.createElement('option');
option.value = camera.id;
option.text = camera.label;
document.getElementById('camList').add(option);
}));
});
}
function toggleVis(id){
console.info("Got id", id);
["srcScanFile", "srcText", "srcWifi", "srcScanCam"].forEach(function (item){
document.getElementById(item).style.display = "none";
});
if (id != undefined && id != null) document.getElementById(id).style.display = "block";
refreshQRCode();
}
toggleVis("srcText");
//https://github.com/evgeni/qifi/blob/gh-pages/index.html#L168 //https://github.com/evgeni/qifi/blob/gh-pages/index.html#L168
function escapeString (string) { function escapeString (string) {
var to_escape = ['\\', ';', ',', ':', '"']; var to_escape = ['\\', ';', ',', ':', '"'];
@ -100,6 +187,13 @@
return qrstring; return qrstring;
} }
function refreshQRCode(){ function refreshQRCode(){
if (qrcode == null){
qrcode = new QRCode("qrcode", {
text: document.getElementById("text").value,
colorDark : "#000000",
colorLight : "#ffffff"
});
}
document.getElementById("errors").innerText=""; document.getElementById("errors").innerText="";
qrcode.clear(); // clear the code. qrcode.clear(); // clear the code.
var qrText = ""; var qrText = "";
@ -110,9 +204,17 @@
const hidden = document.getElementById("hidden").checked; const hidden = document.getElementById("hidden").checked;
const wifiString = generateWifiString(ssid, password, hidden, encryption); const wifiString = generateWifiString(ssid, password, hidden, encryption);
qrText= wifiString; qrText= wifiString;
} else if (document.getElementById("useCAM").checked) {
qrText= document.getElementById("camQrResult").innerText;
} else if (document.getElementById("useFILE").checked) {
qrText= document.getElementById("fileQrResult").innerText;
} else { } else {
qrText = document.getElementById("text").value; qrText = document.getElementById("text").value;
} }
console.info("Given qrtext was: " + qrText);
qrcode._htOption.text = qrText; qrcode._htOption.text = qrText;
qrcode._htOption.correctLevel = parseInt(document.getElementById("correction").value); qrcode._htOption.correctLevel = parseInt(document.getElementById("correction").value);
try { try {
@ -122,18 +224,24 @@
console.error(error); console.error(error);
} }
targetSize = Math.min(deviceWidth - border, deviceHeight - border);
console.info("Targetsize: " + targetSize);
var finalSizeQr=targetSize; var finalSizeQr=targetSize;
var finalSizeCanvas=targetSize; var finalSizeCanvas=targetSize;
var integerScale = Math.max(Math.floor(targetSize / (qrcode._oQRCode.moduleCount + 1)),1); var integerScale = Math.max(Math.floor(targetSize / (qrcode._oQRCode.moduleCount + 1)),1);
if (integerScale == 1) document.getElementById("errors").innerText = "Warning, QR will probably be too small to properly scan. Try less data or less error correction."; if (integerScale == 1) document.getElementById("errors").innerText = "Warning, QR will probably be too small to properly scan. Try less data or less error correction.";
if (!document.getElementById("preventIntegerScaling").checked){ console.info("IntegerScale: " + integerScale);
if (!document.getElementById("preventIntegerScaling").checked){
finalSizeQr = integerScale * (qrcode._oQRCode.moduleCount + 1); finalSizeQr = integerScale * (qrcode._oQRCode.moduleCount + 1);
finalSizeCanvas = finalSizeQr - 1; finalSizeCanvas = finalSizeQr - 1;
} }
console.info("FinalSizeQr: " + finalSizeQr);
console.info("FinalSizeCanvas: " + finalSizeCanvas);
qrcode._htOption.width = finalSizeQr; qrcode._htOption.width = finalSizeQr;
qrcode._htOption.height = finalSizeQr; qrcode._htOption.height = finalSizeQr;
@ -146,14 +254,26 @@
console.error(error); console.error(error);
} }
} }
var qrcode;
document.getElementById("useTEXT").addEventListener("change",function(){toggleVis("srcText");});
document.getElementById("useCAM").addEventListener("change",function(){
initQrScanner();
initQrCam();
toggleVis("srcScanCam");
});
document.getElementById("useFILE").addEventListener("change",function(){
initQrScanner();
toggleVis("srcScanFile");
});
document.getElementById("useWIFI").addEventListener("change",function(){toggleVis("srcWifi");});
document.getElementById("ssid").addEventListener("change",refreshQRCode); document.getElementById("ssid").addEventListener("change",refreshQRCode);
document.getElementById("text").addEventListener("change",refreshQRCode); document.getElementById("text").addEventListener("change",refreshQRCode);
document.getElementById("password").addEventListener("change",refreshQRCode); document.getElementById("password").addEventListener("change",refreshQRCode);
document.getElementById("encryption").addEventListener("change",refreshQRCode); document.getElementById("encryption").addEventListener("change",refreshQRCode);
document.getElementById("hidden").addEventListener("change",refreshQRCode); document.getElementById("hidden").addEventListener("change",refreshQRCode);
document.getElementById("useTEXT").addEventListener("change",refreshQRCode); document.getElementById("useTEXT").addEventListener("change",refreshQRCode);
document.getElementById("useCAM").addEventListener("change",refreshQRCode);
document.getElementById("useFILE").addEventListener("change",refreshQRCode);
document.getElementById("useWIFI").addEventListener("change",refreshQRCode); document.getElementById("useWIFI").addEventListener("change",refreshQRCode);
document.getElementById("preventIntegerScaling").addEventListener("change",refreshQRCode); document.getElementById("preventIntegerScaling").addEventListener("change",refreshQRCode);
document.getElementById("correction").addEventListener("change",refreshQRCode); document.getElementById("correction").addEventListener("change",refreshQRCode);
@ -179,13 +299,39 @@ g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4));
g.setColor(1,1,1); g.setColor(1,1,1);
`; `;
sendCustomizedApp({ sendCustomizedApp({
storage:[ storage:[{name:"qrcode.app.js", url:"app.js", content:app},]
{name:"qrcode.app.js", url:"app.js", content:app},
]
}); });
}); });
document.getElementById('camList').addEventListener('change', event => {
scanner.setCamera(event.target.value).then(updateFlashAvailability);
});
document.getElementById('flashToggle').addEventListener('click', () => {
scanner.toggleFlash().then(() => document.getElementById('flashState').textContent = scanner.isFlashOn() ? 'on' : 'off');
});
document.getElementById('startButton').addEventListener('click', () => {
scanner.start();
});
document.getElementById('stopButton').addEventListener('click', () => {
scanner.stop();
});
document.getElementById('fileSelector').addEventListener('change', event => {
const file = document.getElementById('fileSelector').files[0];
if (!file) {
return;
}
QrScanner.scanImage(file)
.then(result => setResult(document.getElementById('fileQrResult'), result))
.catch(e => setResult(document.getElementById('fileQrResult'), e || 'No QR code found.'));
});
</script> </script>
</body> </body>
</html> </html>

87
apps/qrcode/qr-scanner-worker.min.js vendored Normal file
View File

@ -0,0 +1,87 @@
'use strict';(function(){function T(a,b){let c=[],d="";b=a.readBits([8,16,16][b]);for(let d=0;d<b;d++){let b=a.readBits(8);c.push(b)}try{d+=decodeURIComponent(c.map(a=>`%${("0"+a.toString(16)).substr(-2)}`).join(""))}catch(e){}return{bytes:c,text:d}}function U(a,b){a=new V(a);let c=9>=b?0:26>=b?1:2;for(b={text:"",bytes:[],chunks:[],version:b};4<=a.available();){var d=a.readBits(4);if(d===t.Terminator)return b;if(d===t.ECI)0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(7)}):
0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(14)}):0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(21)}):b.chunks.push({type:r.ECI,assignmentNumber:-1});else if(d===t.Numeric){var e=a;d=[];for(var f="",g=e.readBits([10,12,14][c]);3<=g;){var h=e.readBits(10);if(1E3<=h)throw Error("Invalid numeric value above 999");var k=Math.floor(h/100),n=Math.floor(h/10)%10;h%=10;d.push(48+k,48+n,48+h);f+=k.toString()+n.toString()+h.toString();g-=3}if(2===g){g=e.readBits(7);
if(100<=g)throw Error("Invalid numeric value above 99");e=Math.floor(g/10);g%=10;d.push(48+e,48+g);f+=e.toString()+g.toString()}else if(1===g){e=e.readBits(4);if(10<=e)throw Error("Invalid numeric value above 9");d.push(48+e);f+=e.toString()}d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Numeric,text:d.text})}else if(d===t.Alphanumeric){e=a;d=[];f="";for(g=e.readBits([9,11,13][c]);2<=g;)n=e.readBits(11),k=Math.floor(n/45),n%=45,d.push(B[k].charCodeAt(0),B[n].charCodeAt(0)),
f+=B[k]+B[n],g-=2;1===g&&(e=e.readBits(6),d.push(B[e].charCodeAt(0)),f+=B[e]);d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Alphanumeric,text:d.text})}else if(d===t.Byte)d=T(a,c),b.text+=d.text,b.bytes.push(...d.bytes),b.chunks.push({type:r.Byte,bytes:d.bytes,text:d.text});else if(d===t.Kanji){f=a;d=[];e=f.readBits([8,10,12][c]);for(g=0;g<e;g++)k=f.readBits(13),k=Math.floor(k/192)<<8|k%192,k=7936>k?k+33088:k+49472,d.push(k>>8,k&255);f=(new TextDecoder("shift-jis")).decode(Uint8Array.from(d));
d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Kanji,bytes:d.bytes,text:d.text})}else d===t.StructuredAppend&&b.chunks.push({type:r.StructuredAppend,currentSequence:a.readBits(4),totalSequence:a.readBits(4),parity:a.readBits(8)})}if(0===a.available()||0===a.readBits(a.available()))return b}function J(a,b){return a^b}function W(a,b,c,d){b.degree()<c.degree()&&([b,c]=[c,b]);let e=a.zero;for(var f=a.one;c.degree()>=d/2;){var g=b;let d=e;b=c;e=f;if(b.isZero())return null;
c=g;f=a.zero;g=b.getCoefficient(b.degree());for(g=a.inverse(g);c.degree()>=b.degree()&&!c.isZero();){let d=c.degree()-b.degree(),e=a.multiply(c.getCoefficient(c.degree()),g);f=f.addOrSubtract(a.buildMonomial(d,e));c=c.addOrSubtract(b.multiplyByMonomial(d,e))}f=f.multiplyPoly(e).addOrSubtract(d);if(c.degree()>=b.degree())return null}d=f.getCoefficient(0);if(0===d)return null;a=a.inverse(d);return[f.multiply(a),c.multiply(a)]}function X(a,b){let c=new Uint8ClampedArray(a.length);c.set(a);a=new Y(285,
256,0);var d=new w(a,c),e=new Uint8ClampedArray(b),f=!1;for(var g=0;g<b;g++){var h=d.evaluateAt(a.exp(g+a.generatorBase));e[e.length-1-g]=h;0!==h&&(f=!0)}if(!f)return c;d=new w(a,e);d=W(a,a.buildMonomial(b,1),d,b);if(null===d)return null;b=d[0];g=b.degree();if(1===g)b=[b.getCoefficient(1)];else{e=Array(g);f=0;for(h=1;h<a.size&&f<g;h++)0===b.evaluateAt(h)&&(e[f]=a.inverse(h),f++);b=f!==g?null:e}if(null==b)return null;d=d[1];e=b.length;f=Array(e);for(g=0;g<e;g++){h=a.inverse(b[g]);let c=1;for(let d=
0;d<e;d++)g!==d&&(c=a.multiply(c,J(1,a.multiply(b[d],h))));f[g]=a.multiply(d.evaluateAt(h),a.inverse(c));0!==a.generatorBase&&(f[g]=a.multiply(f[g],h))}d=f;for(e=0;e<b.length;e++){f=c.length-1-a.log(b[e]);if(0>f)return null;c[f]^=d[e]}return c}function E(a,b){a^=b;for(b=0;a;)b++,a&=a-1;return b}function C(a,b){return b<<1|a}function Z(a,b,c){c=aa[c.dataMask];let d=a.height;var e=17+4*b.versionNumber,f=A.createEmpty(e,e);f.setRegion(0,0,9,9,!0);f.setRegion(e-8,0,8,9,!0);f.setRegion(0,e-8,9,8,!0);for(var g of b.alignmentPatternCenters)for(var h of b.alignmentPatternCenters)6===
g&&6===h||6===g&&h===e-7||g===e-7&&6===h||f.setRegion(g-2,h-2,5,5,!0);f.setRegion(6,9,1,e-17,!0);f.setRegion(9,6,e-17,1,!0);6<b.versionNumber&&(f.setRegion(e-11,0,3,6,!0),f.setRegion(0,e-11,6,3,!0));b=f;g=[];e=h=0;f=!0;for(let k=d-1;0<k;k-=2){6===k&&k--;for(let n=0;n<d;n++){let m=f?d-1-n:n;for(let d=0;2>d;d++){let f=k-d;if(!b.get(f,m)){e++;let b=a.get(f,m);c({y:m,x:f})&&(b=!b);h=h<<1|b;8===e&&(g.push(h),h=e=0)}}}f=!f}return g}function ba(a){var b=a.height,c=Math.floor((b-17)/4);if(6>=c)return K[c-
1];c=0;for(var d=5;0<=d;d--)for(var e=b-9;e>=b-11;e--)c=C(a.get(e,d),c);d=0;for(e=5;0<=e;e--)for(let c=b-9;c>=b-11;c--)d=C(a.get(e,c),d);a=Infinity;let f;for(let e of K){if(e.infoBits===c||e.infoBits===d)return e;b=E(c,e.infoBits);b<a&&(f=e,a=b);b=E(d,e.infoBits);b<a&&(f=e,a=b)}if(3>=a)return f}function ca(a){let b=0;for(var c=0;8>=c;c++)6!==c&&(b=C(a.get(c,8),b));for(c=7;0<=c;c--)6!==c&&(b=C(a.get(8,c),b));var d=a.height;c=0;for(var e=d-1;e>=d-7;e--)c=C(a.get(8,e),c);for(e=d-8;e<d;e++)c=C(a.get(e,
8),c);a=Infinity;d=null;for(let {bits:f,formatInfo:g}of da){if(f===b||f===c)return g;e=E(b,f);e<a&&(d=g,a=e);b!==c&&(e=E(c,f),e<a&&(d=g,a=e))}return 3>=a?d:null}function ea(a,b,c){let d=b.errorCorrectionLevels[c],e=[],f=0;d.ecBlocks.forEach(a=>{for(let b=0;b<a.numBlocks;b++)e.push({numDataCodewords:a.dataCodewordsPerBlock,codewords:[]}),f+=a.dataCodewordsPerBlock+d.ecCodewordsPerBlock});if(a.length<f)return null;a=a.slice(0,f);b=d.ecBlocks[0].dataCodewordsPerBlock;for(c=0;c<b;c++)for(var g of e)g.codewords.push(a.shift());
if(1<d.ecBlocks.length)for(g=d.ecBlocks[0].numBlocks,b=d.ecBlocks[1].numBlocks,c=0;c<b;c++)e[g+c].codewords.push(a.shift());for(;0<a.length;)for(let b of e)b.codewords.push(a.shift());return e}function L(a){let b=ba(a);if(!b)return null;var c=ca(a);if(!c)return null;a=Z(a,b,c);var d=ea(a,b,c.errorCorrectionLevel);if(!d)return null;c=d.reduce((a,b)=>a+b.numDataCodewords,0);c=new Uint8ClampedArray(c);a=0;for(let b of d){d=X(b.codewords,b.codewords.length-b.numDataCodewords);if(!d)return null;for(let e=
0;e<b.numDataCodewords;e++)c[a++]=d[e]}try{return U(c,b.versionNumber)}catch(e){return null}}function M(a,b,c,d){var e=a.x-b.x+c.x-d.x;let f=a.y-b.y+c.y-d.y;if(0===e&&0===f)return{a11:b.x-a.x,a12:b.y-a.y,a13:0,a21:c.x-b.x,a22:c.y-b.y,a23:0,a31:a.x,a32:a.y,a33:1};{let h=b.x-c.x;var g=d.x-c.x;let k=b.y-c.y,n=d.y-c.y;c=h*n-g*k;g=(e*n-g*f)/c;e=(h*f-e*k)/c;return{a11:b.x-a.x+g*b.x,a12:b.y-a.y+g*b.y,a13:g,a21:d.x-a.x+e*d.x,a22:d.y-a.y+e*d.y,a23:e,a31:a.x,a32:a.y,a33:1}}}function fa(a,b,c,d){a=M(a,b,c,d);
return{a11:a.a22*a.a33-a.a23*a.a32,a12:a.a13*a.a32-a.a12*a.a33,a13:a.a12*a.a23-a.a13*a.a22,a21:a.a23*a.a31-a.a21*a.a33,a22:a.a11*a.a33-a.a13*a.a31,a23:a.a13*a.a21-a.a11*a.a23,a31:a.a21*a.a32-a.a22*a.a31,a32:a.a12*a.a31-a.a11*a.a32,a33:a.a11*a.a22-a.a12*a.a21}}function ha(a,b){var c=fa({x:3.5,y:3.5},{x:b.dimension-3.5,y:3.5},{x:b.dimension-6.5,y:b.dimension-6.5},{x:3.5,y:b.dimension-3.5}),d=M(b.topLeft,b.topRight,b.alignmentPattern,b.bottomLeft),e=d.a11*c.a11+d.a21*c.a12+d.a31*c.a13,f=d.a12*c.a11+
d.a22*c.a12+d.a32*c.a13,g=d.a13*c.a11+d.a23*c.a12+d.a33*c.a13,h=d.a11*c.a21+d.a21*c.a22+d.a31*c.a23,k=d.a12*c.a21+d.a22*c.a22+d.a32*c.a23,n=d.a13*c.a21+d.a23*c.a22+d.a33*c.a23,m=d.a11*c.a31+d.a21*c.a32+d.a31*c.a33,l=d.a12*c.a31+d.a22*c.a32+d.a32*c.a33,p=d.a13*c.a31+d.a23*c.a32+d.a33*c.a33;c=A.createEmpty(b.dimension,b.dimension);d=(a,b)=>{const c=g*a+n*b+p;return{x:(e*a+h*b+m)/c,y:(f*a+k*b+l)/c}};for(let e=0;e<b.dimension;e++)for(let f=0;f<b.dimension;f++){let b=d(f+.5,e+.5);c.set(f,e,a.get(Math.floor(b.x),
Math.floor(b.y)))}return{matrix:c,mappingFunction:d}}function x(a){return a.reduce((a,c)=>a+c)}function ia(a,b,c){let d=y(a,b),e=y(b,c),f=y(a,c),g,h,k;e>=d&&e>=f?[g,h,k]=[b,a,c]:f>=e&&f>=d?[g,h,k]=[a,b,c]:[g,h,k]=[a,c,b];0>(k.x-h.x)*(g.y-h.y)-(k.y-h.y)*(g.x-h.x)&&([g,k]=[k,g]);return{bottomLeft:g,topLeft:h,topRight:k}}function ja(a,b,c,d){d=(x(z(a,c,d,5))/7+x(z(a,b,d,5))/7+x(z(c,a,d,5))/7+x(z(b,a,d,5))/7)/4;if(1>d)throw Error("Invalid module size");b=Math.round(y(a,b)/d);a=Math.round(y(a,c)/d);a=
Math.floor((b+a)/2)+7;switch(a%4){case 0:a++;break;case 2:a--}return{dimension:a,moduleSize:d}}function N(a,b,c,d){let e=[{x:Math.floor(a.x),y:Math.floor(a.y)}];var f=Math.abs(b.y-a.y)>Math.abs(b.x-a.x);if(f){var g=Math.floor(a.y);var h=Math.floor(a.x);a=Math.floor(b.y);b=Math.floor(b.x)}else g=Math.floor(a.x),h=Math.floor(a.y),a=Math.floor(b.x),b=Math.floor(b.y);let k=Math.abs(a-g),n=Math.abs(b-h),m=Math.floor(-k/2),l=g<a?1:-1,p=h<b?1:-1,q=!0;for(let v=g,u=h;v!==a+l;v+=l){g=f?u:v;h=f?v:u;if(c.get(g,
h)!==q&&(q=!q,e.push({x:g,y:h}),e.length===d+1))break;m+=n;if(0<m){if(u===b)break;u+=p;m-=k}}c=[];for(f=0;f<d;f++)e[f]&&e[f+1]?c.push(y(e[f],e[f+1])):c.push(0);return c}function z(a,b,c,d){let e=b.y-a.y,f=b.x-a.x;b=N(a,b,c,Math.ceil(d/2));a=N(a,{x:a.x-f,y:a.y-e},c,Math.ceil(d/2));c=b.shift()+a.shift()-1;return a.concat(c).concat(...b)}function F(a,b){let c=x(a)/x(b),d=0;b.forEach((b,f)=>{d+=Math.pow(a[f]-b*c,2)});return{averageSize:c,error:d}}function O(a,b,c){try{let d=z(a,{x:-1,y:a.y},c,b.length),
e=z(a,{x:a.x,y:-1},c,b.length),f=z(a,{x:Math.max(0,a.x-a.y)-1,y:Math.max(0,a.y-a.x)-1},c,b.length),g=z(a,{x:Math.min(c.width,a.x+a.y)+1,y:Math.min(c.height,a.y+a.x)+1},c,b.length),h=F(d,b),k=F(e,b),n=F(f,b),m=F(g,b),l=(h.averageSize+k.averageSize+n.averageSize+m.averageSize)/4;return Math.sqrt(h.error*h.error+k.error*k.error+n.error*n.error+m.error*m.error)+(Math.pow(h.averageSize-l,2)+Math.pow(k.averageSize-l,2)+Math.pow(n.averageSize-l,2)+Math.pow(m.averageSize-l,2))/l}catch(d){return Infinity}}
function H(a,b){for(var c=Math.round(b.x);a.get(c,Math.round(b.y));)c--;for(var d=Math.round(b.x);a.get(d,Math.round(b.y));)d++;c=(c+d)/2;for(d=Math.round(b.y);a.get(Math.round(c),d);)d--;for(b=Math.round(b.y);a.get(Math.round(c),b);)b++;return{x:c,y:(d+b)/2}}function ka(a){var b=[],c=[];let d=[];var e=[];for(let m=0;m<=a.height;m++){var f=0,g=!1;let l=[0,0,0,0,0];for(let b=-1;b<=a.width;b++){var h=a.get(b,m);if(h===g)f++;else{l=[l[1],l[2],l[3],l[4],f];f=1;g=h;var k=x(l)/7;k=Math.abs(l[0]-k)<k&&Math.abs(l[1]-
k)<k&&Math.abs(l[2]-3*k)<3*k&&Math.abs(l[3]-k)<k&&Math.abs(l[4]-k)<k&&!h;var n=x(l.slice(-3))/3;h=Math.abs(l[2]-n)<n&&Math.abs(l[3]-n)<n&&Math.abs(l[4]-n)<n&&h;if(k){let a=b-l[3]-l[4],d=a-l[2];k={startX:d,endX:a,y:m};n=c.filter(b=>d>=b.bottom.startX&&d<=b.bottom.endX||a>=b.bottom.startX&&d<=b.bottom.endX||d<=b.bottom.startX&&a>=b.bottom.endX&&1.5>l[2]/(b.bottom.endX-b.bottom.startX)&&.5<l[2]/(b.bottom.endX-b.bottom.startX));0<n.length?n[0].bottom=k:c.push({top:k,bottom:k})}if(h){let a=b-l[4],c=a-
l[3];h={startX:c,y:m,endX:a};k=e.filter(b=>c>=b.bottom.startX&&c<=b.bottom.endX||a>=b.bottom.startX&&c<=b.bottom.endX||c<=b.bottom.startX&&a>=b.bottom.endX&&1.5>l[2]/(b.bottom.endX-b.bottom.startX)&&.5<l[2]/(b.bottom.endX-b.bottom.startX));0<k.length?k[0].bottom=h:e.push({top:h,bottom:h})}}}b.push(...c.filter(a=>a.bottom.y!==m&&2<=a.bottom.y-a.top.y));c=c.filter(a=>a.bottom.y===m);d.push(...e.filter(a=>a.bottom.y!==m));e=e.filter(a=>a.bottom.y===m)}b.push(...c.filter(a=>2<=a.bottom.y-a.top.y));d.push(...e);
c=[];for(var m of b)2>m.bottom.y-m.top.y||(b=(m.top.startX+m.top.endX+m.bottom.startX+m.bottom.endX)/4,e=(m.top.y+m.bottom.y+1)/2,a.get(Math.round(b),Math.round(e))&&(f=[m.top.endX-m.top.startX,m.bottom.endX-m.bottom.startX,m.bottom.y-m.top.y+1],f=x(f)/f.length,g=O({x:Math.round(b),y:Math.round(e)},[1,1,3,1,1],a),c.push({score:g,x:b,y:e,size:f})));if(3>c.length)return null;c.sort((a,b)=>a.score-b.score);m=[];for(b=0;b<Math.min(c.length,5);++b){e=c[b];f=[];for(var l of c)l!==e&&f.push(Object.assign(Object.assign({},
l),{score:l.score+Math.pow(l.size-e.size,2)/e.size}));f.sort((a,b)=>a.score-b.score);m.push({points:[e,f[0],f[1]],score:e.score+f[0].score+f[1].score})}m.sort((a,b)=>a.score-b.score);let {topRight:p,topLeft:q,bottomLeft:v}=ia(...m[0].points);m=P(a,d,p,q,v);l=[];m&&l.push({alignmentPattern:{x:m.alignmentPattern.x,y:m.alignmentPattern.y},bottomLeft:{x:v.x,y:v.y},dimension:m.dimension,topLeft:{x:q.x,y:q.y},topRight:{x:p.x,y:p.y}});m=H(a,p);b=H(a,q);c=H(a,v);(a=P(a,d,m,b,c))&&l.push({alignmentPattern:{x:a.alignmentPattern.x,
y:a.alignmentPattern.y},bottomLeft:{x:c.x,y:c.y},topLeft:{x:b.x,y:b.y},topRight:{x:m.x,y:m.y},dimension:a.dimension});return 0===l.length?null:l}function P(a,b,c,d,e){let f,g;try{({dimension:f,moduleSize:g}=ja(d,c,e,a))}catch(m){return null}var h=c.x-d.x+e.x,k=c.y-d.y+e.y;c=(y(d,e)+y(d,c))/2/g;e=1-3/c;let n={x:d.x+e*(h-d.x),y:d.y+e*(k-d.y)};b=b.map(b=>{const c=(b.top.startX+b.top.endX+b.bottom.startX+b.bottom.endX)/4;b=(b.top.y+b.bottom.y+1)/2;if(a.get(Math.floor(c),Math.floor(b))){var d=O({x:Math.floor(c),
y:Math.floor(b)},[1,1,1],a)+y({x:c,y:b},n);return{x:c,y:b,score:d}}}).filter(a=>!!a).sort((a,b)=>a.score-b.score);return{alignmentPattern:15<=c&&b.length?b[0]:n,dimension:f}}function Q(a){var b=ka(a);if(!b)return null;for(let e of b){b=ha(a,e);var c=b.matrix;if(null==c)c=null;else{var d=L(c);if(d)c=d;else{for(d=0;d<c.width;d++)for(let a=d+1;a<c.height;a++)c.get(d,a)!==c.get(a,d)&&(c.set(d,a,!c.get(d,a)),c.set(a,d,!c.get(a,d)));c=L(c)}}if(c)return{binaryData:c.bytes,data:c.text,chunks:c.chunks,version:c.version,
location:{topRightCorner:b.mappingFunction(e.dimension,0),topLeftCorner:b.mappingFunction(0,0),bottomRightCorner:b.mappingFunction(e.dimension,e.dimension),bottomLeftCorner:b.mappingFunction(0,e.dimension),topRightFinderPattern:e.topRight,topLeftFinderPattern:e.topLeft,bottomLeftFinderPattern:e.bottomLeft,bottomRightAlignmentPattern:e.alignmentPattern},matrix:b.matrix}}return null}function R(a,b){Object.keys(b).forEach(c=>{a[c]=b[c]})}function I(a,b,c,d={}){let e=Object.create(null);R(e,la);R(e,d);
d="onlyInvert"===e.inversionAttempts||"invertFirst"===e.inversionAttempts;var f="attemptBoth"===e.inversionAttempts||d;var g=e.greyScaleWeights,h=e.canOverwriteImage,k=b*c;if(a.length!==4*k)throw Error("Malformed data passed to binarizer.");var n=0;if(h){var m=new Uint8ClampedArray(a.buffer,n,k);n+=k}m=new S(b,c,m);if(g.useIntegerApproximation)for(var l=0;l<c;l++)for(var p=0;p<b;p++){var q=4*(l*b+p);m.set(p,l,g.red*a[q]+g.green*a[q+1]+g.blue*a[q+2]+128>>8)}else for(l=0;l<c;l++)for(p=0;p<b;p++)q=4*
(l*b+p),m.set(p,l,g.red*a[q]+g.green*a[q+1]+g.blue*a[q+2]);g=Math.ceil(b/8);l=Math.ceil(c/8);p=g*l;if(h){var v=new Uint8ClampedArray(a.buffer,n,p);n+=p}v=new S(g,l,v);for(p=0;p<l;p++)for(q=0;q<g;q++){var u=Infinity,r=0;for(var t=0;8>t;t++)for(let a=0;8>a;a++){let b=m.get(8*q+a,8*p+t);u=Math.min(u,b);r=Math.max(r,b)}t=(u+r)/2;t=Math.min(255,1.11*t);24>=r-u&&(t=u/2,0<p&&0<q&&(r=(v.get(q,p-1)+2*v.get(q-1,p)+v.get(q-1,p-1))/4,u<r&&(t=r)));v.set(q,p,t)}h?(p=new Uint8ClampedArray(a.buffer,n,k),n+=k,p=new A(p,
b)):p=A.createEmpty(b,c);q=null;f&&(h?(a=new Uint8ClampedArray(a.buffer,n,k),q=new A(a,b)):q=A.createEmpty(b,c));for(b=0;b<l;b++)for(a=0;a<g;a++){c=g-3;c=2>a?2:a>c?c:a;h=l-3;h=2>b?2:b>h?h:b;k=0;for(n=-2;2>=n;n++)for(u=-2;2>=u;u++)k+=v.get(c+n,h+u);c=k/25;for(h=0;8>h;h++)for(k=0;8>k;k++)n=8*a+h,u=8*b+k,r=m.get(n,u),p.set(n,u,r<=c),f&&q.set(n,u,!(r<=c))}f=f?{binarized:p,inverted:q}:{binarized:p};let {binarized:w,inverted:x}=f;(f=Q(d?x:w))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||
(f=Q(d?w:x));return f}class A{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new A(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let b=a;b<a+c;b++)this.set(b,f,!!e)}}class S{constructor(a,b,c){this.width=a;a*=b;if(c&&c.length!==a)throw Error("Wrong buffer size");this.data=c||new Uint8ClampedArray(a)}get(a,
b){return this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c}}class V{constructor(a){this.bitOffset=this.byteOffset=0;this.bytes=a}readBits(a){if(1>a||32<a||a>this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0<this.bitOffset){b=8-this.bitOffset;var c=a<b?a:b;b-=c;b=(this.bytes[this.byteOffset]&255>>8-c<<b)>>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0<a){for(;8<=a;)b=b<<8|this.bytes[this.byteOffset]&255,this.byteOffset++,
a-=8;0<a&&(c=8-a,b=b<<a|(this.bytes[this.byteOffset]&255>>c<<c)>>c,this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var r;(function(a){a.Numeric="numeric";a.Alphanumeric="alphanumeric";a.Byte="byte";a.Kanji="kanji";a.ECI="eci";a.StructuredAppend="structuredappend"})(r||(r={}));var t;(function(a){a[a.Terminator=0]="Terminator";a[a.Numeric=1]="Numeric";a[a.Alphanumeric=2]="Alphanumeric";a[a.Byte=4]="Byte";a[a.Kanji=8]="Kanji";a[a.ECI=7]="ECI";a[a.StructuredAppend=
3]="StructuredAppend"})(t||(t={}));let B="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");class w{constructor(a,b){if(0===b.length)throw Error("No coefficients.");this.field=a;let c=b.length;if(1<c&&0===b[0]){let d=1;for(;d<c&&0===b[d];)d++;if(d===c)this.coefficients=a.zero.coefficients;else for(this.coefficients=new Uint8ClampedArray(c-d),a=0;a<this.coefficients.length;a++)this.coefficients[a]=b[d+a]}else this.coefficients=b}degree(){return this.coefficients.length-1}isZero(){return 0===
this.coefficients[0]}getCoefficient(a){return this.coefficients[this.coefficients.length-1-a]}addOrSubtract(a){if(this.isZero())return a;if(a.isZero())return this;let b=this.coefficients;a=a.coefficients;b.length>a.length&&([b,a]=[a,b]);let c=new Uint8ClampedArray(a.length),d=a.length-b.length;for(var e=0;e<d;e++)c[e]=a[e];for(e=d;e<a.length;e++)c[e]=b[e-d]^a[e];return new w(this.field,c)}multiply(a){if(0===a)return this.field.zero;if(1===a)return this;let b=this.coefficients.length,c=new Uint8ClampedArray(b);
for(let d=0;d<b;d++)c[d]=this.field.multiply(this.coefficients[d],a);return new w(this.field,c)}multiplyPoly(a){if(this.isZero()||a.isZero())return this.field.zero;let b=this.coefficients,c=b.length;a=a.coefficients;let d=a.length,e=new Uint8ClampedArray(c+d-1);for(let h=0;h<c;h++){let c=b[h];for(let b=0;b<d;b++){var f=h+b,g=this.field.multiply(c,a[b]);e[f]=e[h+b]^g}}return new w(this.field,e)}multiplyByMonomial(a,b){if(0>a)throw Error("Invalid degree less than 0");if(0===b)return this.field.zero;
let c=this.coefficients.length;a=new Uint8ClampedArray(c+a);for(let d=0;d<c;d++)a[d]=this.field.multiply(this.coefficients[d],b);return new w(this.field,a)}evaluateAt(a){let b=0;if(0===a)return this.getCoefficient(0);let c=this.coefficients.length;if(1===a)return this.coefficients.forEach(a=>{b^=a}),b;b=this.coefficients[0];for(let d=1;d<c;d++)b=J(this.field.multiply(a,b),this.coefficients[d]);return b}}class Y{constructor(a,b,c){this.primitive=a;this.size=b;this.generatorBase=c;this.expTable=Array(this.size);
this.logTable=Array(this.size);a=1;for(b=0;b<this.size;b++)this.expTable[b]=a,a*=2,a>=this.size&&(a=(a^this.primitive)&this.size-1);for(a=0;a<this.size-1;a++)this.logTable[this.expTable[a]]=a;this.zero=new w(this,Uint8ClampedArray.from([0]));this.one=new w(this,Uint8ClampedArray.from([1]))}multiply(a,b){return 0===a||0===b?0:this.expTable[(this.logTable[a]+this.logTable[b])%(this.size-1)]}inverse(a){if(0===a)throw Error("Can't invert 0");return this.expTable[this.size-this.logTable[a]-1]}buildMonomial(a,
b){if(0>a)throw Error("Invalid monomial degree less than 0");if(0===b)return this.zero;a=new Uint8ClampedArray(a+1);a[0]=b;return new w(this,a)}log(a){if(0===a)throw Error("Can't take log(0)");return this.logTable[a]}exp(a){return this.expTable[a]}}let K=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,
ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},
{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,
dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6,34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},
{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38},{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},
{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},
{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,
dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,
dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},
{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50,74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},
{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749,versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},
{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17,dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,
ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,
dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,
dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,
dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,
32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},
{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},
{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117},{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,
dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6,26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},
{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]},{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},
{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,
30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},
{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,
dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,
dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017,versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,
dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15},{numBlocks:61,dataCodewordsPerBlock:16}]}]}],da=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,
dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0,dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,
formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,
dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,
dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],aa=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y%2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2],y=(a,b)=>Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2)),la={inversionAttempts:"attemptBoth",greyScaleWeights:{red:.2126,green:.7152,blue:.0722,useIntegerApproximation:!1},
canOverwriteImage:!0};I.default=I;let G="dontInvert",D={red:77,green:150,blue:29,useIntegerApproximation:!0};self.onmessage=a=>{let b=a.data.data;switch(a.data.type){case "decode":a=I(b.data,b.width,b.height,{inversionAttempts:G,greyScaleWeights:D});self.postMessage({type:"qrResult",data:a?a.data:null});break;case "grayscaleWeights":D.red=b.red;D.green=b.green;D.blue=b.blue;D.useIntegerApproximation=b.useIntegerApproximation;break;case "inversionMode":switch(b){case "original":G="dontInvert";break;
case "invert":G="onlyInvert";break;case "both":G="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}})()
//# sourceMappingURL=qr-scanner-worker.min.js.map

File diff suppressed because one or more lines are too long

20
apps/qrcode/qr-scanner.umd.min.js vendored Normal file
View File

@ -0,0 +1,20 @@
'use strict';(function(d,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(d=d||self,d.QrScanner=a())})(this,function(){class d{static hasCamera(){return d.listCameras(!1).then(a=>!!a.length).catch(()=>!1)}static listCameras(a=!1){if(!navigator.mediaDevices)return Promise.resolve([]);let b=null;return(a?navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then(a=>b=a).catch(()=>{}):Promise.resolve()).then(()=>navigator.mediaDevices.enumerateDevices()).then(a=>
a.filter(a=>"videoinput"===a.kind).map((a,b)=>({id:a.deviceId,label:a.label||(0===b?"Default Camera":`Camera ${b+1}`)}))).finally(()=>{if(b)for(let a of b.getTracks())a.stop(),b.removeTrack(a)})}constructor(a,b,c=this._onDecodeError,f=this._calculateScanRegion,k="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=d.DEFAULT_CANVAS_SIZE;this._preferredCamera=k;this._flashOn=this._paused=this._active=!1;"number"===typeof c?(this._legacyCanvasSize=
c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._onDecodeError=c;"number"===typeof f?(this._legacyCanvasSize=f,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._calculateScanRegion=f;this._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);
a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let g=!1;a.hidden&&(a.hidden=!1,g=!0);document.body.contains(a)||(document.body.appendChild(a),g=!0);requestAnimationFrame(()=>{let b=window.getComputedStyle(a);"none"===b.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==b.visibility&&(a.style.setProperty("visibility","visible","important"),g=!0);g&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity=
0,a.style.width=0,a.style.height=0)});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=d.createQrEngine()}hasFlash(){let a=null;return(this.$video.srcObject?Promise.resolve(this.$video.srcObject.getVideoTracks()[0]):this._getCameraStream().then(({stream:b})=>{console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");
a=b;return b.getVideoTracks()[0]})).then(a=>"torch"in a.getSettings()).catch(()=>!1).finally(()=>{if(a)for(let b of a.getTracks())b.stop(),a.removeTrack(b)})}isFlashOn(){return this._flashOn}toggleFlash(){return this._flashOn?this.turnFlashOff():this.turnFlashOn()}turnFlashOn(){if(this._flashOn)return Promise.resolve();this._flashOn=!0;return!this._active||this._paused?Promise.resolve():this.hasFlash().then(a=>a?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]}):Promise.reject("No flash available")).catch(()=>
{this._flashOn=!1;throw e;})}turnFlashOff(){if(this._flashOn)return this._flashOn=!1,this._restartVideoStream()}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this.stop();d._postWorkerMessage(this._qrEnginePromise,"close")}start(){if(this._active&&!this._paused)return Promise.resolve();"https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https.");
this._active=!0;if(document.hidden)return Promise.resolve();this._paused=!1;return this.$video.srcObject?(this.$video.play(),Promise.resolve()):this._getCameraStream().then(({stream:a,facingMode:b})=>{this.$video.srcObject=a;this.$video.play();this._setVideoMirror(b);this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{}))}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(a=!1){this._paused=!0;if(!this._active)return Promise.resolve(!0);this.$video.pause();
let b=()=>{const a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const b of a)b.stop(),this.$video.srcObject.removeTrack(b);this.$video.srcObject=null};return a?(b(),Promise.resolve(!0)):(new Promise(a=>setTimeout(a,300))).then(()=>{if(!this._paused)return!1;b();return!0})}setCamera(a){if(a===this._preferredCamera)return Promise.resolve();this._preferredCamera=a;return this._restartVideoStream()}static scanImage(a,b=null,c=null,f=null,k=!1,g=!1){let h=c instanceof Worker,l=Promise.all([c||
d.createQrEngine(),d._loadImage(a)]).then(([a,g])=>{c=a;let l;[f,l]=this._drawToCanvas(g,b,f,k);return c instanceof Worker?(h||c.postMessage({type:"inversionMode",data:"both"}),new Promise((a,b)=>{let k,g,h;g=f=>{"qrResult"===f.data.type&&(c.removeEventListener("message",g),c.removeEventListener("error",h),clearTimeout(k),null!==f.data.data?a(f.data.data):b(d.NO_QR_CODE_FOUND))};h=a=>{c.removeEventListener("message",g);c.removeEventListener("error",h);clearTimeout(k);b("Scanner error: "+(a?a.message||
a:"Unknown Error"))};c.addEventListener("message",g);c.addEventListener("error",h);k=setTimeout(()=>h("timeout"),1E4);let m=l.getImageData(0,0,f.width,f.height);c.postMessage({type:"decode",data:m},[m.data.buffer])})):new Promise((a,b)=>{let k=setTimeout(()=>b("Scanner error: timeout"),1E4);c.detect(f).then(c=>{c.length?a(c[0].rawValue):b(d.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(k))})});b&&g&&(l=l.catch(()=>d.scanImage(a,null,c,f,k)));return l=l.finally(()=>
{h||d._postWorkerMessage(c,"close")})}setGrayscaleWeights(a,b,c,f=!0){d._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:c,useIntegerApproximation:f})}setInversionMode(a){d._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=d.WORKER_PATH){return("BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats?BarcodeDetector.getSupportedFormats():Promise.resolve([])).then(b=>-1!==b.indexOf("qr_code")?new BarcodeDetector({formats:["qr_code"]}):
new Worker(a))}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return{x:Math.round((a.videoWidth-b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,downScaledHeight:this._legacyCanvasSize}}_scanFrame(){if(!this._active||
this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{1>=this.$video.readyState?this._scanFrame():this._qrEnginePromise.then(a=>d.scanImage(this.$video,this._scanRegion,a,this.$canvas)).then(this._onDecode,a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=d.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==d.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){if(!navigator.mediaDevices)return Promise.reject("Camera not found.");
let a="environment"===this._preferredCamera||"user"===this._preferredCamera?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}];return[...b.map(b=>Object.assign({},b,{[a]:{exact:this._preferredCamera}})),...b].reduceRight((a,b)=>()=>navigator.mediaDevices.getUserMedia({video:b,audio:!1}).then(a=>({stream:a,facingMode:this._getFacingMode(a)||(b.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment")})).catch(a),()=>Promise.reject("Camera not found."))()}_restartVideoStream(){let a=
this._paused;return this.pause(!0).then(b=>{if(b&&!a&&this._active)return this.start()})}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b=null,c=null,f=!1){c=c||document.createElement("canvas");let d=b&&b.x?b.x:0,g=b&&b.y?b.y:0,h=b&&b.width?b.width:a.width||a.videoWidth,l=b&&b.height?b.height:
a.height||a.videoHeight;f||(f=b&&b.downScaledWidth?b.downScaledWidth:h,b=b&&b.downScaledHeight?b.downScaledHeight:l,c.width!==f&&(c.width=f),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,d,g,h,l,0,0,c.width,c.height);return[c,b]}static _loadImage(a){if(a instanceof HTMLCanvasElement||a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap||window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return Promise.resolve(a);
if(a instanceof Image)return d._awaitImageLoad(a).then(()=>a);if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a;return d._awaitImageLoad(b).then(()=>{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src);return b})}return Promise.reject("Unsupported image type.")}static _awaitImageLoad(a){return new Promise((b,c)=>{if(a.complete&&0!==a.naturalWidth)b();else{let f,d;f=()=>
{a.removeEventListener("load",f);a.removeEventListener("error",d);b()};d=()=>{a.removeEventListener("load",f);a.removeEventListener("error",d);c("Image load error")};a.addEventListener("load",f);a.addEventListener("error",d)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(a=>{a instanceof Worker&&a.postMessage({type:b,data:c})})}}d.DEFAULT_CANVAS_SIZE=400;d.NO_QR_CODE_FOUND="No QR code found";d.WORKER_PATH="qr-scanner-worker.min.js";return d})
//# sourceMappingURL=qr-scanner.umd.min.js.map

File diff suppressed because one or more lines are too long

7
apps/snaky/README.md Normal file
View File

@ -0,0 +1,7 @@
# Snaky
Eat apples and don't bite your tail.
## Controls
Use the touch screen, drag up, down, right or left.

1
apps/snaky/snaky-icon.js Normal file
View File

@ -0,0 +1 @@
E.toArrayBuffer(atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7u7u7u7AAAJmZmZmZmZmZkAAAAAAAAAu7u7u7u7uwAJmZmZmZmZmZkAAAAAAAALuyu7u7u7uwAJmZmZmZmZmZkAAAAAAAALIiIru7IiKwAJmZmZmZmZmZkAAAAAAAALISIru7IiK7AJmZmZmZmZmZkAAAAAAAALsiK7u7IjK7AJmZmZmZmZmZkAAAAAAAALu7u7u7u7u7AJmZmZmZmZmZkAAAAAAAALu7u7u7u7u7AAAAAAAJmZmZkAAAAAAAALsru7u7siu7AAAAAAAAmZmZkAAAAAAAALuxEiIiIruwAAAAAAAJmZmZkAAAAAAAAAu7IiIiu7uwAAAAAAAJmZmZkAAAAAAAAAu7u7u7u7sAAAAAAAAJmZmZkAAAAAAAAAALuqqqqwAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAAmZmZkAAAAAAAAAAAmZmZmQAAAAAAAAAJmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAmZmZmZmZmZmZmZmZmZmZkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="))

185
apps/snaky/snaky.js Normal file
View File

@ -0,0 +1,185 @@
//Bangle.setLCDMode("176x176");
Bangle.setLCDTimeout(0);
const H = g.getWidth();
const W = g.getHeight();
let running = true;
let score = 0;
let d;
const gridSize = 29;
const tileSize = 6;
let nextX = 0;
let nextY = 0;
const defaultTailSize = 3;
let tailSize = defaultTailSize;
const snakeTrail = [];
const snake = { x: 10, y: 10 };
const apple = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) };
function drawBackground(){
g.setColor("#000000");
g.fillRect(0, 0, H, W);
}
function drawBackgroundSuccess(){
g.setColor("#00FFFF");
g.fillRect(0, 0, H, W);
}
function drawApple(){
g.setColor("#FF0000");
g.fillCircle((apple.x * tileSize) + tileSize/2, (apple.y * tileSize) + tileSize/2, tileSize/2);
}
function drawSnake(){
g.setColor("#008000");
for (let i = 0; i < snakeTrail.length; i++) {
g.fillRect(snakeTrail[i].x * tileSize, snakeTrail[i].y * tileSize, snakeTrail[i].x * tileSize + tileSize, snakeTrail[i].y * tileSize + tileSize);
//snake bites it's tail
if (snakeTrail[i].x === snake.x && snakeTrail[i].y === snake.y && tailSize > defaultTailSize) {
Bangle.buzz(1000);
gameOver();
}
}
g.setColor("#FFFFFF");
g.fillRect(snake.x*tileSize, snake.y*tileSize, snake.x*tileSize+ tileSize, snake.y*tileSize + tileSize);
g.setColor("#0000ff");
g.fillRect((snake.x*tileSize)+1, (snake.y*tileSize)+2, (snake.x*tileSize)+2, (snake.y*tileSize)+4);
g.setColor("#0000ff");
g.fillRect((snake.x*tileSize)+tileSize-1, (snake.y*tileSize)+2, (snake.x*tileSize)+tileSize-2, (snake.y*tileSize)+4);
}
function drawScore(){
g.setColor("#555555");
g.setFont("Vector20");
g.setFontAlign(0, 0);
g.drawString("Score:" + score, W / 2, 10);
}
function gameStart() {
running = true;
score = 0;
}
function gameOver() {
g.clear();
g.setColor("#000000");
g.setFont("Vector12");
g.drawString("GAME OVER!", W / 2, H / 2 - 20);
g.drawString("Score: " + score, W / 2, H / 2 - 10);
g.drawString("Tap to Restart", W / 2, H / 2 + 10);
running = false;
tailSize = defaultTailSize;
}
function draw() {
if (!running) {
return;
}
g.clear();
// move snake in next pos
snake.x += nextX;
snake.y += nextY;
// snake over game world
if (snake.x < 0) {
snake.x = gridSize - 1;
}
if (snake.x > gridSize - 1) {
snake.x = 0;
}
if (snake.y < 0) {
snake.y = gridSize - 1;
}
if (snake.y > gridSize - 1) {
snake.y = 0;
}
//snake bite apple
if (snake.x === apple.x && snake.y === apple.y) {
Bangle.beep(20);
drawBackgroundSuccess();
tailSize++;
score++;
apple.x = Math.floor(Math.random() * gridSize);
apple.y = Math.floor(Math.random() * gridSize);
drawApple();
}
drawBackground();
drawApple();
drawSnake();
drawScore();
//set snake trail
snakeTrail.push({ x: snake.x, y: snake.y });
while (snakeTrail.length > tailSize) {
snakeTrail.shift();
}
g.flip();
}
let dDiff = 10;
Bangle.on('drag', function(a) {
if (a.dx > dDiff ) { // right
if (d !== 'l')
{
nextX = 1;
nextY = 0;
d = 'r';
}
}
if (a.dx < -dDiff ) { // left
if (d !== 'r')
{
nextX = -1;
nextY = 0;
d = 'l';
}
}
if (a.dy < -dDiff) { // Up
if (d !== 'd') {
nextX = 0;
nextY = -1;
d = 'u';
}
}
if (a.dy > dDiff) { // Down
if (d !== 'u')
{
nextX = 0;
nextY = 1;
d = 'd';
}
}
});
Bangle.on('touch', button => {
if (!running) {
gameStart();
}
});
// render X times per second
const x = 5;
setInterval(draw, 1000 / x);

BIN
apps/snaky/snaky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

View File

@ -1,2 +1,3 @@
0.1: New watch face 0.1: New watch face
0.2: Use Bangle.setUI for button/launcher handling 0.2: Use Bangle.setUI for button/launcher handling
0.3: Bangle.js 2 support

View File

@ -5,9 +5,10 @@ function padNum(n, l) {
return ("0".repeat(l)+n).substr(-l); return ("0".repeat(l)+n).substr(-l);
} }
let rects = {}; var rects = {};
let rectsToClear = {}; var rectsToClear = {};
let commands = []; var commands = [];
var showSeconds = true;
function pushCommand(command) { function pushCommand(command) {
let hash = E.CRC32(E.toJS(arguments)); let hash = E.CRC32(E.toJS(arguments));
@ -20,17 +21,20 @@ function executeCommands() {
"ram"; "ram";
for (let hash in rectsToClear) delete rects[hash]; for (let hash in rectsToClear) delete rects[hash];
for (let r of rectsToClear) if (r) g.clearRect(r.x1, r.y1, r.x2, r.y2); for (let r of rectsToClear) if (r) g.clearRect(r.x1, r.y1, r.x2, r.y2);
g.getModified(true); for (let c of commands) rects[c.hash] = c.command();
for (let c of commands) {
c.command();
rects[c.hash] = g.getModified(true);
}
rectsToClear = Object.assign({}, rects); rectsToClear = Object.assign({}, rects);
commands = []; commands = [];
} }
function drawVectorText(text, size, x, y, alignX, alignY) { function drawVectorText(text, size, x, y, alignX, alignY) {
g.setFont("Vector", size).setFontAlign(alignX, alignY).drawString(text, x, y); g.setFont("Vector", size).setFontAlign(alignX, alignY).drawString(text, x, y);
var m = g.stringMetrics(text);
return {
x1: x - m.width * (alignX / 2 + 0.5),
y1: y - m.height * (alignY / 2 + 0.5),
x2: x - m.width * (alignX / 2 - 0.5),
y2: y - m.height * (alignY / 2 - 0.5)
};
} }
function draw() { function draw() {
@ -43,11 +47,12 @@ function draw() {
let secondsText = padNum(d.getSeconds(), 2); let secondsText = padNum(d.getSeconds(), 2);
let dowText = locale.dow(d); let dowText = locale.dow(d);
let dateText = locale.date(d, true); let dateText = locale.date(d, true);
let width = g.getWidth() - 2;
g.setFont("Vector", 256); g.setFont("Vector", 256);
let timeFontSize = g.getWidth() / ((g.stringWidth(timeText) / 256) + (Math.max(g.stringWidth(meridian), g.stringWidth(secondsText)) / 512 * 9 / 10)); let timeFontSize = width / ((g.stringWidth(timeText) / 256) + (Math.max(g.stringWidth(meridian), g.stringWidth(secondsText)) / 512 * 9 / 10));
let dowFontSize = g.getWidth() / (g.stringWidth(dowText) / 256); let dowFontSize = width / (g.stringWidth(dowText) / 256);
let dateFontSize = g.getWidth() / (g.stringWidth(dateText) / 256); let dateFontSize = width / (g.stringWidth(dateText) / 256);
let timeHeight = g.setFont("Vector", timeFontSize).getFontHeight() * 9 / 10; let timeHeight = g.setFont("Vector", timeFontSize).getFontHeight() * 9 / 10;
let dowHeight = g.setFont("Vector", dowFontSize).getFontHeight(); let dowHeight = g.setFont("Vector", dowFontSize).getFontHeight();
@ -56,26 +61,28 @@ function draw() {
let remainingHeight = g.getHeight() - 24 - timeHeight - dowHeight - dateHeight; let remainingHeight = g.getHeight() - 24 - timeHeight - dowHeight - dateHeight;
let spacer = remainingHeight / 4; let spacer = remainingHeight / 4;
let x = 2;
let y = 24 + spacer; let y = 24 + spacer;
pushCommand(drawVectorText, timeText, timeFontSize, 0, y, -1, -1); pushCommand(drawVectorText, timeText, timeFontSize, x, y, -1, -1);
pushCommand(drawVectorText, meridian, timeFontSize*9/20, g.getWidth(), y, 1, -1); pushCommand(drawVectorText, meridian, timeFontSize*9/20, x + width, y, 1, -1);
pushCommand(drawVectorText, secondsText, timeFontSize*9/20, g.getWidth(), y + timeHeight, 1, 1); if (showSeconds) pushCommand(drawVectorText, secondsText, timeFontSize*9/20, x + width, y + timeHeight, 1, 1);
y += timeHeight + spacer; y += timeHeight + spacer;
pushCommand(drawVectorText, dowText, dowFontSize, g.getWidth()/2, y, 0, -1); pushCommand(drawVectorText, dowText, dowFontSize, x + width/2, y, 0, -1);
y += dowHeight + spacer; y += dowHeight + spacer;
pushCommand(drawVectorText, dateText, dateFontSize, g.getWidth()/2, y, 0, -1); pushCommand(drawVectorText, dateText, dateFontSize, x + width/2, y, 0, -1);
executeCommands(); executeCommands();
} }
let timeout; var timeout;
function tick() { function tick() {
draw(); draw();
timeout = setTimeout(tick, 1000 - getTime() % 1 * 1000); var period = showSeconds ? 1000 : 60 * 1000;
timeout = setTimeout(tick, period - getTime() * 1000 % period);
} }
Bangle.on('lcdPower', function(on) { Bangle.on('lcdPower', function(on) {
@ -84,6 +91,13 @@ Bangle.on('lcdPower', function(on) {
if (on) tick(); if (on) tick();
}); });
Bangle.on('lock', function(locked) {
if (timeout) clearTimeout(timeout);
timeout = null;
showSeconds = !locked;
tick();
});
g.clear(); g.clear();
tick(); tick();
Bangle.loadWidgets(); Bangle.loadWidgets();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -9,3 +9,4 @@
0.10: Use new Layout library 0.10: Use new Layout library
0.11: Bangle.js 2 support 0.11: Bangle.js 2 support
0.12: Allow hiding the widget 0.12: Allow hiding the widget
0.13: Tweak Bangle.js 2 light theme colors

View File

@ -54,14 +54,62 @@ exports.get = function() {
scheduleExpiry(storage.readJSON('weather.json')||{}); scheduleExpiry(storage.readJSON('weather.json')||{});
exports.drawIcon = function(cond, x, y, r) { exports.drawIcon = function(cond, x, y, r) {
var palette;
if (B2) {
if (g.theme.dark) {
palette = {
sun: '#FF0',
cloud: '#FFF',
bgCloud: '#777', // dithers on B2, but that's ok
rain: '#0FF',
lightning: '#FF0',
snow: '#FFF',
mist: '#FFF'
};
} else {
palette = {
sun: '#FF0',
cloud: '#777', // dithers on B2, but that's ok
bgCloud: '#000',
rain: '#00F',
lightning: '#FF0',
snow: '#0FF',
mist: '#0FF'
};
}
} else {
if (g.theme.dark) {
palette = {
sun: '#FE0',
cloud: '#BBB',
bgCloud: '#777',
rain: '#0CF',
lightning: '#FE0',
snow: '#FFF',
mist: '#FFF'
};
} else {
palette = {
sun: '#FC0',
cloud: '#000',
bgCloud: '#777',
rain: '#07F',
lightning: '#FC0',
snow: '#CCC',
mist: '#CCC'
};
}
}
function drawSun(x, y, r) { function drawSun(x, y, r) {
g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0")); g.setColor(palette.sun);
g.fillCircle(x, y, r); g.fillCircle(x, y, r);
} }
function drawCloud(x, y, r, c) { function drawCloud(x, y, r, c) {
const u = r/12; const u = r/12;
if (c==null) c = B2 ? '#FFF': (g.theme.dark ? "#BBB" : "#AAA"); if (c==null) c = palette.cloud;
g.setColor(c); g.setColor(c);
g.fillCircle(x-8*u, y+3*u, 4*u); g.fillCircle(x-8*u, y+3*u, 4*u);
g.fillCircle(x-4*u, y-2*u, 5*u); g.fillCircle(x-4*u, y-2*u, 5*u);
@ -78,7 +126,7 @@ exports.drawIcon = function(cond, x, y, r) {
} }
function drawBrokenClouds(x, y, r) { function drawBrokenClouds(x, y, r) {
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777"); // dithers on B2, but that's ok drawCloud(x+1/8*r, y-1/8*r, 7/8*r, palette.bgCloud);
drawCloud(x-1/8*r, y+1/8*r, 7/8*r); drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
} }
@ -88,7 +136,7 @@ exports.drawIcon = function(cond, x, y, r) {
} }
function drawRainLines(x, y, r) { function drawRainLines(x, y, r) {
g.setColor(B2 ? '#0FF' : (g.theme.dark ? "#0CF" : "#07F")); g.setColor(palette.rain);
const y1 = y+1/2*r; const y1 = y+1/2*r;
const y2 = y+1*r; const y2 = y+1*r;
const poly = g.fillPolyAA ? p => g.fillPolyAA(p) : p => g.fillPoly(p); const poly = g.fillPolyAA ? p => g.fillPolyAA(p) : p => g.fillPoly(p);
@ -124,7 +172,7 @@ exports.drawIcon = function(cond, x, y, r) {
function drawThunderstorm(x, y, r) { function drawThunderstorm(x, y, r) {
function drawLightning(x, y, r) { function drawLightning(x, y, r) {
g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0")); g.setColor(palette.lightning);
g.fillPoly([ g.fillPoly([
x-2/6*r, y-r, x-2/6*r, y-r,
x-4/6*r, y+1/6*r, x-4/6*r, y+1/6*r,
@ -152,7 +200,7 @@ exports.drawIcon = function(cond, x, y, r) {
} }
} }
g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC")); g.setColor(palette.snow);
const w = 1/12*r; const w = 1/12*r;
for(let i = 0; i<=6; ++i) { for(let i = 0; i<=6; ++i) {
const points = [ const points = [
@ -187,7 +235,7 @@ exports.drawIcon = function(cond, x, y, r) {
[-0.2, 0.3], [-0.2, 0.3],
]; ];
g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC")); g.setColor(palette.mist);
for(let i = 0; i<5; ++i) { for(let i = 0; i<5; ++i) {
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r, g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
y+(0.4*i-0.7)*r-1); y+(0.4*i-0.7)*r-1);
@ -197,7 +245,7 @@ exports.drawIcon = function(cond, x, y, r) {
} }
function drawUnknown(x, y, r) { function drawUnknown(x, y, r) {
drawCloud(x, y, r, "#777"); // dithers on B2, but that's ok drawCloud(x, y, r, palette.bgCloud);
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6); g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
} }

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Minor layout format tweak so it uses less memory and draws ok on Bangle.js 1 (#1012) 0.02: Minor layout format tweak so it uses less memory and draws ok on Bangle.js 1 (#1012)
0.03: Minor layout extra spaces. 0.03: Minor layout extra spaces.
0.04: Layout now compatible with Bangle.js 2

View File

@ -73,12 +73,10 @@ var clockLayout = new Layout( {
{type: "img", filly: 1, id: "weatherIcon", src: sunIcon}, {type: "img", filly: 1, id: "weatherIcon", src: sunIcon},
{type: "v", fillx:1, c: [ {type: "v", fillx:1, c: [
{type: "h", c: [ {type: "h", c: [
{type: "txt", font: "10%", id: "temp", label: "000"}, {type: "txt", font: "10%", id: "temp", label: "000 °C"},
{type: "txt", font: "10%", id: "tempUnit", label: "°C"},
]}, ]},
{type: "h", c: [ {type: "h", c: [
{type: "txt", font: "10%", id: "wind", label: "00"}, {type: "txt", font: "10%", id: "wind", label: "00 km/h"},
{type: "txt", font: "10%", id: "windUnit", label: "km/h"},
]} ]}
] ]
}, },
@ -106,18 +104,14 @@ function draw() {
if(weatherJson && weatherJson.weather){ if(weatherJson && weatherJson.weather){
var currentWeather = weatherJson.weather; var currentWeather = weatherJson.weather;
const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/); const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
clockLayout.temp.label = temp[1]; clockLayout.temp.label = temp[1] + " " + temp[2];
clockLayout.tempUnit.label = temp[2];
clockLayout.weatherIcon.src = chooseIcon(currentWeather.txt); clockLayout.weatherIcon.src = chooseIcon(currentWeather.txt);
const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/); const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/);
clockLayout.wind.label = wind[1] + " ".repeat(wind[2].length-1); clockLayout.wind.label = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase();
clockLayout.windUnit.label = wind[2] + " " + (currentWeather.wrose||'').toUpperCase();
} }
else{ else{
clockLayout.temp.label = "Err"; clockLayout.temp.label = "Err";
clockLayout.tempUnit.label = "";
clockLayout.wind.label = "No Data"; clockLayout.wind.label = "No Data";
clockLayout.windUnit.label = "";
clockLayout.weatherIcon.src = errIcon; clockLayout.weatherIcon.src = errIcon;
} }
clockLayout.clear(); clockLayout.clear();

View File

@ -1,2 +1,3 @@
1.00: Release for Bangle 2 (2021/11/18) 1.00: Release for Bangle 2 (2021/11/18)
1.01: Internal id update to wid_* as per Gordon's request (2021/11/21) 1.01: Internal id update to wid_* as per Gordon's request (2021/11/21)
1.02: Support dark themes

View File

@ -1,9 +1,9 @@
(function(){ (function(){
let COLORS = { let COLORS = {
'white': "#fff", 'white': g.theme.dark ? "#000" : "#fff",
'black': "#000", 'black': g.theme.dark ? "#fff" : "#000",
'charging': "#08f", 'charging': "#08f",
'high': "#000", 'high': g.theme.dark ? "#fff" : "#000",
'low': "#f00", 'low': "#f00",
}; };