Merge branch 'espruino:master' into master
|
@ -1,2 +1,4 @@
|
|||
apps/animclk/V29.LBM.js
|
||||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
|
|
19
README.md
|
@ -377,40 +377,41 @@ that handles configuring the app.
|
|||
When the app settings are opened, this function is called with one
|
||||
argument, `back`: a callback to return to the settings menu.
|
||||
|
||||
Usually it will save any information in `app.json` where `app` is the name
|
||||
Usually it will save any information in `myappid.json` where `myappid` is the name
|
||||
of your app - so you should change the example accordingly.
|
||||
|
||||
Example `settings.js`
|
||||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
||||
let settings = require('Storage').readJSON('myappid.json',1)||{};
|
||||
if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.json',settings);
|
||||
require('Storage').write('myappid.json', settings);
|
||||
}
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: settings.monkeys||12,
|
||||
value: settings.monkeys,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
```json
|
||||
{ "id": "app",
|
||||
{ "id": "myappid",
|
||||
...
|
||||
"storage": [
|
||||
...
|
||||
{"name":"app.settings.js","url":"settings.js"},
|
||||
{"name":"myappid.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
{"name":"myappid.json"}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
|
|
@ -152,6 +152,8 @@
|
|||
"no-prototype-builtins": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-unreachable": "warn",
|
||||
"no-cond-assign": "warn",
|
||||
"no-useless-catch": "warn",
|
||||
// TODO: "no-undef": "warn",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
|
|
After Width: | Height: | Size: 650 B |
|
@ -0,0 +1,4 @@
|
|||
0.01: Initial version for upload
|
||||
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
|
||||
0.03: Code style cleanup
|
||||
0.04: Set 00:00 to 12:00 for 12 hour time
|
|
@ -0,0 +1,12 @@
|
|||
# 93 Dub
|
||||
|
||||

|
||||
|
||||
Uses many portions from Espruino documentation, example watchfaces, and the waveclk app. It also sourced from Jon Barlow's 91 Dub v2.0 source code and resources and adapted for Bangle.js 2's screen. Time, date and the battery display works. It is not pixel perfect to the original.
|
||||
|
||||
Contributors:
|
||||
Leer10
|
||||
Orviwan (original watchface and assets)
|
||||
Gordon Williams (Bangle.js, watchapps for reference code and documentation)
|
||||
DiscoMinotaur (adjustments)
|
||||
Ray Holder (minor 12 hour time rendering adjustment)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBG2XwAgcPC6P/h//AAIDBA4Pwh/w+AGBAgIDBC4oVDAAITBCAIIBAYIBBAgIvHh4YCFgQPBAoIvCCwoAWIQYAQGLgAWI6bQVdQiiDOyAX/C/7+IAIYvSh4RBAYIXLAwJAHC6ZFCF5yn/C7wDBBAJ3EVAKBDC5QLBYAoLFC5nwCgoXlL44vSL653sL4QXBL6DvXC9YCBACIXCZ4YAQFaYAgPAhqCa4SDFLoZpICYIXDQKLyCDIQXVAAKI0AAYA=="))
|
|
@ -0,0 +1,140 @@
|
|||
// get 12 hour status, code from barclock
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
|
||||
// define background
|
||||
var imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
|
||||
|
||||
// define fonts
|
||||
// reg number first char 48 28 by 41
|
||||
var fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
// tiny font for percentage first char 48 6 by 8
|
||||
var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
|
||||
// date font first char 48 12 by 15
|
||||
var fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
|
||||
// define days of the week images
|
||||
var imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
|
||||
var imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
|
||||
var imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
|
||||
var imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
|
||||
var imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
|
||||
var imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
|
||||
var imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
|
||||
|
||||
|
||||
|
||||
// define icons
|
||||
var imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
|
||||
var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
|
||||
var img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
|
||||
var imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
|
||||
|
||||
//vars
|
||||
var separator = true;
|
||||
var is24hr = !is12Hour;
|
||||
var leadingZero = true;
|
||||
|
||||
//the following 2 sections are used from waveclk to schedule minutely updates
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
g.clear();
|
||||
g.drawImage(imgBg,0,0);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
function draw(){
|
||||
drawBackground();
|
||||
var date = new Date();
|
||||
var h = date.getHours(), m = date.getMinutes();
|
||||
var d = date.getDate(), w = date.getDay();
|
||||
g.reset();
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
|
||||
//draw 24 hr indicator and 12 hr specific behavior
|
||||
if (is24hr){
|
||||
g.drawImage(img24hr,32, 65);
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
}
|
||||
} else if (h > 12) {
|
||||
g.drawImage(imgPM,40, 70);
|
||||
h = h - 12;
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
} else if (h === 0) {
|
||||
// display 12:00 instead of 00:00 for 12 hr mode
|
||||
h = "12";
|
||||
}
|
||||
|
||||
//draw separator
|
||||
if (separator){
|
||||
g.drawImage(imgSep, 85,98);}
|
||||
|
||||
//draw day of week
|
||||
var imgW = null;
|
||||
if (w == 0) {imgW = imgSun;}
|
||||
if (w == 1) {imgW = imgMon;}
|
||||
if (w == 2) {imgW = imgTue;}
|
||||
if (w == 3) {imgW = imgWed;}
|
||||
if (w == 4) {imgW = imgThr;}
|
||||
if (w == 5) {imgW = imgFri;}
|
||||
if (w == 6) {imgW = imgSat;}
|
||||
g.drawImage(imgW, 85, 63);
|
||||
|
||||
|
||||
// draw nums
|
||||
// draw time
|
||||
g.setColor(0,0,0);
|
||||
g.setBgColor(1,1,1);
|
||||
g.setFontCustom(fontNum, 48, 28, 41);
|
||||
if (h<10) {
|
||||
if (leadingZero) {
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
}
|
||||
g.drawString(h, 25, 90, true);
|
||||
g.drawString(("0"+m).substr(-2), 92, 90, true);
|
||||
// draw date
|
||||
g.setFontCustom(fontDate, 48, 12, 15);
|
||||
g.drawString(("0"+d).substr(-2), 123,63, true);
|
||||
|
||||
// widget redraw
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
draw();
|
||||
|
||||
//the following section is also from waveclk
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -1 +0,0 @@
|
|||
2021/11/18 | 1.0: Release for Bangle 2
|
|
@ -0,0 +1 @@
|
|||
0.01: Beta version for Bangle 2 (2021/11/28)
|
|
@ -0,0 +1,15 @@
|
|||
# A Clock with Timer, Map and Time Zones
|
||||
|
||||
* Works with Bangle 2
|
||||
* Timer
|
||||
* Right tap: start/increase by 10 minutes; Left tap: decrease by 5 minutes
|
||||
* Short buzz at T-30, T-20, T-10 ; Double buzz at T
|
||||
* Other time zones
|
||||
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
|
||||
* World Map
|
||||
* The yellow line shows the position of the sun
|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP/AAnAnEH4Ef+eAiEDAoPDz+T/ff/+T3+T/VAj8z/0f4VP51zDoX/5Hzz/z//f5EBAoP+r4FBFIgPBAAP4v5AFABPvrwSB0YFBrtX/+nCI3u/+vhFhh/q/f/9Fhu4NB187v3n/fvCIf/CIIAFRIUB8EAg3QgJmB4H/iAEB//+/lggqUC//wi4FB8AHBj4FB+H/wEzBgPg/0AkE3BIP8gE8n4VBGIN/IAPAsEA//8v6OBAoUjgEIAoPwkMATIN//BQBgfgg/wAoMH/EHEwILB/gNBgFgAocByEB/ED9AoCAoPAgE4gHwgeAgOYgAVBAoMYAoKECAoIVBAoIfBoCRCAAw="))
|
|
@ -0,0 +1,129 @@
|
|||
// assets
|
||||
function getImg() {
|
||||
return require("heatshrink").decompress(atob("2FRgP/ABnxBRP5BJH+gEfBZHghnAv4JFmA+Bj0PBIn3//4h3An4oDAQJWEEIf8AwMEuFOCofAh/QjAWEg4VEwEAnw2DDoKEHEYPwAoUBmgrDhgUHS4XgAwUD/gVC/g+FAAZgEwEf4YqC/EQFQ4NDFgV/4Z3C/EcCo1974VCLAV/V4d7Co9/Co0PCoX+vk4Ko/HCosCRYX5nwTFkEAr/nCokICoL+B/aCGCoMHCoq3EdoraGCosPz4HBcILEJCocBwEHOwQrIgQrHgoHCFYMEgwVJYoMBsEnCofAnkMNQJXH4D4EbQMPkF/xwrEj+/HIkAoAVDj8QueHCoorDCoUDLwd96J0BKwgrHh4VDv+9CosDx6QCCo4HB//8VwvvXgQVDJIYSBCo/sBwaZBgF/NoYVHgH8V4qYDAwUYlAVFEYbFDDgwAGConogf9Zg8DCpP4cIh0Dg0BGAgVE+gVIgUA+AVI+wVE/xAEh5HDEgn+CpEAbgJCCHQoVBn4VJ/ED4ANDAAQVJ4EPPQPAt4VF4BeDColgj/8h/gFYwJBCpF//k//ANDCAYVIcgP+CpH/54VHCAIVB/4VIwYECCocIAwIVBx4VG9+AMITbCYAYJB34VG/UAj4VI7/9Cgw9CJYXAmBtDMAQsIfYhvCCofyvywGB4QFFgYGC/d+agYVLSgf8+ArG/APBD4QVBgh0CAwNwv/fCo4PCCo94s7VDCohnDAoI7Enlv8BZECoRCDAggAB3/3/gzDMAIVFY4IVE4IPBOoZ9DCpXwCoMvCqKxB//3bYywD4BtFAAPfDooVFFYIVGw4VFB4KZFngNE/uPCovgFYgEBuK+Fg4zFCoIrFCovwgQVF+AVFgPxEYzFEbgQVD4EDCoozBYogVCgYVE8bpGCo4HDCoPzBgoVIL4fAg4MGgAIHCofgCszND8BOHK4x2BCofwXgv4h6vGCps/Co6uDAA/7RgIjDDwTaDABPA//9FaAtDCop0FC5YVDLwoAH8//94GD/wVNCYKNECpwPBQggVPNggVBNp4VFFZwAGCquHCqnzCB4"));
|
||||
}
|
||||
var IMAGEWIDTH = 176;
|
||||
var IMAGEHEIGHT = 81;
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontMichroma16 = function(scale) {
|
||||
g.setFontCustom(atob("AAAAGAAYAAAAGAB4A/APwD4AeADgAAAAAAA/8H/4YBjAGMAcwBzAHMAcwBzAHMAYYBh/+D/wAAAAABgAOABwAGAA//h/+AAAAAA4+Hn4YZjhmMOYw5jDmMMYwxjDGOMYYxh/GD4YAAAAADBwcHhgGOAYwBzHHMccxxzHHMcc5xhnGH/4PfAAAAAAAOAB4APgB2AGYAxgHGA4YDBgYGD/+P/4AOAAYAAAAAD+cP547BjsGOwc7BzsHOwc7BzsHOwY7zjv+APgAAAAAD/wf/hmGOYYxhzGHMYcxhzGHOYYZhh3uDP4AeAAAEAA4ADgAOAI4DjgeODw4eDjgOcA7gD8APgA8AAAAAAAAAA58H/4bxjmGMYcxhzGHMYcxhzGHOYYbxh/+DnwAAAAADxgfnBnOOMYwxjDHMMcwxzDHMMY4xhjOH/4P/AAAAAABnAGcAAA"), 46, atob("BAgQCBAQEBAQEBAQBA=="), 16+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
// timer
|
||||
var timervalue = 0;
|
||||
var istimeron = false;
|
||||
var timertick;
|
||||
|
||||
Bangle.on('touch',t=>{
|
||||
if (t == 1) {
|
||||
Bangle.buzz(30);
|
||||
if (timervalue < 5*60) { timervalue = 1 ; }
|
||||
else { timervalue -= 5*60; }
|
||||
}
|
||||
else if (t == 2) {
|
||||
Bangle.buzz(30);
|
||||
if (!istimeron) {
|
||||
istimeron = true;
|
||||
timertick = setInterval(countDown, 1000);
|
||||
}
|
||||
timervalue += 60*10;
|
||||
}
|
||||
});
|
||||
|
||||
function timeToString(duration) {
|
||||
var hrs = ~~(duration / 3600);
|
||||
var mins = ~~((duration % 3600) / 60);
|
||||
var secs = ~~duration % 60;
|
||||
var ret = "";
|
||||
if (hrs > 0) {
|
||||
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||
}
|
||||
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||
ret += "" + secs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
timervalue--;
|
||||
|
||||
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.setFont("6x8").drawString("Timer", 44, g.getHeight()/2-20);
|
||||
g.setFont("Michroma16").drawString(timeToString(timervalue), 44, g.getHeight()/2-10);
|
||||
|
||||
if (timervalue <= 0) {
|
||||
istimeron = false;
|
||||
clearInterval(timertick);
|
||||
|
||||
Bangle.buzz().then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 500));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(1000);
|
||||
});
|
||||
}
|
||||
else
|
||||
if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); }
|
||||
}
|
||||
|
||||
function showWelcomeMessage() {
|
||||
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||
g.setFontAlign(0, 0).setFont("6x8");
|
||||
g.drawString("Touch right to", 44, 80);
|
||||
g.drawString("start timer", 44, 88);
|
||||
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
||||
}
|
||||
|
||||
// time
|
||||
var drawTimeout;
|
||||
|
||||
function getGmt() {
|
||||
var d = new Date();
|
||||
var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
|
||||
return gmt;
|
||||
}
|
||||
|
||||
function getTimeFromTimezone(offset) {
|
||||
return new Date(getGmt().getTime() + offset * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function queueNextDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||
|
||||
var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
|
||||
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
|
||||
g.reset();
|
||||
|
||||
var locale = require("locale");
|
||||
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
|
||||
g.setFont("6x8");
|
||||
g.drawString(locale.date(new Date(),1), 125, 68);
|
||||
g.drawString("PAR "+locale.time(getTimeFromTimezone(1),1), 125, 80);
|
||||
g.drawString("TYO "+locale.time(getTimeFromTimezone(9),1), 125, 88);
|
||||
|
||||
queueNextDraw();
|
||||
}
|
||||
|
||||
// init
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||
draw();
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
showWelcomeMessage();
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.1 KiB |
|
@ -0,0 +1,2 @@
|
|||
1.00: Release (2021/12/01)
|
||||
1.01: Grey font when timer is frozen (2021/12/04)
|
|
@ -0,0 +1,16 @@
|
|||
# A Speech Timer
|
||||
|
||||
* A timer designed to help keeping your speeches and presentations to time
|
||||
* Vibrates 1-2-3 times and changes screen color within the target time range.
|
||||
* Example for a 5 to 7 minutes speech: vibrates once at 5:00 (green), twice at 6:00 (yellow), thrice at 7:00 (red).
|
||||
* Use the buttons to start a timer
|
||||
* Swipe left or right to choose different target times
|
||||
* Touching the timer on the upper part of the screen locks (or unlocks) the buttons to prevent accidental changes
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP//kAj//AAP5/+PApH7//PAonvAoXzAonj//nApHggEHAoWAgA5BAAJCCAoU/IYIFCv///w0CAonrv/HAoXLv+DAogLFgPeAoV+nlOAoV4/8+AoV79+eFIVzAof7u/v5xBCs4FL84FE//O74FBu4FB64FD73TAoNz/+eAoV5IIIFCvl8vwFCv8A/wFDO4IFFFIQFCGoSVFUIqtDh65D/1vYof+Y4LLDw7dD/0ndIYRCeoQFC/P/z/+i///oFBGoX8gEfAgI="))
|
|
@ -0,0 +1,173 @@
|
|||
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));
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontMichroma16 = function(scale) {
|
||||
g.setFontCustom(atob("AAAAGAAYAAAAGAB4A/APwD4AeADgAAAAAAA/8H/4YBjAGMAcwBzAHMAcwBzAHMAYYBh/+D/wAAAAABgAOABwAGAA//h/+AAAAAA4+Hn4YZjhmMOYw5jDmMMYwxjDGOMYYxh/GD4YAAAAADBwcHhgGOAYwBzHHMccxxzHHMcc5xhnGH/4PfAAAAAAAOAB4APgB2AGYAxgHGA4YDBgYGD/+P/4AOAAYAAAAAD+cP547BjsGOwc7BzsHOwc7BzsHOwY7zjv+APgAAAAAD/wf/hmGOYYxhzGHMYcxhzGHOYYZhh3uDP4AeAAAEAA4ADgAOAI4DjgeODw4eDjgOcA7gD8APgA8AAAAAAAAAA58H/4bxjmGMYcxhzGHMYcxhzGHOYYbxh/+DnwAAAAADxgfnBnOOMYwxjDHMMcwxzDHMMY4xhjOH/4P/AAAAAABnAGcAAA"), 46, atob("BAgQCBAQEBAQEBAQBA=="), 16+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
function timeToString(duration) {
|
||||
var hrs = ~~(duration / 3600);
|
||||
var mins = ~~((duration % 3600) / 60);
|
||||
var secs = ~~duration % 60;
|
||||
var ret = "";
|
||||
if (hrs > 0) {
|
||||
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||
}
|
||||
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||
ret += "" + secs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var newtimer_left_from = 60;
|
||||
var newtimer_left_to = 2*60;
|
||||
|
||||
var newtimer_right_from = 5*60;
|
||||
var newtimer_right_to = 7*60;
|
||||
|
||||
var current_from = 5*60;
|
||||
var current_mid = 6*60;
|
||||
var current_to = 7*60;
|
||||
var current_value = 0;
|
||||
|
||||
var timerinterval;
|
||||
var istimeron = false;
|
||||
|
||||
var islocked = false;
|
||||
|
||||
function countDown() {
|
||||
current_value++;
|
||||
draw();
|
||||
|
||||
if (current_value == current_from) {
|
||||
Bangle.buzz(500);
|
||||
} else if (current_value == current_mid) {
|
||||
Bangle.buzz(400).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 800));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(500);
|
||||
});
|
||||
} else if (current_value == current_to) {
|
||||
Bangle.buzz(300).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 600));
|
||||
}).then(()=>{
|
||||
Bangle.buzz(300).then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 600));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(500);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Bangle.on('touch',(touchside, touchdata)=>{
|
||||
if (!islocked && istimeron && touchdata.y > (100+10)) {
|
||||
Bangle.buzz(40);
|
||||
istimeron = false;
|
||||
clearInterval(timerinterval);
|
||||
} else if (touchdata.y > 24 && touchdata.y < (100-10)) {
|
||||
Bangle.buzz(40);
|
||||
islocked = !islocked;
|
||||
} else if (!islocked && touchdata.y > (100+10) && touchdata.x > 88 + 10) {
|
||||
Bangle.buzz(40);
|
||||
current_from = newtimer_right_from;
|
||||
current_to = newtimer_right_to;
|
||||
current_mid = (current_from + current_to) / 2;
|
||||
current_value = 0;
|
||||
if (timerinterval) clearInterval(timerinterval);
|
||||
timerinterval = setInterval(countDown, 1000);
|
||||
istimeron = true;
|
||||
} else if (!islocked && touchdata.y > (100+10) && touchdata.x < 88 - 10) {
|
||||
Bangle.buzz(40);
|
||||
current_from = newtimer_left_from;
|
||||
current_to = newtimer_left_to;
|
||||
current_mid = (current_from + current_to) / 2;
|
||||
current_value = 0;
|
||||
if (timerinterval) clearInterval(timerinterval);
|
||||
timerinterval = setInterval(countDown, 1000);
|
||||
istimeron = true;
|
||||
}
|
||||
showInstructions = false;
|
||||
draw();
|
||||
});
|
||||
|
||||
Bangle.on('swipe',(swiperight, swipedown)=>{
|
||||
console.log(swiperight);
|
||||
console.log(swipedown);
|
||||
|
||||
if (swiperight == -1) {
|
||||
if (newtimer_left_from >= 60) {
|
||||
newtimer_left_from += 60;
|
||||
newtimer_left_to += 60;
|
||||
} else { // special case for 0:30 to 1:00
|
||||
newtimer_left_from = 60;
|
||||
newtimer_left_to = 120;
|
||||
}
|
||||
newtimer_right_from += 60;
|
||||
newtimer_right_to += 60;
|
||||
draw();
|
||||
} else if (swiperight == 1) {
|
||||
if (newtimer_left_from > 60) {
|
||||
newtimer_left_from -= 60;
|
||||
newtimer_left_to -= 60;
|
||||
} else { // special case for 0:30 to 1:00
|
||||
newtimer_left_from = 30;
|
||||
newtimer_left_to = 60;
|
||||
}
|
||||
|
||||
if (newtimer_right_from > 120) {
|
||||
newtimer_right_from -= 60;
|
||||
newtimer_right_to -= 60;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
var drawTimeout;
|
||||
var showInstructions = true;
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
if (current_value >= current_to) { g.setBgColor("#F00"); }
|
||||
else if (current_value >= current_mid) { g.setBgColor("#FF0"); }
|
||||
else if (current_value >= current_from) { g.setBgColor("#8F8"); }
|
||||
g.clearRect(0,24,176,176);
|
||||
|
||||
g.reset().setFontAlign(0, 0).setColor(istimeron ? "#000" : "#444");
|
||||
g.setFont("Michroma36").drawString(timeToString(current_value), 88, 62);
|
||||
|
||||
g.reset().setFontAlign(0, 0);
|
||||
|
||||
g.setFont("HaxorNarrow7x17");
|
||||
g.drawString(timeToString(current_from), 44, 62+26);
|
||||
g.drawString(timeToString(current_mid), 88, 62+26);
|
||||
g.drawString(timeToString(current_to), 132, 62+26);
|
||||
|
||||
if (current_value >= current_from) { g.drawRect(44-1,62+26+9,44+1,62+26+9+1); }
|
||||
if (current_value >= current_mid) { g.drawRect(88-1,62+26+9,88+1,62+26+9+1); }
|
||||
if (current_value >= current_to) { g.drawRect(132-1,62+26+9,132+1,62+26+9+1); }
|
||||
|
||||
if (showInstructions) {
|
||||
g.setFont("6x8").drawString("Tapping timer locks buttons", 88, 100+5);
|
||||
g.setFont("6x8").drawString("<= Swipe to change time =>", 88, 168);
|
||||
}
|
||||
|
||||
g.setColor(islocked ? "#444" : "#000");
|
||||
g.setFont("Michroma16");
|
||||
g.drawString(timeToString(newtimer_left_from), 44, 138-9);
|
||||
g.drawString(timeToString(newtimer_left_to), 44, 138+9);
|
||||
g.drawString(timeToString(newtimer_right_from), 132, 138-9);
|
||||
g.drawString(timeToString(newtimer_right_to), 132, 138+9);
|
||||
|
||||
g.drawRect(0+8,138-24, 88-9+1, 138+22+1);
|
||||
g.drawRect(0+8,138-24, 88-9, 138+22);
|
||||
g.drawRect(88+8,138-24, 176-10+1, 138+22+1);
|
||||
g.drawRect(88+8,138-24, 176-10, 138+22);
|
||||
}
|
||||
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
|
@ -9,3 +9,4 @@
|
|||
0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021
|
||||
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
0.12: Actual pixels as of 29th Nov 2021
|
||||
|
|
After Width: | Height: | Size: 15 KiB |
|
@ -1 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Remove messages on disconnect
|
||||
Fix music control
|
||||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
// Config app not implemented yet
|
||||
setTimeout(()=>load("messages.app.js"),10);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
eval(require("Storage").read("android.settings.js"))(()=>load());
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() { event.t="add";require("messages").pushMessage(event); },
|
||||
"notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); },
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
|
@ -33,7 +33,16 @@
|
|||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
}
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
Object.assign(event, {
|
||||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||"Call", body:"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
|
@ -42,6 +51,7 @@
|
|||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
|
@ -50,6 +60,12 @@
|
|||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", m:cmd });
|
||||
}
|
||||
gbSend({ t: "music", n:cmd });
|
||||
};
|
||||
// Message response
|
||||
Bangle.messageResponse = (msg,response) => {
|
||||
if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS" });
|
||||
// error/warn here?
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
(function(back) {
|
||||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : "Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
"On" : _=>gb({t:"findPhone",n:true}),
|
||||
"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
}),
|
||||
"Messages" : ()=>load("messages.app.js")
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
0.03: Clock now shows day of week under date.
|
||||
|
|
|
@ -23,6 +23,7 @@ function draw() {
|
|||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var dateStr = require("locale").date(date).toUpperCase();
|
||||
var dowStr = require("locale").dow(date).toUpperCase();
|
||||
// draw time
|
||||
g.setFontAlign(0,0).setFont("Anton");
|
||||
g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background
|
||||
|
@ -32,6 +33,10 @@ function draw() {
|
|||
g.setFontAlign(0,0).setFont("6x8",2);
|
||||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||
g.drawString(dateStr,x,y);
|
||||
//draw day of week
|
||||
y += 16;
|
||||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||
g.drawString(dowStr,x,y);
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.04: Fix tapping at very bottom of list, exit on inactivity
|
||||
0.03: Add "Calculating" placeholder, update JSON save format
|
||||
0.02: Fix JSON save format
|
||||
0.01: First release
|
|
@ -0,0 +1,29 @@
|
|||
# Authentiwatch - 2FA Authenticator
|
||||
|
||||
* GitHub: https://github.com/andrewgoz/Authentiwatch <-- Report bugs here
|
||||
* Bleeding edge AppLoader: https://andrewgoz.github.io/Authentiwatch/
|
||||
|
||||
## Supports
|
||||
|
||||
* Google Authenticator compatible 2-factor authentication
|
||||
* Hash calculations:
|
||||
* Bangle 1: SHA1 only (same as Google Authenticator)
|
||||
* Bangle 2: All (SHA1, SHA256, SHA512)
|
||||
* Timed (TOTP) and Counter (HOTP) modes
|
||||
* Custom periods
|
||||
* Between 6 and 10 digits
|
||||
* Phone/PC configuration web page:
|
||||
* Add/edit/delete/arrange tokens
|
||||
* Scan QR codes
|
||||
* Produce scannable QR codes
|
||||
|
||||
## Usage
|
||||
|
||||
* Use the Phone/PC web page interface to manage the tokens stored on the watch.
|
||||
* Tokens are stored *ONLY* on the watch.
|
||||
* Swipe right to exit to the app launcher.
|
||||
* Swipe left on selected counter token to advance the counter to the next value.
|
||||
|
||||
## Creator
|
||||
|
||||
Andrew Gregory (andrew.gregory at gmail)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4AD64ADFlgAFF04INFz4LUF0QwjEBwv/FzwwgF/4v/F6nMAAWi1AFD5nOeEHPEweoFooAB5/X5wvdFwotG5nN6/WAoQuaEoguHSYPQLwIIDF8uo5ouB6AJEFzuiFwup5/WFwI6GL0esXYKMBHYy9j1WqfBSOhBIYKJF8gAKF/4v6cZAvhGDAuWSDAvXMCwuYF+AwUFzX+0XGGAgxKFrYuBAAQxEeg4tcF4oABBQnGAAgv/F6b5KXsIvIGAqNnF/69fX8ZeSF7btNR8IuOF75ePL8ouOd74NKF8IANF94wEF1QAXA"))
|
|
@ -0,0 +1,325 @@
|
|||
const tokenentryheight = 46;
|
||||
// Hash functions
|
||||
const crypto = require("crypto");
|
||||
const algos = {
|
||||
"SHA512":{sha:crypto.SHA512,retsz:64,blksz:128},
|
||||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||
};
|
||||
const calculating = "Calculating";
|
||||
const notokens = "No tokens";
|
||||
const notsupported = "Not supported";
|
||||
|
||||
// sample settings:
|
||||
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
|
||||
var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
|
||||
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
|
||||
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
|
||||
|
||||
// QR Code Text
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret}
|
||||
//
|
||||
// ${algorithm} : one of SHA1 / SHA256 / SHA512
|
||||
// ${digits} : one of 6 / 8
|
||||
// ${period} : one of 30 / 60
|
||||
// ${url} : a domain name "example.com"
|
||||
// ${secret} : the seed code
|
||||
|
||||
function b32decode(seedstr) {
|
||||
// RFC4648
|
||||
var i, buf = 0, bitcount = 0, retstr = "";
|
||||
for (i in seedstr) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0);
|
||||
if (c != -1) {
|
||||
buf <<= 5;
|
||||
buf |= c;
|
||||
bitcount += 5;
|
||||
if (bitcount >= 8) {
|
||||
retstr += String.fromCharCode(buf >> (bitcount - 8));
|
||||
buf &= (0xFF >> (16 - bitcount));
|
||||
bitcount -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
retstr += String.fromCharCode(buf << (8 - bitcount));
|
||||
}
|
||||
var retbuf = new Uint8Array(retstr.length);
|
||||
for (i in retstr) {
|
||||
retbuf[i] = retstr.charCodeAt(i);
|
||||
}
|
||||
return retbuf;
|
||||
}
|
||||
function do_hmac(key, message, algo) {
|
||||
var a = algos[algo];
|
||||
// RFC2104
|
||||
if (key.length > a.blksz) {
|
||||
key = a.sha(key);
|
||||
}
|
||||
var istr = new Uint8Array(a.blksz + message.length);
|
||||
var ostr = new Uint8Array(a.blksz + a.retsz);
|
||||
for (var i = 0; i < a.blksz; ++i) {
|
||||
var c = (i < key.length) ? key[i] : 0;
|
||||
istr[i] = c ^ 0x36;
|
||||
ostr[i] = c ^ 0x5C;
|
||||
}
|
||||
istr.set(message, a.blksz);
|
||||
ostr.set(a.sha(istr), a.blksz);
|
||||
var ret = a.sha(ostr);
|
||||
// RFC4226 dynamic truncation
|
||||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||
return v.getUint32(0) & 0x7FFFFFFF;
|
||||
}
|
||||
function hotp(d, token, dohmac) {
|
||||
var tick;
|
||||
if (token.period > 0) {
|
||||
// RFC6238 - timed
|
||||
var seconds = Math.floor(d.getTime() / 1000);
|
||||
tick = Math.floor(seconds / token.period);
|
||||
} else {
|
||||
// RFC4226 - counter
|
||||
tick = -token.period;
|
||||
}
|
||||
var msg = new Uint8Array(8);
|
||||
var v = new DataView(msg.buffer);
|
||||
v.setUint32(0, tick >> 16 >> 16);
|
||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||
var ret = calculating;
|
||||
if (dohmac) {
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = notsupported;
|
||||
}
|
||||
}
|
||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||
}
|
||||
|
||||
var state = {
|
||||
listy: 0,
|
||||
prevcur:0,
|
||||
curtoken:-1,
|
||||
nextTime:0,
|
||||
otp:"",
|
||||
rem:0,
|
||||
hide:0
|
||||
};
|
||||
|
||||
function drawToken(id, r) {
|
||||
var x1 = r.x;
|
||||
var y1 = r.y;
|
||||
var x2 = r.x + r.w - 1;
|
||||
var y2 = r.y + r.h - 1;
|
||||
var adj, sz;
|
||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||
if (id == state.curtoken) {
|
||||
// current token
|
||||
g.setColor(g.theme.fgH);
|
||||
g.setBgColor(g.theme.bgH);
|
||||
g.setFont("Vector", 16);
|
||||
// center just below top line
|
||||
g.setFontAlign(0, -1, 0);
|
||||
adj = y1;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setFont("Vector", 30);
|
||||
// center in box
|
||||
g.setFontAlign(0, 0, 0);
|
||||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(tokens[id].label.substr(0, 10), (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period);
|
||||
g.fillRect(x1, y2 - 4, xr, y2 - 1);
|
||||
adj = 0;
|
||||
} else {
|
||||
// counter - draw triangle as swipe hint
|
||||
let yc = (y1 + y2) / 2;
|
||||
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
|
||||
adj = 10;
|
||||
}
|
||||
// digits just below label
|
||||
sz = 30;
|
||||
do {
|
||||
g.setFont("Vector", sz--);
|
||||
} while (g.stringWidth(state.otp) > (r.w - adj));
|
||||
g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + 16, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
g.drawLine(x1, y1, x2, y1);
|
||||
g.drawLine(x1, y2, x2, y2);
|
||||
g.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var timerfn = exitApp;
|
||||
var timerdly = 10000;
|
||||
var d = new Date();
|
||||
if (state.curtoken != -1) {
|
||||
var t = tokens[state.curtoken];
|
||||
if (state.otp == calculating) {
|
||||
state.otp = hotp(d, t, true).hotp;
|
||||
}
|
||||
if (d.getTime() > state.nextTime) {
|
||||
if (state.hide == 0) {
|
||||
// auto-hide the current token
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nextTime = 0;
|
||||
} else {
|
||||
// time to generate a new token
|
||||
var r = hotp(d, t, state.otp != "");
|
||||
state.nextTime = r.next;
|
||||
state.otp = r.hotp;
|
||||
if (t.period <= 0) {
|
||||
state.hide = 1;
|
||||
}
|
||||
state.hide--;
|
||||
}
|
||||
}
|
||||
state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000));
|
||||
}
|
||||
if (tokens.length > 0) {
|
||||
var drewcur = false;
|
||||
var id = Math.floor(state.listy / tokenentryheight);
|
||||
var y = id * tokenentryheight + Bangle.appRect.y - state.listy;
|
||||
while (id < tokens.length && y < Bangle.appRect.y2) {
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight});
|
||||
if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
|
||||
drewcur = true;
|
||||
}
|
||||
id += 1;
|
||||
y += tokenentryheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - schedule a redraw
|
||||
if (tokens[state.curtoken].period > 0) {
|
||||
timerdly = (state.otp == calculating) ? 1 : 1000; // timed
|
||||
} else {
|
||||
timerdly = state.nexttime - d.getTime(); // counter
|
||||
}
|
||||
timerfn = draw;
|
||||
if (tokens[state.curtoken].period <= 0) {
|
||||
state.hide = 0;
|
||||
}
|
||||
} else {
|
||||
// de-select the current token if it is scrolled out of view
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nexttime = 0;
|
||||
}
|
||||
} else {
|
||||
g.setFont("Vector", 30);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
}
|
||||
state.drawtimer = setTimeout(timerfn, timerdly);
|
||||
}
|
||||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
|
||||
if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
if (id != -1) {
|
||||
var y = id * tokenentryheight - state.listy;
|
||||
if (y < 0) {
|
||||
state.listy += y;
|
||||
y = 0;
|
||||
}
|
||||
y += tokenentryheight;
|
||||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
state.otp = "";
|
||||
}
|
||||
state.nextTime = 0;
|
||||
state.curtoken = id;
|
||||
state.hide = 2;
|
||||
}
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
|
||||
state.listy = Math.max(0, newy);
|
||||
draw();
|
||||
}
|
||||
|
||||
function onSwipe(e) {
|
||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||
tokens[state.curtoken].period--;
|
||||
let newsettings={tokens:tokens,misc:settings.misc};
|
||||
require("Storage").writeJSON("authentiwatch.json", newsettings);
|
||||
state.nextTime = 0;
|
||||
state.otp = "";
|
||||
state.hide = 2;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function bangle1Btn(e) {
|
||||
if (tokens.length > 0) {
|
||||
if (state.curtoken == -1) {
|
||||
state.curtoken = state.prevcur;
|
||||
} else {
|
||||
switch (e) {
|
||||
case -1: state.curtoken--; break;
|
||||
case 1: state.curtoken++; break;
|
||||
}
|
||||
}
|
||||
state.curtoken = Math.max(state.curtoken, 0);
|
||||
state.curtoken = Math.min(state.curtoken, tokens.length - 1);
|
||||
var fakee = {};
|
||||
fakee.y = state.curtoken * tokenentryheight - state.listy + Bangle.appRect.y;
|
||||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
} else {
|
||||
draw(); // resets idle timer
|
||||
}
|
||||
}
|
||||
|
||||
function exitApp() {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
|
||||
Bangle.on('touch', onTouch);
|
||||
Bangle.on('drag' , onDrag );
|
||||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,375 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style type="text/css">
|
||||
body{font-family:sans-serif}
|
||||
body div{display:none}
|
||||
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
||||
#tokens th,#tokens td{padding:5px}
|
||||
#tokens tr:nth-child(odd){background-color:#ccc}
|
||||
#tokens tr:nth-child(even){background-color:#eee}
|
||||
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
||||
#advbtn,#scan,#tokenqr table{text-align:center}
|
||||
#edittoken tbody#adv{display:none}
|
||||
#edittoken.showadv tbody#adv{display:table-row-group}
|
||||
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
||||
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
||||
button{height:3em}
|
||||
table button{width:100%}
|
||||
form.totp tr.hotp,form.hotp tr.totp{display:none}
|
||||
</style>
|
||||
|
||||
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
||||
<script src="qr_packed.js"></script>
|
||||
|
||||
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
||||
<script src="../../core/lib/qrcode.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/* Start of all TOTP URLs */
|
||||
const otpAuthUrl = 'otpauth://';
|
||||
|
||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||
|
||||
/* Settings */
|
||||
var settings = {tokens:[], misc:{}};
|
||||
var tokens = settings.tokens;
|
||||
|
||||
/* Remove any non-base-32 characters from the given string and collapses
|
||||
* whitespace to a single space. Optionally removes all whitespace from
|
||||
* the string.
|
||||
*/
|
||||
function base32clean(val, nows) {
|
||||
var ret = val.replaceAll(/\s+/g, ' ');
|
||||
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
||||
if (nows) {
|
||||
ret = ret.replaceAll(/\s+/g, '');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Save changes to a token to the global tokens[] array.
|
||||
* id is the index into the global tokens[].
|
||||
* forget is a flag indicating if the token should be forgotten.
|
||||
*/
|
||||
function saveEdit(id, forget) {
|
||||
if (forget) {
|
||||
if (confirm('Forget token?')) {
|
||||
tokens.splice(id, 1);
|
||||
updateTokens();
|
||||
}
|
||||
} else {
|
||||
let fe = document.forms['edittoken'].elements;
|
||||
let d = parseInt(fe['digits'].value);
|
||||
let p = parseInt(fe['period'].value);
|
||||
let c = parseInt(fe['count'].value);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: p = (c > 0) ? -c : 0; break;
|
||||
default : p = (p > 0) ? p : 30; break;
|
||||
}
|
||||
tokens[id] = {
|
||||
'algorithm':fe['algorithm'].value,
|
||||
'digits':((d > 0) ? d : 6),
|
||||
'period':p,
|
||||
'issuer':fe['issuer'].value,
|
||||
'account':fe['account'].value,
|
||||
'secret':base32clean(fe['secret'].value, false),
|
||||
'label':fe['label'].value
|
||||
};
|
||||
updateTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate and display a QR-code representing the current token.
|
||||
*/
|
||||
function showQrCode() {
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
var url = new String(otpAuthUrl);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: url += 'hotp/'; break;
|
||||
default : url += 'totp/'; break;
|
||||
}
|
||||
if (fe['account'].value.length > 0) {
|
||||
url += encodeURIComponent(fe['account'].value);
|
||||
} else {
|
||||
url += encodeURIComponent(fe['label'].value);
|
||||
}
|
||||
url += '?';
|
||||
if (fe['issuer'].value.length > 0) {
|
||||
url += 'issuer=' + encodeURIComponent(fe['issuer'].value);
|
||||
url += '&';
|
||||
}
|
||||
url += 'secret=' + base32clean(fe['secret'].value, true);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]:
|
||||
if (parseInt(fe['count'].value) != 0) {
|
||||
url += '&counter=' + fe['count'].value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (parseInt(fe['period'].value) != 30) {
|
||||
url += '&period=' + fe['period'].value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parseInt(fe['digits'].value) != 6) {
|
||||
url += '&digits=' + fe['digits'].value;
|
||||
}
|
||||
if (fe['algorithm'].value != 'SHA1') {
|
||||
url += '&algorithm=' + fe['algorithm'].value;
|
||||
}
|
||||
tokenqr.clear();
|
||||
tokenqr.makeCode(url);
|
||||
document.body.className = 'showqr';
|
||||
}
|
||||
|
||||
function onTypeChanged() {
|
||||
var f = document.forms['edittoken'];
|
||||
var fe = f.elements;
|
||||
if (fe['type'].value == tokentypes[0]) { f.classList.add('totp'); f.classList.remove('hotp'); }
|
||||
if (fe['type'].value == tokentypes[1]) { f.classList.add('hotp'); f.classList.remove('totp'); }
|
||||
}
|
||||
|
||||
/* Generate a form for editing the specified token.
|
||||
* id is the index into the global tokens[].
|
||||
*/
|
||||
function editToken(id) {
|
||||
var p;
|
||||
const selectMarkup = function(name, ary, cur, onchg) {
|
||||
var ret = '<select name="' + name + '"' + ((typeof onchg == 'string') ? ' onchange="' + onchg + '"' : '') + '>';
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
ret += '<option' + ((ary[i] == cur) ? ' selected=selected' : '') + '>' + ary[i] + '</option>';
|
||||
}
|
||||
return ret + '</select>';
|
||||
};
|
||||
scanning=false;
|
||||
var markup = '<form id="edittoken"><input type="hidden" name="tokenid" value="' + id + '"><table>';
|
||||
markup += '<tr><td>Name:</td><td><input name="label" type="text" value="' + tokens[id].label + '"></td></tr>';
|
||||
markup += '<tr><td>Secret:</td><td><input name="secret" type="text" value="' + tokens[id].secret + '"></td></tr>';
|
||||
markup += '<tbody id="adv">';
|
||||
markup += '<tr><td>Account:</td><td><input name="account" type="text" value="' + tokens[id].account + '"></td></tr>';
|
||||
markup += '<tr><td>Issuer:</td><td><input name="issuer" type="text" value="' + tokens[id].issuer + '"></td></tr>';
|
||||
p = parseInt(tokens[id].period);
|
||||
markup += '<tr><td>Type:</td><td>';
|
||||
markup += selectMarkup('type', tokentypes, (tokens[id].period > 0) ? tokentypes[0] : tokentypes[1], 'onTypeChanged()');
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr class="totp"><td>Period:</td><td><input name="period" type="text" value="' + ((p > 0) ? p : 30) + '"></td></tr>';
|
||||
markup += '<tr class="hotp"><td>Count:</td><td><input name="count" type="text" value="' + ((p >= 0) ? 0 : -p) + '"></td></tr>';
|
||||
markup += '<tr><td>Digits:</td><td>';
|
||||
markup += selectMarkup('digits', ['6','7','8','9','10'], tokens[id].digits);
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr><td>Hash:</td><td>';
|
||||
markup += selectMarkup('algorithm', ['SHA1','SHA256','SHA512'], tokens[id].algorithm);
|
||||
markup += '</td></tr>';
|
||||
markup += '</tbody><tr><td id="advbtn" colspan="2">';
|
||||
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
|
||||
markup += '</td></tr></table></form>';
|
||||
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
|
||||
if (tokens[id].isnew) {
|
||||
markup += '<button type="button" onclick="startScan()">Scan QR Code</button>';
|
||||
} else {
|
||||
markup += '<button type="button" onclick="showQrCode()">Show QR Code</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
|
||||
}
|
||||
document.getElementById('edit').innerHTML = markup;
|
||||
document.body.className = 'editing';
|
||||
onTypeChanged();
|
||||
}
|
||||
|
||||
/* Create a new blank token and open the editor for it.
|
||||
*/
|
||||
function addToken() {
|
||||
tokens[tokens.length] = {'algorithm':'SHA1','digits':6,'period':30,'issuer':'','account':'','secret':'','label':'','isnew':true};
|
||||
editToken(tokens.length - 1);
|
||||
}
|
||||
|
||||
/* Move the specified token up or down in the global tokens[].
|
||||
* id is the index in the global tokens[] of the token to move.
|
||||
* dir is the direction to move: -1=up, 1=down.
|
||||
*/
|
||||
function moveToken(id, dir) {
|
||||
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
||||
updateTokens();
|
||||
}
|
||||
|
||||
/* Update the display listing all the tokens.
|
||||
*/
|
||||
function updateTokens() {
|
||||
const tokenButton = function(fn, id, label, dir) {
|
||||
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
|
||||
};
|
||||
var markup = '<table><tr><th>Token</th><th colspan="2">Order</th></tr>';
|
||||
/* any tokens marked new are cancelled new additions and must be removed */
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].isnew) {
|
||||
tokens.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
markup += '<tr><td>';
|
||||
markup += tokenButton('editToken', i, tokens[i].label);
|
||||
markup += '</td><td>';
|
||||
if (i < (tokens.length - 1)) {
|
||||
markup += tokenButton('moveToken', i, '▼', 1);
|
||||
}
|
||||
markup += '</td><td>';
|
||||
if (i > 0) {
|
||||
markup += tokenButton('moveToken', i, '▲', -1);
|
||||
}
|
||||
markup += '</td></tr>';
|
||||
}
|
||||
markup += '</table>';
|
||||
markup += '<button type="button" onclick="addToken()">Add Token</button>';
|
||||
markup += '<button type="button" onclick="saveTokens()">Save to watch</button>';
|
||||
document.getElementById('tokens').innerHTML = markup;
|
||||
document.body.className = 'select';
|
||||
}
|
||||
|
||||
/* Original QR-code reader: https://www.sitepoint.com/create-qr-code-reader-mobile-website/ */
|
||||
qrcode.callback = res => {
|
||||
if (res) {
|
||||
if (res.startsWith(otpAuthUrl)) {
|
||||
res = decodeURIComponent(res);
|
||||
var paramsidx = res.indexOf('?');
|
||||
var params = res.substr(paramsidx+1).split('&');
|
||||
var t = {
|
||||
'algorithm':'SHA1',
|
||||
'digits':'6',
|
||||
'counter':'0',
|
||||
'period':'30',
|
||||
'secret':'',
|
||||
'issuer':''
|
||||
};
|
||||
var otpok = true;
|
||||
for (let pi in params) {
|
||||
var param = params[pi].split('=');
|
||||
if (param[0] in t) {
|
||||
t[param[0]] = param[1];
|
||||
} else {
|
||||
otpok = false;
|
||||
}
|
||||
}
|
||||
t['account'] = res.substring(res.lastIndexOf('/', paramsidx)+1, paramsidx);
|
||||
if ((t['account'] == '') || (t['secret'] == '')) {
|
||||
otpok = false;
|
||||
}
|
||||
if (otpok) {
|
||||
scanning = false;
|
||||
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
||||
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
||||
t['label'] = t['label'].substr(0, 10);
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||
t['period'] = '30';
|
||||
fe['type'].value = tokentypes[1];
|
||||
} else {
|
||||
t['counter'] = '0';
|
||||
fe['type'].value = tokentypes[0];
|
||||
}
|
||||
fe['algorithm'].value = t['algorithm'];
|
||||
fe['digits' ].value = t['digits' ];
|
||||
fe['count' ].value = t['counter' ];
|
||||
fe['period' ].value = t['period' ];
|
||||
fe['secret' ].value = t['secret' ];
|
||||
fe['issuer' ].value = t['issuer' ];
|
||||
fe['account' ].value = t['account' ];
|
||||
fe['label' ].value = t['label' ];
|
||||
onTypeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function startScan() {
|
||||
document.body.className = 'scanning';
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({video:{facingMode:'environment'}})
|
||||
.then(function(stream){
|
||||
scanning=true;
|
||||
video.setAttribute('playsinline',true);
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
scanTick();
|
||||
doScan();
|
||||
});
|
||||
}
|
||||
function scanTick() {
|
||||
canvasElement.height = video.videoHeight;
|
||||
canvasElement.width = video.videoWidth;
|
||||
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
||||
if (scanning) {
|
||||
requestAnimationFrame(scanTick);
|
||||
} else {
|
||||
video.srcObject.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
function doScan() {
|
||||
try {
|
||||
qrcode.decode();
|
||||
} catch (e) {
|
||||
setTimeout(doScan,300);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load settings JSON file from the watch.
|
||||
*/
|
||||
function loadTokens() {
|
||||
Util.showModal('Loading...');
|
||||
Puck.eval(`require('Storage').readJSON(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Util.hideModal();
|
||||
if (data.data ) settings.tokens = data.data ; /* v0.02 settings */
|
||||
if (data.tokens) settings.tokens = data.tokens; /* v0.03+ settings */
|
||||
if (data.misc ) settings.misc = data.misc ; /* v0.03+ settings */
|
||||
tokens = settings.tokens;
|
||||
updateTokens();
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function saveTokens() {
|
||||
Util.showModal('Saving...');
|
||||
let newsettings={tokens:tokens,misc:settings.misc};
|
||||
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('authentiwatch.json')},${JSON.stringify(newsettings)})\n`,()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
function onInit() {
|
||||
loadTokens();
|
||||
updateTokens();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="select">
|
||||
<h1>Authentiwatch</h1>
|
||||
<div id="tokens">
|
||||
<p>No watch comms.</p>
|
||||
</div>
|
||||
<div id="scan">
|
||||
<table>
|
||||
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
||||
<tr><td><button type="button" onclick="editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="edit">
|
||||
</div>
|
||||
<div id="tokenqr">
|
||||
<table><tr><td id="qrcode"></td></tr><tr><td>
|
||||
<button type="button" onclick="document.body.className='editing'">Back</button>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const video=document.createElement('video');
|
||||
const canvasElement=document.getElementById('qr-canvas');
|
||||
const canvas=canvasElement.getContext('2d');
|
||||
let scanning=false;
|
||||
const tokenqr=new QRCode(document.getElementById('qrcode'), '');
|
||||
</script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,107 @@
|
|||
/* Packed with Google Closure
|
||||
*
|
||||
* Ported to JavaScript by Lazar Laszlo 2011
|
||||
* lazarsoft@gmail.com, www.lazarsoft.info
|
||||
*
|
||||
* Copyright 2007 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks",
|
||||
function(){for(var d=0,c=0;c<this.ecBlocks.length;c++)d+=this.ecBlocks[c].length;return d});this.getECBlocks=function(){return this.ecBlocks}}function k(h,b,e,d,c,a){this.versionNumber=h;this.alignmentPatternCenters=b;this.ecBlocks=[e,d,c,a];h=0;b=e.ECCodewordsPerBlock;e=e.getECBlocks();for(d=0;d<e.length;d++)c=e[d],h+=c.Count*(c.DataCodewords+b);this.totalCodewords=h;this.__defineGetter__("VersionNumber",function(){return this.versionNumber});this.__defineGetter__("AlignmentPatternCenters",function(){return this.alignmentPatternCenters});
|
||||
this.__defineGetter__("TotalCodewords",function(){return this.totalCodewords});this.__defineGetter__("DimensionForVersion",function(){return 17+4*this.versionNumber});this.buildFunctionPattern=function(){var d=this.DimensionForVersion,c=new I(d);c.setRegion(0,0,9,9);c.setRegion(d-8,0,8,9);c.setRegion(0,d-8,9,8);for(var e=this.alignmentPatternCenters.length,b=0;b<e;b++)for(var a=this.alignmentPatternCenters[b]-2,h=0;h<e;h++)0==b&&(0==h||h==e-1)||b==e-1&&0==h||c.setRegion(this.alignmentPatternCenters[h]-
|
||||
2,a,5,5);c.setRegion(6,9,1,d-17);c.setRegion(9,6,d-17,1);6<this.versionNumber&&(c.setRegion(d-11,0,3,6),c.setRegion(0,d-11,6,3));return c};this.getECBlocksForLevel=function(c){return this.ecBlocks[c.ordinal()]}}function z(h,b,e,d,c,a,l,m,f){this.a11=h;this.a12=d;this.a13=l;this.a21=b;this.a22=c;this.a23=m;this.a31=e;this.a32=a;this.a33=f;this.transformPoints1=function(c){for(var d=c.length,e=this.a11,b=this.a12,h=this.a13,a=this.a21,l=this.a22,p=this.a23,m=this.a31,f=this.a32,g=this.a33,y=0;y<d;y+=
|
||||
2){var q=c[y],k=c[y+1],n=h*q+p*k+g;c[y]=(e*q+a*k+m)/n;c[y+1]=(b*q+l*k+f)/n}};this.transformPoints2=function(c,d){for(var e=c.length,b=0;b<e;b++){var h=c[b],a=d[b],l=this.a13*h+this.a23*a+this.a33;c[b]=(this.a11*h+this.a21*a+this.a31)/l;d[b]=(this.a12*h+this.a22*a+this.a32)/l}};this.buildAdjoint=function(){return new z(this.a22*this.a33-this.a23*this.a32,this.a23*this.a31-this.a21*this.a33,this.a21*this.a32-this.a22*this.a31,this.a13*this.a32-this.a12*this.a33,this.a11*this.a33-this.a13*this.a31,this.a12*
|
||||
this.a31-this.a11*this.a32,this.a12*this.a23-this.a13*this.a22,this.a13*this.a21-this.a11*this.a23,this.a11*this.a22-this.a12*this.a21)};this.times=function(c){return new z(this.a11*c.a11+this.a21*c.a12+this.a31*c.a13,this.a11*c.a21+this.a21*c.a22+this.a31*c.a23,this.a11*c.a31+this.a21*c.a32+this.a31*c.a33,this.a12*c.a11+this.a22*c.a12+this.a32*c.a13,this.a12*c.a21+this.a22*c.a22+this.a32*c.a23,this.a12*c.a31+this.a22*c.a32+this.a32*c.a33,this.a13*c.a11+this.a23*c.a12+this.a33*c.a13,this.a13*c.a21+
|
||||
this.a23*c.a22+this.a33*c.a23,this.a13*c.a31+this.a23*c.a32+this.a33*c.a33)}}function P(h,b){this.bits=h;this.points=b}function Q(h){this.image=h;this.resultPointCallback=null;this.sizeOfBlackWhiteBlackRun=function(b,e,d,c){var h=Math.abs(c-e)>Math.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=e<c?1:-1,x=b<d?1:-1,v=0,t=b,a=e;t!=d;t+=x){var J=h?a:t,n=h?t:a;1==v?this.image[J+n*g.width]&&v++:this.image[J+n*g.width]||v++;if(3==v)return c=t-b,e=a-e,Math.sqrt(c*
|
||||
c+e*e);q+=f;if(0<q){if(a==c)break;a+=k;q-=m}}b=d-b;e=c-e;return Math.sqrt(b*b+e*e)};this.sizeOfBlackWhiteBlackRunBothWays=function(b,e,d,c){var a=this.sizeOfBlackWhiteBlackRun(b,e,d,c),h=1;d=b-(d-b);0>d?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X),
|
||||
Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b,
|
||||
d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5,
|
||||
3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0<a.AlignmentPatternCenters.length)for(a=1-3/m,f=Math.floor(e.X+a*(d.X-e.X+b.X-e.X)),a=Math.floor(e.Y+a*(d.Y-e.Y+b.Y-e.Y));;){f=this.findAlignmentInRegion(c,
|
||||
f,a,4);break}c=this.createTransform(e,d,b,f,h);h=this.sampleGrid(this.image,c,h);return new P(h,null==f?[b,e,d]:[b,e,d,f])};this.detect=function(){var b=(new S).findFinderPattern(this.image);return this.processFinderPatternInfo(b)}}function r(h){this.errorCorrectionLevel=C.forBits(h>>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<<
|
||||
3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h&
|
||||
31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e<this.bits.length;e++)this.bits[e]=0;this.__defineGetter__("Width",function(){return this.width});this.__defineGetter__("Height",function(){return this.height});this.__defineGetter__("Dimension",function(){if(this.width!=this.height)throw"Can't call getDimension() on a non-square matrix";return this.width});this.get_Renamed=function(d,c){return 0!=(u(this.bits[c*this.rowSize+(d>>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+
|
||||
(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;c<d;c++)this.bits[c]=0};this.setRegion=function(d,c,e,b){if(0>c||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c<b;c++)for(var h=c*this.rowSize,a=d;a<e;a++)this.bits[h+(a>>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords=
|
||||
a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e=
|
||||
0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d<c;d++)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;throw"Error readFormatInformation";
|
||||
};this.readVersion=function(){if(null!=this.parsedVersion)return this.parsedVersion;var e=this.bitMatrix.Dimension,d=e-17>>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=
|
||||
this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0<k;k-=2){6==k&&k--;for(var x=0;x<b;x++)for(var v=a?b-1-x:x,t=0;2>t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k-
|
||||
t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1<e&&0==b[0]){for(var d=1;d<e&&0==b[d];)d++;if(d==e)this.coefficients=a.Zero.coefficients;else{this.coefficients=Array(e-d);for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=0;for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=b[d+e]}}else this.coefficients=b;this.__defineGetter__("Zero",
|
||||
function(){return 0==this.coefficients[0]});this.__defineGetter__("Degree",function(){return this.coefficients.length-1});this.__defineGetter__("Coefficients",function(){return this.coefficients});this.getCoefficient=function(c){return this.coefficients[this.coefficients.length-1-c]};this.evaluateAt=function(c){if(0==c)return this.getCoefficient(0);var d=this.coefficients.length;if(1==c){for(var b=c=0;b<d;b++)c=n.addOrSubtract(c,this.coefficients[b]);return c}for(var e=this.coefficients[0],b=1;b<
|
||||
d;b++)e=n.addOrSubtract(this.field.multiply(c,e),this.coefficients[b]);return e};this.addOrSubtract=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(this.Zero)return c;if(c.Zero)return this;var d=this.coefficients;c=c.coefficients;if(d.length>c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;h<e;h++)b[h]=c[h];for(h=e;h<c.length;h++)b[h]=n.addOrSubtract(d[h-e],c[h]);return new w(a,b)};this.multiply1=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";
|
||||
if(this.Zero||c.Zero)return this.field.Zero;var d=this.coefficients,b=d.length;c=c.coefficients;for(var e=c.length,a=Array(b+e-1),h=0;h<b;h++)for(var f=d[h],g=0;g<e;g++)a[h+g]=n.addOrSubtract(a[h+g],this.field.multiply(f,c[g]));return new w(this.field,a)};this.multiply2=function(c){if(0==c)return this.field.Zero;if(1==c)return this;for(var d=this.coefficients.length,b=Array(d),e=0;e<d;e++)b[e]=this.field.multiply(this.coefficients[e],c);return new w(this.field,b)};this.multiplyByMonomial=function(c,
|
||||
d){if(0>c)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a<e.length;a++)e[a]=0;for(a=0;a<b;a++)e[a]=this.field.multiply(this.coefficients[a],d);return new w(this.field,e)};this.divide=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(c.Zero)throw"Divide by 0";for(var d=this.field.Zero,b=this,e=c.getCoefficient(c.Degree),e=this.field.inverse(e);b.Degree>=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree,
|
||||
h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero});
|
||||
this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e<b.length;e++)b[e]=0;b[0]=c;return new w(this,b)};this.exp=function(d){return this.expTable[d]};this.log=function(d){if(0==d)throw"System.ArgumentException";return this.logTable[d]};this.inverse=function(d){if(0==d)throw"System.ArithmeticException";return this.expTable[255-this.logTable[d]]};this.multiply=function(d,c){return 0==
|
||||
d||0==c?0:1==d?c:1==c?d:this.expTable[(this.logTable[d]+this.logTable[c])%255]}}function u(a,b){return 0<=a?a>>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++};
|
||||
this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped=
|
||||
!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<<D)/7);e=Math.floor(b/2);return Math.abs(b-(a[0]<<D))<e&&Math.abs(b-
|
||||
(a[1]<<D))<e&&Math.abs(3*b-(a[2]<<D))<3*e&&Math.abs(b-(a[3]<<D))<e&&Math.abs(b-(a[4]<<D))<e};this.centerFromEnd=function(a,b){return b-a[4]-a[3]-a[2]/2};this.crossCheckVertical=function(a,b,e,d){for(var c=this.image,h=g.height,l=this.CrossCheckStateCount,m=a;0<=m&&c[b+m*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[b+m*g.width];)l[2]++,m++;if(m==h)return NaN;
|
||||
for(;m<h&&!c[b+m*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[b+m*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<=
|
||||
e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[m+b*g.width];)l[2]++,m++;if(m==h)return NaN;for(;m<h&&!c[m+b*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[m+b*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e),
|
||||
Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;h<c;h++){var l=this.possibleCenters[h];if(l.aboutEquals(a,b,e)){l.incrementCount();d=!0;break}}d||(e=new U(e,b,a),this.possibleCenters.push(e),null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(e));return!0}return!1};this.selectBestPatterns=function(){var a=this.possibleCenters.length;if(3>a)throw"Couldn't find enough finder patterns (found "+a+")";if(3<a){for(var b=0,e=0,d=0;d<a;d++)var c=
|
||||
this.possibleCenters[d].EstimatedModuleSize,b=b+c,e=e+c*c;var p=b/a;this.possibleCenters.sort(function(c,d){var b=Math.abs(d.EstimatedModuleSize-p),e=Math.abs(c.EstimatedModuleSize-p);return b<e?-1:b==e?0:1});a=Math.max(.2*p,Math.sqrt(e/a-p*p));for(d=this.possibleCenters.length-1;0<=d;d--)Math.abs(this.possibleCenters[d].EstimatedModuleSize-p)>a&&this.possibleCenters.splice(d,1)}3<this.possibleCenters.length&&this.possibleCenters.sort(function(c,d){return c.count>d.count?-1:c.count<d.count?1:0});
|
||||
return[this.possibleCenters[0],this.possibleCenters[1],this.possibleCenters[2]]};this.findRowSkip=function(){var a=this.possibleCenters.length;if(1>=a)return 0;for(var b=null,e=0;e<a;e++){var d=this.possibleCenters[e];if(d.Count>=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c<d;c++)a=this.possibleCenters[c],a.Count>=K&&(b++,e+=a.EstimatedModuleSize);
|
||||
if(3>b)return!1;for(var b=e/d,p=0,c=0;c<d;c++)a=this.possibleCenters[c],p+=Math.abs(a.EstimatedModuleSize-b);return p<=.05*e};this.findFinderPattern=function(a){var b;this.image=a;var e=g.height,d=g.width,c=Math.floor(3*e/(4*W));c<L&&(c=L);for(var h=!1,l=Array(5),m=c-1;m<e&&!h;m+=c){l[0]=0;l[1]=0;l[2]=0;l[3]=0;for(var f=b=l[4]=0;f<d;f++)if(a[f+m*g.width])1==(b&1)&&b++,l[b]++;else if(0==(b&1))if(4==b)if(this.foundPatternCross(l)){if(b=this.handlePossibleCenter(l,m,f))c=2,this.hasSkipped?h=this.haveMultiplyConfirmedCenters():
|
||||
(b=this.findRowSkip(),b>l[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f<d&&!a[f+m*g.width]);f--}b=0;l[0]=0;l[1]=0;l[2]=0;l[3]=0;l[4]=0}else l[0]=l[2],l[1]=l[3],l[2]=l[4],l[3]=1,l[4]=0,b=3;else l[++b]++;else l[b]++;this.foundPatternCross(l)&&(b=this.handlePossibleCenter(l,m,d))&&(c=l[0],this.hasSkipped&&(h=this.haveMultiplyConfirmedCenters()))}a=this.selectBestPatterns();g.orderBestPatterns(a);return new V(a)}}function M(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",
|
||||
function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return Math.floor(this.x)});this.__defineGetter__("Y",function(){return Math.floor(this.y)});this.incrementCount=function(){this.count++};this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters=
|
||||
[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN;
|
||||
for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;l<h&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l++;if(l==h||f[1]>b)return NaN;for(;l<h&&!e[d+l*g.width]&&f[2]<=b;)f[2]++,l++;return f[2]>b||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length,
|
||||
e=0;e<a;e++)if(this.possibleCenters[e].aboutEquals(c,d,b))return new M(b,d,c);b=new M(b,d,c);this.possibleCenters.push(b);null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(b)}return null};this.find=function(){for(var c,b=this.startX,h=this.height,f=b+d,l=e+(h>>1),p=[0,0,0],k=0;k<h;k++){var n=l+(0==(k&1)?k+1>>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A<f&&!a[A+g.width*n];)A++;for(c=0;A<f;){if(a[A+n*g.width])if(1==c)p[c]++;else if(2==c){if(this.foundPatternCross(p)&&
|
||||
(c=this.handlePossibleCenter(p,n,A),null!=c))return c;p[0]=p[2];p[1]=1;p[2]=0;c=1}else p[++c]++;else 1==c&&c++,p[c]++;A++}if(this.foundPatternCross(p)&&(c=this.handlePossibleCenter(p,n,f),null!=c))return c}if(0!=this.possibleCenters.length)return this.possibleCenters[0];throw"Couldn't find enough alignment patterns";}}function X(a,b,e){this.blockPointer=0;this.bitPointer=7;this.dataLength=0;this.blocks=a;this.numErrorCorrectionCode=e;9>=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<=
|
||||
b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b<this.bitPointer+1){var a=0;for(d=0;d<b;d++)a+=1<<d;a<<=this.bitPointer-b+1;d=(this.blocks[this.blockPointer]&a)>>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b<this.bitPointer+1+8){for(d=c=0;d<this.bitPointer+1;d++)c+=1<<d;d=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;d+=this.blocks[this.blockPointer]>>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer=
|
||||
8+this.bitPointer);return d}if(b<this.bitPointer+1+16){for(d=a=c=0;d<this.bitPointer+1;d++)c+=1<<d;c=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;var e=this.blocks[this.blockPointer]<<b-(this.bitPointer+1+8);this.blockPointer++;for(d=0;d<b-(this.bitPointer+1+8);d++)a+=1<<d;a<<=8-(b-(this.bitPointer+1+8));d=(this.blocks[this.blockPointer]&a)>>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0};
|
||||
this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1<b){var a=this.getNextBits(11);var e=a%45,c=c+d[Math.floor(a/45)],c=c+d[e];b-=2}else 1==b&&(a=this.getNextBits(6),c+=d[a],--b);while(0<
|
||||
b);return c};this.getFigureString=function(b){var c=0,d="";do 3<=b?(c=this.getNextBits(10),100>c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0<b);return d};this.get8bitByteArray=function(b){var c=[];do{var d=this.getNextBits(8);c.push(d);b--}while(0<b);return c};this.getKanjiString=function(b){var c="";do{var d=this.getNextBits(13);d=(d/192<<8)+d%192;c+=String.fromCharCode(40956>=d+33088?d+33088:d+49472);b--}while(0<
|
||||
b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0<b.length)break;else throw"Empty data block";if(1!=c&&2!=c&&4!=c&&8!=c&&7!=c)throw"Invalid mode: "+c+" in (block:"+this.blockPointer+" bit:"+this.bitPointer+")";if(7==c)this.parseECIValue();else{var a=this.getDataLength(c);
|
||||
if(1>a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 2:c=this.getRomanAndFigureString(a);a=Array(c.length);for(e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 4:c=this.get8bitByteArray(a);b.push(c);break;case 8:c=this.getKanjiString(a),b.push(c)}}}while(1);return b})}var F={checkAndNudgePoints:function(a,b){for(var e,d,c=g.width,h=g.height,f=!0,m=0;m<b.length&&f;m+=2){d=
|
||||
Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h<b;h++){for(var f=
|
||||
c.length,m=h+.5,k=0;k<f;k+=2)c[k]=(k>>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k<f;k+=2)a[Math.floor(c[k])+g.width*Math.floor(c[k+1])]&&d.set_Renamed(k>>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,
|
||||
84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18,
|
||||
new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22,
|
||||
new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))),
|
||||
new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5,
|
||||
87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)),
|
||||
new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26,
|
||||
new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30,
|
||||
new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30,
|
||||
new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10,
|
||||
48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46),
|
||||
new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14,
|
||||
122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138,
|
||||
166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40<a)throw"ArgumentException";return k.VERSIONS[a-1]};k.getProvisionalVersionForDimension=function(a){if(1!=a%4)throw"Error getProvisionalVersionForDimension";
|
||||
try{return k.getVersionForNumber(a-17>>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d<k.VERSION_DECODE_INFO.length;d++){var c=k.VERSION_DECODE_INFO[d];if(c==a)return this.getVersionForNumber(d+7);c=r.numBitsDiffering(a,c);c<b&&(e=d+7,b=c)}return 3>=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k,
|
||||
q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427,
|
||||
9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!=
|
||||
b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d<N.length;d++){var c=N[d],h=c[0];if(h==a)return new r(c[1]);h=this.numBitsDiffering(a,h);h<b&&(e=c[1],b=h)}return 3>=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException";
|
||||
var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;b<c.length;b++)e+=c[b].Count;e=Array(e);for(var h=0,f=0;f<c.length;f++){var g=c[f];for(b=0;b<g.Count;b++){var k=g.DataCodewords,q=d.ECCodewordsPerBlock+k;e[h++]=new G(k,Array(q))}}b=e[0].codewords.length;for(c=e.length-1;0<=c&&e[c].codewords.length!=b;)c--;c++;d=b-d.ECCodewordsPerBlock;for(b=g=0;b<d;b++)for(f=0;f<h;f++)e[f].codewords[b]=a[g++];for(f=c;f<h;f++)e[f].codewords[d]=a[g++];k=e[0].codewords.length;for(b=d;b<k;b++)for(f=0;f<
|
||||
h;f++)e[f].codewords[f<c?b:b+1]=a[g++];return e};var H={forReference:function(a){if(0>a||7<a)throw"System.ArgumentException";return H.DATA_MASKS[a]}};H.DATA_MASKS=[new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a&1)}},
|
||||
new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==b%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b)%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(u(a,
|
||||
1)+b/3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==(e&1)+e%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==((e&1)+e%3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,
|
||||
e)};this.isMasked=function(a,b){return 0==((a+b&1)+a*b%3&1)}}];n.QR_CODE_FIELD=new n(285);n.DATA_MATRIX_FIELD=new n(301);n.addOrSubtract=function(a,b){return a^b};var E={};E.rsDecoder=new function(a){this.field=a;this.decode=function(a,e){for(var b=new w(this.field,a),c=Array(e),f=0;f<c.length;f++)c[f]=0;for(var h=!0,f=0;f<e;f++){var g=b.evaluateAt(this.field.exp(f));c[c.length-1-f]=g;0!=g&&(h=!1)}if(!h)for(f=new w(this.field,c),b=this.runEuclideanAlgorithm(this.field.buildMonomial(e,1),f,e),f=b[1],
|
||||
b=this.findErrorLocations(b[0]),c=this.findErrorMagnitudes(f,b,!1),f=0;f<b.length;f++){h=a.length-1-this.field.log(b[f]);if(0>h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree<e.Degree){var b=a;a=e;e=b}for(var b=this.field.One,f=this.field.Zero,h=this.field.Zero,g=this.field.One;e.Degree>=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree);
|
||||
for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1==
|
||||
b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&c<b;f++)0==a.evaluateAt(f)&&(d[c]=this.field.inverse(f),c++);if(c!=b)throw"Error locator degree does not match number of roots";return d};this.findErrorMagnitudes=function(a,e,d){for(var b=e.length,f=Array(b),h=0;h<b;h++){for(var g=this.field.inverse(e[h]),k=1,q=0;q<b;q++)h!=q&&(k=this.field.multiply(k,n.addOrSubtract(1,this.field.multiply(e[q],g))));f[h]=this.field.multiply(a.evaluateAt(g),this.field.inverse(k));d&&(f[h]=this.field.multiply(f[h],
|
||||
g))}return f}}(n.QR_CODE_FIELD);E.correctErrors=function(a,b){for(var e=a.length,d=Array(e),c=0;c<e;c++)d[c]=a[c]&255;e=a.length-b;try{E.rsDecoder.decode(d,e)}catch(p){throw p;}for(c=0;c<b;c++)a[c]=d[c]};E.decode=function(a){var b=new T(a);a=b.readVersion();for(var e=b.readFormatInformation().ErrorCorrectionLevel,b=b.readCodewords(),b=G.getDataBlocks(b,a,e),d=0,c=0;c<b.length;c++)d+=b[c].NumDataCodewords;for(var d=Array(d),f=0,h=0;h<b.length;h++){var c=b[h],g=c.Codewords,k=c.NumDataCodewords;E.correctErrors(g,
|
||||
k);for(c=0;c<k;c++)d[f++]=g[c]}return new X(d,a.VersionNumber,e.Bits)};var g={imagedata:null,width:0,height:0,qrCodeSymbol:null,debug:!1,maxImgSize:1048576,sizeOfDataLengthInfo:[[10,9,8,8],[12,11,16,10],[14,13,16,12]],callback:null,vidSuccess:function(a){g.localstream=a;g.webkit?g.video.src=window.webkitURL.createObjectURL(a):g.moz?(g.video.mozSrcObject=a,g.video.play()):g.video.src=a;g.gUM=!0;g.canvas_qr2=document.createElement("canvas");g.canvas_qr2.id="qr-canvas";g.qrcontext2=g.canvas_qr2.getContext("2d");
|
||||
g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;setTimeout(g.captureToCanvas,500)},vidError:function(a){g.gUM=!1},captureToCanvas:function(){if(g.gUM)try{if(0==g.video.videoWidth)setTimeout(g.captureToCanvas,500);else{g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;g.qrcontext2.drawImage(g.video,0,0);try{g.decode()}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}}}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}},setWebcam:function(a){var b=
|
||||
navigator;g.video=document.getElementById(a);var e=!0;if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices)try{navigator.mediaDevices.enumerateDevices().then(function(a){a.forEach(function(a){console.log("deb1");"videoinput"===a.kind&&-1<a.label.toLowerCase().search("back")&&(e=[{sourceId:a.deviceId}]);console.log(a.kind+": "+a.label+" id = "+a.deviceId)})})}catch(d){console.log(d)}else console.log("no navigator.mediaDevices.enumerateDevices");b.getUserMedia?b.getUserMedia({video:e,
|
||||
audio:!1},g.vidSuccess,g.vidError):b.webkitGetUserMedia?(g.webkit=!0,b.webkitGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError)):b.mozGetUserMedia&&(g.moz=!0,b.mozGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError))},decode:function(a){if(0==arguments.length){if(g.canvas_qr2){var b=g.canvas_qr2;var e=g.qrcontext2}else b=document.getElementById("qr-canvas"),e=b.getContext("2d");g.width=b.width;g.height=b.height;g.imagedata=e.getImageData(0,0,g.width,g.height);g.result=g.process(e);null!=
|
||||
g.callback&&g.callback(g.result);return g.result}var d=new Image;d.crossOrigin="Anonymous";d.onload=function(){var a=document.getElementById("out-canvas");null!=a&&(a=a.getContext("2d"),a.clearRect(0,0,320,240),a.drawImage(d,0,0,320,240));var a=document.createElement("canvas"),b=a.getContext("2d"),e=d.height,f=d.width;d.width*d.height>g.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata=
|
||||
b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)},
|
||||
decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;d<g.height;d++)for(var c=0;c<g.width;c++){var f=4*c+d*g.width*4;g.imagedata.data[f]=0;g.imagedata.data[f+1]=0;g.imagedata.data[f+2]=e[c+d*g.width]?255:0}a.putImageData(g.imagedata,0,0)}e=
|
||||
(new Q(e)).detect();if(g.debug){for(d=0;d<e.bits.Height;d++)for(c=0;c<e.bits.Width;c++)f=8*c+2*d*g.width*4,g.imagedata.data[f]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+1]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+2]=e.bits.get_Renamed(c,d)?255:0;a.putImageData(g.imagedata,0,0)}f=E.decode(e.bits).DataByte;a="";for(d=0;d<f.length;d++)for(c=0;c<f[d].length;c++)a+=String.fromCharCode(f[d][c]);f=(new Date).getTime();console.log(f-b);return g.decode_utf8(a)},getPixel:function(a,b){if(g.width<
|
||||
a)throw"point error";if(g.height<b)throw"point error";var e=4*a+b*g.width*4;return(33*g.imagedata.data[e]+34*g.imagedata.data[e+1]+33*g.imagedata.data[e+2])/100},binarize:function(a){for(var b=Array(g.width*g.height),e=0;e<g.height;e++)for(var d=0;d<g.width;d++){var c=g.getPixel(d,e);b[d+e*g.width]=c<=a?!0:!1}return b},getMiddleBrightnessPerArea:function(a){for(var b=Math.floor(g.width/4),e=Math.floor(g.height/4),d=Array(4),c=0;4>c;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f=
|
||||
0;4>f;f++){d[f][c][0]=255;for(var h=0;h<e;h++)for(var m=0;m<b;m++){var k=a[b*f+m+(e*c+h)*g.width];k<d[f][c][0]&&(d[f][c][0]=k);k>d[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h<e;h++)for(var m=0;m<e;m++)for(var k=
|
||||
0;k<c;k++)for(var n=0;n<d;n++)f[d*m+n+(c*h+k)*g.width]=a[d*m+n+(c*h+k)*g.width]<b[m][h]?!0:!1;return f},grayscale:function(){for(var a=new ArrayBuffer(g.width*g.height),a=new Uint8Array(a),b=0;b<g.height;b++)for(var e=0;e<g.width;e++){var d=g.getPixel(e,b);a[e+b*g.width]=d}return a}},L=3,W=57,D=8,K=2;g.orderBestPatterns=function(a){function b(a,b){var c=a.X-b.X,d=a.Y-b.Y;return Math.sqrt(c*c+d*d)}var e=b(a[0],a[1]),d=b(a[1],a[2]),c=b(a[0],a[2]);d>=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1],
|
||||
e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}();
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -5,4 +5,5 @@
|
|||
0.05: Clock does not start if app Languages is not installed
|
||||
0.06: Improve accuracy
|
||||
0.07: Update to use Bangle.setUI instead of setWatch
|
||||
0.08: Use theme colors, Layout library
|
||||
0.08: Use theme colors, Layout library
|
||||
0.09: Fix time/date disappearing after fullscreen notification
|
||||
|
|
|
@ -24,7 +24,7 @@ function renderBar(l) {
|
|||
return;
|
||||
}
|
||||
const width = this.fraction*l.w;
|
||||
g.fillRect(l.x, l.y, width-1, l.y+l.height-1);
|
||||
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
||||
}
|
||||
|
||||
const Layout = require("Layout");
|
||||
|
@ -78,7 +78,7 @@ function dateText(date) {
|
|||
return `${dayName} ${dayMonth}`;
|
||||
}
|
||||
|
||||
draw = function draw() {
|
||||
draw = function draw(force) {
|
||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
||||
const date = new Date();
|
||||
layout.time.label = timeText(date);
|
||||
|
@ -86,6 +86,10 @@ draw = function draw() {
|
|||
layout.date.label = dateText(date);
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||
if (force) {
|
||||
Bangle.drawWidgets();
|
||||
layout.forgetLazyState();
|
||||
}
|
||||
layout.render();
|
||||
// schedule update at start of next second
|
||||
const millis = date.getMilliseconds();
|
||||
|
@ -96,7 +100,7 @@ draw = function draw() {
|
|||
Bangle.setUI("clock");
|
||||
Bangle.on("lcdPower", function(on) {
|
||||
if (on) {
|
||||
draw();
|
||||
draw(true);
|
||||
}
|
||||
});
|
||||
g.reset().clear();
|
||||
|
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.2 KiB |
|
@ -1,2 +1,4 @@
|
|||
0.01: start of development
|
||||
0.02: first running version for BangleJs2
|
||||
0.03: corrected icon, added screen shot, extended description
|
||||
0.04: corrected format of background image (raw binary)
|
||||
|
|
|
@ -1,10 +1,47 @@
|
|||
# TheBinWatch
|
||||
|
||||
Binary watch to train Your brain
|
||||
Inspired by the 80's LCD wrist watch from RALtec
|
||||
Inspired by the LCD wrist watch from TecRAL from 1989
|
||||
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
- swipe to left or right to change displayed text (date, time, ...)
|
||||
- currently only available for BangeJs2
|
||||
- Widgets will not be shown
|
||||
- If bluetooth connection is not established an icon will show up
|
||||
- If bluetooth connection is not established an icon will show up
|
||||
|
||||
## How it works
|
||||
Binary means that every digit can represent 2 states: 0 or 1, displayed by a black bar.
|
||||
|
||||
The principle is the same like in out well known and daily used decimal system with values from 0 to 9:
|
||||
|
||||
We start from the most right position with the least significant bit (binary digit) which can have the value 0 or 1
|
||||
The 2nd bit from the right can have the value 0 or 2 (sum of all bits to the right set to 1 plus 1).
|
||||
This principle is valid for all the remaining bits.
|
||||
|
||||
Mathematically spoken: the value of a digit is the base number of the system (10 for decimal or 2 for binary)
|
||||
to the power of the position (from the right, starting with 0).
|
||||
That means in numbers: 2^5 = 32, 2^4 = 16, 2^3 = 8, 2^2 = 4, 2^1 = 2, 2^0 = 1
|
||||
|
||||
The upper row represents the hours with 4 bit (2^4 = 16 possible values in total, 12 are used: 1 to 12),
|
||||
the 2nd row represents the minutes with 6 bit (2^6 = 64 possible values in total, 60 are used: 0 to 59).
|
||||
Same holds for the thrid row: 0-59 seconds
|
||||
|
||||
To read the values of a row we summ up the vaules of set bits (black bars).
|
||||
E.g. the picture above, 3rd row (seconds):
|
||||
101001
|
||||
is 1 * 32 + 0 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 1 * 1
|
||||
is (only the '1' bit): 32 + 8 + 1 = 41
|
||||
|
||||
for the minutes we do the same: 32 + 1 = 33
|
||||
and the hours: 8 + 2 = 10
|
||||
|
||||
So the time is 10:33:41 (that's all)
|
||||
|
||||
## TRAIN YOUR BRAIN
|
||||
|
||||
Remark: more infos about the original watch including manual can be found here:
|
||||
https://timeartpiece.com/watches/tech-ral-binary
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("2GwwcCBQ0JkmSpICuoBMNIP4ABH14CCpBAMgRB/IOlJIJkSIOcgIP5BNH2ICDIJ3/AFvkIJsEIOuAIJKSCk4jQ7dt2wCJt4dP/hBc4EAgIOCIJl8EgPyv5BPyBAIgJBCn4GBg4cG/wKBhJBC/ZBLsATByRBM/5BCyRBM/5BMvMkIJ3gDoOSp5BM+RBLhJBCXIIABj4bF+AJBDoOfA4JBLCQMEMoRBPoBBHBZCnFwEAgfJIIftIJPfDYM8IJ3+ILkCCIOTIJnYDYKnCn5BPpBAGF4WSuEHCgRBF/gRBjxBE/pBJcYMBIJ//U4IRBILDjDBYJBJ25BBh4vCk5BXiRxC+BBJ8ARBDQIdBp4JBQZISBhJBQ/IRCkBBFF4WfIJkHII32II++EgN5F4UkHg/8JohBYCAMEIIdJIJWwCYIRDEwJBGkkn/1k85BDkhAEF4f/IJP4CIM8II3+II/wgEDeoZBH/MJQIPJyV/8hBZFgYCBn5BJwEAgSDEyZBF5DDB+f5yUfIIYZBAAQaCKQJBJ4EAgJBH/5BGtgkBiRBK/1CGAPyvhBB/gRCyBBUh5BFCgJBHvgkB+RBEXIJBEJwKDB8mE55BHOIZuBIJH+CIMJIIraB//7IItgCYIRFIIvyiVP8//kkk5//CIZBCF4YVBIJd5IJH/IIvggEHII1PIIfwAwX5kMkzJKBIKnwCIIsFAQOfII4SBghBGFIRBDAwPJGwJBFoAvELIRBIwEAgfJIJPtIIffEgM8IJ0mpAMBIIP+CIVIIKUCQY+TII3YgEB8hBHn5BFmVOIJIvDXgZBG/hSBiRBK/pBD4BBBCIxBIGQKoBIILLBCIQvEIJrdDAQoOBIIe3IIMPIJEnIIlMyfz/JBDAgJBFNYRBI8BBBFg7dEQYYSBhJBIkgmC/0SsmH+V/knPIIsgCgWfIJkHIJn2IIO+IIN5IJsTkknQYRBC/4UGIJYtBghBJpJBE2ATBCJIsD/nJkLMB5MmfYRBHBIRBH/AtBnhBM/xBBwEAgRBPhMn/1JmY2D8hBSgIUGAQk/IIVvIIMeIJWTE4RBC+VJXIZBGSIJBJ4BBBFhJBD//btiWBiRBOHAMnyVkA4aOBIJQnBAARBCh5BLDQXbvgWBOAIUKfwf/LggIFIJt+AQMJIJbgC/dgCYIRLyVPHQoAGIJP+IAcDDhgAkTwhBEAG5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5Bs+EAgFJAoP27dt2wCJB4P8z4DBCJdt34PB5ApBh5BTwEAgfJAoP+IJffIIvtIJYpC5PAIK8CpM/IKHkyZBQ/1JQYMBIKX8CwMSIIX/IJYWCkhBC/pBKt41DnBBXBwILCIJW3IIeSv5BMBoI1CkArBNYRBP8AVBBYMkA4P7IJn5EANPAoJBNGQQrBg5BTg5BE/5BJ35BH+xBJEAQmCFgRBRKwMEDQWfIJ3JEARBO/gmCwAtBIKH4CYM8IIvtIJAWCIIv+IJHfBgPkEwWcIKoLCkmTIJv+EAc/IJQpCIIeQFoMfIJ/AgEBIIeSv///pBHt4gGIIP/IJZoEYwJBSh5BG/5BHBQQgHII+3II2SQYMDIJ3+CQMJDQlPIJgRDAQIHB/ZBJ/ImEoAvBDwRBL+ARBvJBH+xBGCwRBH/5BG35BHpxBTFggCBIJf8IIufIJfJEwlIF4MPIJuAa4IaFIIX+IIvfBIPkIJHtIIocCNA3AIKMCDQ0/II4VCII2TII9vIJKDBgJBM/gQBiQaGBwRBICIpBD/pBHGQgCCnBBRDQ4OC/ZBD25BJyV/IIwHBIJEgGIKtBIJXgB4IsGAQJBJ/JBHp4LBII4mIGIMHIJYOCIJX/IIe/IJv2IIYaCExB0BIJ0EDRGfIJHJII9JIJH8ExGAGYJBK/ANBFhBBD9pBCAoP+CI5BD/xBC74GB8gmIzhBOgIaJyZBEt5BMn5BEGIQmJyBBBj5BJ4BBBQZOSv///pBEDogCFEYRBFExOTYwMDIJcPIJn/IIIECIJv7tu3IJmSQYJBJ/wMBhIaKp5BGCJICBII35ExVAGoIkBII3wBYN5IJv27ZvCIJv/tu/AYPJExVOIJosKAQJBF/hBLz5BCIoRBLpA1Bh5BHwEAgRBO/3fAYPkIJ3tCwQmM4BBZn5tCIJ2TB4PvIJ6DBgJBHAHRB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5BmwEAgO27dtARIZCkASBCJfbt4SB+YCB/omM4AjBIJMDDRe3IIUngEAjZBLv5uCAYJBM25BBh5BH+AuBmxBN/MkCQMDIJ2Sp4DBQZgiBIJH+BYN2DRW/IIfggEHIJaWCIIf2ExW+GoJXBIJMGIJvJkjZBgBBN/gpBIJuwIJX/aIMADRQPB/wXBzgSB9pBJ74TB8hBD/wmKMYMDCAJBJgJBPyBBBhpBJYgRBCn5BLt5BBj5BJ/AuBjYaJC4oSBgJBMFIRBB/5BJtggBIJs7DRDcBC4lwUgJBI25BFFIRBJvgzBCoRBH/4NBgZBLCgIXBoATBmxBK/IpCkgGB/YmIsBBN8EAg4aIBwRBDpwhBuxBH35BI/4mIDwMHIJsAIJX8IIdICQMGIJXJIIefIJPfIJ38B4MNDQ4NB8hBDpISBhxBHCQP+CIZBD9omG7AeBn5BOjpBPnATBII1vII+TIJP4gEBG4RBJ/+ACAIaGBgQsDAQMgIIMbIJApEAQN///9Ew3AIJ/wgEDDQu3IJEnIIM7IIo3BIJP/EwxBBh5BPgE2II/5IIskCQMDIJARFyVPII+2DgJBO/wRBuwaE34LB5JBG8EAg5BFCQP8IJP2EwmwF4JXCIJ0GIJ+ACYJBE75BJpJBHWYRBO/7XBgAaEJgQsGkmQCQPtII3kIJP+EwhdBgY2EIJkBDQdvIJWTEwMNIIYdCIJE/IItvDQMfIJ/4OAMbIIoUEAQgSBgJBGCI4sDIIdsDQJBQ/4TBnYaCbgRBJuCqBIIW3IJ37EwV8FoI1FIJsDIIosHAQNACYM2IIn5IJEkIItgIKfgCgIaCA4P8IJNOCQN2IIO/CYPJIJf/EwQYBg5BU9pBOpASBgxBBDYRBKz5BD74YBn5BR/gVBhoaBA4PkIJNJCQMAIIf+CJJBDNAPYIK8d2wHCIJc4gEB7dvIJuTIIf4C4I0FIJn/wAWBIIYsJAQMgKoMbIIQmEAQ9///923AIKvwgED25BOk5BBnYxBIJ//2wWBh40GEwpBIgE3AoP5IJckCQMDGIQRLyVPB4O+IKwAz/hWFIP5B88hBFz4SK8EAn4HE4EAgJBjHwYCCyYSKjkAg///xEB/0AAAJKFABXwCYMPIKUSIJsDwEAFII7CQYnxSol/ILP5IIUgIIWSEZAABgFwgEfwBBBwIKC45KECIIdG4H8HwQREIJ0CIJ1+gCGBn/8QATIBF4LRB//4ILfJIISYBIIVPIJV/4BBp/w7CpBBR/BBwhKJCIJYDBINHyIIVAIIoXJIOcBIKAmBIIYyBIIgREIKw4BHYJABIIknChHjAwuPc4ovDCIxBS/hBGgBBMADfwFYJECIJuQIIcEBASDPABUfILHkHAWAIKD1DVQP8gK2DBAMHAoRBHYqJBIgAICz5BLwBBE/0AIL7+CkhAEIIeTIK4FBIMcSILT7BDI5BQ/JBCkBBFgRBByQ4CIKmAZARBW5JBCIApBb/kAGRBBbgBBCp5BM/+BBQXHIIXgJQRBW/w1CpBBKpJBKEwfAgA7CIIMAGoRBjhJBJgYbDEwLCBAAIFBQYTdHAAfwCYJQJ//yIIVAIJbgKOIiDFCZhBagJBRVAoUTAA4yBGoJAHAAJBCk4saACf8IIWQIP5BLggOCIN3kGQWAIJufIPkABwQCyIBUAiRBzkBB/IJsCIOZALIP4ADIOVIIJsJIONAHQwA=="))
|
||||
require("heatshrink").decompress(atob("mEwwcCgEBkmSpICKCwQRRhMn/4AK+VACIU4A4PAz+27dt20ECI1IgEDCIOT+wRB2EkCIX+BwMCpE/8f+gmSvwRB2Mkz///v/5IRBpwRHwIRC5PzCIMSCIXwMQNP7dshMkyf/p+G/MgiV+CIPxCJFM8gRByf+CIIvBRIP7sCMCv/h8//C4P+g6ABCIdiCIVP/M///kFIPAj6iLCIYAOCPH4ibUC2zABdgW/8ARFUgILB2/8fwf/kB3BPobUD3/kz4pCTwMDCIrCBCIWTCINv/IREfAVJDoYpCv/JkmAv4RCYQYRM+ARCn4vCHYX+bQOQh4RBfAYRJyUBCI3/F4IFB/4RGdP4RHwDmC7/gmzaC//tbQWBR4UbfAWQgzIDfwVsR4QRCfAIRM/0DCIWSgDaDz4RBsDXDCIIdByVAfAb+CCIf/4AREjYRFgZ9D/D4DpEDfAT+Cj4REhoRJ7ARE/8PfAVJgbmDp/YWZHgv6zIkkSBYWB44sB/4CB/AREkESp4EBx4RBx0/CIPACAf5kECCIQAHPQIAB5MAgVJEYs4AwIjECIMACI0ACIv+pARCn5rDvwFDGoQRDhILDABHyoARBgKeCARQQBCKIA=="))
|
|
@ -12,7 +12,6 @@
|
|||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
require("Font5x7Numeric7Seg").add(Graphics);
|
||||
|
||||
|
||||
/* constants and definitions */
|
||||
|
||||
/* Bangle 2: 176 x 176 */
|
||||
|
@ -63,7 +62,7 @@ const V2_BAT_SIZE_Y = 2;
|
|||
|
||||
const V2_SCREEN_SIZE_X = 176;
|
||||
const V2_SCREEN_SIZE_Y = 176;
|
||||
const V2_BACKGROUND_IMAGE = "Background176_center.png";
|
||||
const V2_BACKGROUND_IMAGE = "binwatch.bg176.img";
|
||||
const V2_BG_COLOR = 0;
|
||||
const V2_FG_COLOR = 1;
|
||||
|
||||
|
@ -91,7 +90,7 @@ const V1_BAT_SIZE_X = 3;
|
|||
const V1_BAT_SIZE_Y = 5;
|
||||
const V1_SCREEN_SIZE_X = 240;
|
||||
const V1_SCREEN_SIZE_Y = 240;
|
||||
const V1_BACKGROUND_IMAGE = "Background240_center.png";
|
||||
const V1_BACKGROUND_IMAGE = "binwatch.bg240.img";
|
||||
const V1_BG_COLOR = 1;
|
||||
const V1_FG_COLOR = 0;
|
||||
|
||||
|
@ -293,7 +292,7 @@ function setRuntimeValues(resolution) {
|
|||
bat_size_x = V1_BAT_SIZE_X;
|
||||
bat_size_y = V1_BAT_SIZE_Y;
|
||||
|
||||
setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"});
|
||||
setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"});
|
||||
|
||||
} else {
|
||||
x_step = V2_X_STEP;
|
||||
|
@ -362,8 +361,7 @@ function draw() {
|
|||
updateVTime();
|
||||
g.clear();
|
||||
g.drawImages([{image:cgimg},
|
||||
{image:require("Storage").read(backgroundImage)},
|
||||
// { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")},
|
||||
{image:require("Storage").read(backgroundImage)}
|
||||
]);
|
||||
drawBT(g, NRF.getSecurityStatus().connected);
|
||||
// Bangle.drawWidgets();
|
||||
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
|
@ -40,3 +40,4 @@
|
|||
0.35: Add Bangle.appRect polyfill
|
||||
Don't set beep vibration up on Bangle.js 2 (built in)
|
||||
0.36: Add comments to .boot0 to make debugging a bit easier
|
||||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||
|
|
|
@ -78,13 +78,7 @@ boot += `E.on('errorFlag', function(errorFlags) {
|
|||
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
||||
// Apply any settings-specific stuff
|
||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.quiet && s.qmOptions) boot+=`Bangle.setOptions(${E.toJS(s.qmOptions)});\n`;
|
||||
if (s.quiet && s.qmBrightness) {
|
||||
if (s.qmBrightness!=1) boot+=`Bangle.setLCDBrightness(${s.qmBrightness});\n`;
|
||||
} else {
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
}
|
||||
if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Bangle.js 2 compatibility
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
g.setBgColor(0, 0, 0);
|
||||
g.clear().flip();
|
||||
var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA=="));
|
||||
var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
||||
var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa"));
|
||||
var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA=="));
|
||||
|
||||
var W=240,H=240;
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var b2v = (W != 240)?-1:1;
|
||||
var b2rot = (W != 240)?Math.PI:0;
|
||||
var b2scale = W/240.0;
|
||||
var bubbles = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
||||
|
@ -12,12 +16,16 @@ function anim() {
|
|||
/* we don't use any kind of buffering here. Just draw one image
|
||||
at a time (image contains a background) too, and there is minimal
|
||||
flicker. */
|
||||
var mx = 120, my = 120;
|
||||
var mx = W/2.0, my = H/2.0;
|
||||
bubbles.forEach(f=>{
|
||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
||||
f.y-=f.v * b2v;
|
||||
if (f.y<-24)
|
||||
f.y=H+8;
|
||||
else if (f.y > (H+8))
|
||||
f.y=0;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot});
|
||||
});
|
||||
g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2});
|
||||
g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot});
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -1,3 +1,5 @@
|
|||
0.01: New widget and app!
|
||||
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
|
||||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.03: Display only minutes:seconds when less than 1 hour left
|
||||
0.04: Change to 7 segment font, move to top widget bar
|
||||
Better auto-update behaviour, less RAM used
|
||||
|
|
|
@ -5,14 +5,13 @@ The advantage is, that you can still see your normal watchface and other widgets
|
|||
The widget is always active, but only shown when the timer is on.
|
||||
Hours, minutes, seconds and timer status can be set with an app.
|
||||
|
||||
When there is less than one seconds left on the timer it buzzes.
|
||||
When there is less than one second left on the timer it buzzes.
|
||||
|
||||
The widget has been tested on Bangle 1 and Bangle 2
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
@ -28,15 +27,15 @@ There are no settings section in the settings app, timer can be set using an app
|
|||
* Hours: Set the hours for the timer
|
||||
* Minutes: Set the minutes for the timer
|
||||
* Seconds: Set the seconds for the timer
|
||||
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
||||
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
||||
|
||||
|
||||
## Releases
|
||||
|
||||
* Offifical app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
||||
* Official app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
||||
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
|
||||
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
|
||||
|
||||
## Requests
|
||||
|
||||
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
||||
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
||||
|
|
|
@ -3,7 +3,6 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
|
||||
const storage = require('Storage');
|
||||
const boolFormat = v => v ? "On" : "Off";
|
||||
let settingsChronowid;
|
||||
|
||||
function updateSettings() {
|
||||
|
@ -12,6 +11,7 @@ function updateSettings() {
|
|||
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
|
||||
settingsChronowid.goal = goal.getTime();
|
||||
storage.writeJSON('chronowid.json', settingsChronowid);
|
||||
if (WIDGETS["chronowid"]) WIDGETS["chronowid"].reload();
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
|
@ -44,6 +44,7 @@ function showMenu() {
|
|||
timerMenu.started.value = settingsChronowid.started;
|
||||
}
|
||||
},
|
||||
'< Back' : ()=>{load();},
|
||||
'Reset values': function() {
|
||||
settingsChronowid.hours = 0;
|
||||
settingsChronowid.minutes = 0;
|
||||
|
@ -84,15 +85,15 @@ function showMenu() {
|
|||
},
|
||||
'Timer on': {
|
||||
value: settingsChronowid.started,
|
||||
format: boolFormat,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settingsChronowid.started = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
timerMenu['-Exit-'] = ()=>{load();};
|
||||
|
||||
return E.showMenu(timerMenu);
|
||||
}
|
||||
|
||||
showMenu();
|
||||
showMenu();
|
||||
|
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,93 +1,79 @@
|
|||
(() => {
|
||||
const storage = require('Storage');
|
||||
settingsChronowid = storage.readJSON("chronowid.json",1)||{}; //read settingsChronowid from file
|
||||
var height = 23;
|
||||
var width = 58;
|
||||
var settingsChronowid;
|
||||
var interval = 0; //used for the 1 second interval timer
|
||||
var now = new Date();
|
||||
var diff;
|
||||
|
||||
var time = 0;
|
||||
var diff = settingsChronowid.goal - now;
|
||||
|
||||
//Convert ms to time
|
||||
function getTime(t) {
|
||||
var milliseconds = parseInt((t % 1000) / 100),
|
||||
seconds = Math.floor((t / 1000) % 60),
|
||||
minutes = Math.floor((t / (1000 * 60)) % 60),
|
||||
hours = Math.floor((t / (1000 * 60 * 60)) % 24);
|
||||
|
||||
hours = (hours < 10) ? "0" + hours : hours;
|
||||
minutes = (minutes < 10) ? "0" + minutes : minutes;
|
||||
seconds = (seconds < 10) ? "0" + seconds : seconds;
|
||||
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
return hours.toString().padStart(2,0) + ":" + minutes.toString().padStart(2,0) + ":" + seconds.toString().padStart(2,0);
|
||||
}
|
||||
|
||||
function printDebug() {
|
||||
print ("Nowtime: " + getTime(now));
|
||||
print ("Now: " + now);
|
||||
/*function printDebug() {
|
||||
print ("Goaltime: " + getTime(settingsChronowid.goal));
|
||||
print ("Goal: " + settingsChronowid.goal);
|
||||
print("Difftime: " + getTime(diff));
|
||||
print("Diff: " + diff);
|
||||
print ("Started: " + settingsChronowid.started);
|
||||
print ("----");
|
||||
}
|
||||
}*/
|
||||
|
||||
//counts down, calculates and displays
|
||||
function countDown() {
|
||||
now = new Date();
|
||||
var now = new Date();
|
||||
diff = settingsChronowid.goal - now; //calculate difference
|
||||
WIDGETS["chronowid"].draw();
|
||||
//time is up
|
||||
// time is up
|
||||
if (settingsChronowid.started && diff < 1000) {
|
||||
Bangle.buzz(1500);
|
||||
//write timer off to file
|
||||
settingsChronowid.started = false;
|
||||
storage.writeJSON('chronowid.json', settingsChronowid);
|
||||
require('Storage').writeJSON('chronowid.json', settingsChronowid);
|
||||
clearInterval(interval); //stop interval
|
||||
interval = undefined;
|
||||
}
|
||||
//printDebug();
|
||||
// calculates width and redraws accordingly
|
||||
WIDGETS["chronowid"].redraw();
|
||||
}
|
||||
|
||||
// draw your widget
|
||||
function draw() {
|
||||
if (!settingsChronowid.started) {
|
||||
width = 0;
|
||||
return; //do not draw anything if timer is not started
|
||||
}
|
||||
g.reset();
|
||||
if (diff >= 0) {
|
||||
if (diff < 3600000) { //less than 1 hour left
|
||||
width = 58;
|
||||
g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(getTime(diff).substring(3), this.x+1, this.y+5); //remove hour part 00:00:00 -> 00:00
|
||||
}
|
||||
if (diff >= 3600000) { //one hour or more left
|
||||
width = 48;
|
||||
g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(getTime(diff), this.x+1, this.y+((height/2)-4)); //display hour 00:00:00
|
||||
}
|
||||
}
|
||||
// not needed anymoe, because we check if diff < 1000 now, so 00:00 is displayed.
|
||||
// else {
|
||||
// width = 58;
|
||||
// g.clearRect(this.x,this.y,this.x+width,this.y+height);
|
||||
// g.setFont("6x8", 2);
|
||||
// g.drawString("END", this.x+15, this.y+5);
|
||||
// }
|
||||
}
|
||||
|
||||
if (settingsChronowid.started) interval = setInterval(countDown, 1000); //start countdown each second
|
||||
|
||||
// add the widget
|
||||
WIDGETS["chronowid"]={area:"bl",width:width,draw:draw,reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
WIDGETS["chronowid"]={area:"tl",width:0,draw:function() {
|
||||
if (!this.width) return;
|
||||
g.reset().setFontAlign(0,0).clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
//g.drawRect(this.x,this.y,this.x+this.width-1, this.y+23);
|
||||
var scale;
|
||||
var timeStr;
|
||||
if (diff < 3600000) { //less than 1 hour left
|
||||
width = 58;
|
||||
scale = 2;
|
||||
timeStr = getTime(diff).substring(3); // remove hour part 00:00:00 -> 00:00
|
||||
} else { //one hour or more left
|
||||
width = 48;
|
||||
scale = 1;
|
||||
timeStr = getTime(diff); //display hour 00:00:00 but small
|
||||
}
|
||||
// Font5x9Numeric7Seg - just build this in as it's tiny
|
||||
g.setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 9 + (scale<<8));
|
||||
g.drawString(timeStr, this.x+this.width/2, this.y+12);
|
||||
}, redraw:function() {
|
||||
var last = this.width;
|
||||
if (!settingsChronowid.started) this.width = 0;
|
||||
else this.width = (diff < 3600000) ? 58 : 48;
|
||||
if (last != this.width) Bangle.drawWidgets();
|
||||
else this.draw();
|
||||
}, reload:function() {
|
||||
settingsChronowid = require('Storage').readJSON("chronowid.json",1)||{};
|
||||
if (interval) clearInterval(interval);
|
||||
interval = undefined;
|
||||
// start countdown each second
|
||||
if (settingsChronowid.started) interval = setInterval(countDown, 1000);
|
||||
// reset everything
|
||||
countDown();
|
||||
}};
|
||||
|
||||
//printDebug();
|
||||
countDown();
|
||||
})();
|
||||
// set width correctly, start countdown each second
|
||||
WIDGETS["chronowid"].reload();
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Submitted to App Loader
|
||||
0.02: Removed unneded code, added HID controlls thanks to t0m1o1 for his code :p
|
|
@ -4,24 +4,96 @@ var fontsizeTime = g.getWidth()>200 ? 4 : 4;
|
|||
var fontheight = 10*fontsize;
|
||||
var fontheightTime = 10*fontsizeTime;
|
||||
var locale = require("locale");
|
||||
var marginTop = 40;
|
||||
var marginTop = 25;
|
||||
var flag = false;
|
||||
|
||||
var hrtOn = false;
|
||||
var hrtStr = "Hrt: ??? bpm";
|
||||
var storage = require('Storage');
|
||||
|
||||
const NONE_MODE = "none";
|
||||
const ID_MODE = "id";
|
||||
const VER_MODE = "ver";
|
||||
const BATT_MODE = "batt";
|
||||
const MEM_MODE = "mem";
|
||||
const STEPS_MODE = "step";
|
||||
const HRT_MODE = "hrt";
|
||||
const NONE_FN_MODE = "no_fn";
|
||||
const HRT_FN_MODE = "fn_hrt";
|
||||
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||
|
||||
var sendHid, next, prev, toggle, up, down, profile;
|
||||
var lasty = 0;
|
||||
var lastx = 0;
|
||||
|
||||
if (settings.HID=="kbmedia") {
|
||||
profile = 'Music';
|
||||
sendHid = function (code, cb) {
|
||||
try {
|
||||
NRF.sendHIDReport([1,code], () => {
|
||||
NRF.sendHIDReport([1,0], () => {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
};
|
||||
next = function (cb) { sendHid(0x01, cb); };
|
||||
prev = function (cb) { sendHid(0x02, cb); };
|
||||
toggle = function (cb) { sendHid(0x10, cb); };
|
||||
up = function (cb) {sendHid(0x40, cb); };
|
||||
down = function (cb) { sendHid(0x80, cb); };
|
||||
} else {
|
||||
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||
if (enable) {
|
||||
settings.HID = "kbmedia";
|
||||
require("Storage").write('setting.json', settings);
|
||||
setTimeout(load, 1000, "hidmsicswipe.app.js");
|
||||
} else setTimeout(load, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
if (next) {
|
||||
setWatch(function(e) {
|
||||
var len = e.time - e.lastTime;
|
||||
E.showMessage('lock');
|
||||
setTimeout(drawApp, 1000);
|
||||
Bangle.setLocked(true);
|
||||
}, BTN1, { edge:"falling",repeat:true,debounce:50});
|
||||
Bangle.on('drag', function(e) {
|
||||
if(!e.b){
|
||||
console.log(lasty);
|
||||
console.log(lastx);
|
||||
if(lasty > 40){
|
||||
writeLine('Down', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"volumedown"}));
|
||||
down(() => {});
|
||||
}
|
||||
else if(lasty < -40){
|
||||
writeLine('Up', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
//Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"}));
|
||||
|
||||
up(() => {});
|
||||
} else if(lastx < -40){
|
||||
writeLine('Prev', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"previous"}));
|
||||
prev(() => {});
|
||||
} else if(lastx > 40){
|
||||
writeLine('Next', 3);
|
||||
// setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"next"}));
|
||||
next(() => {});
|
||||
} else if(lastx==0 && lasty==0){
|
||||
writeLine('play/pause', 3);
|
||||
//setTimeout(drawApp, 1000);
|
||||
// Bluetooth.println(JSON.stringify({t:"music", n:"play"}));
|
||||
|
||||
toggle(() => {});
|
||||
}
|
||||
lastx = 0;
|
||||
lasty = 0;
|
||||
}
|
||||
else{
|
||||
lastx = lastx + e.dx;
|
||||
lasty = lasty + e.dy;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let infoMode = NONE_MODE;
|
||||
let functionMode = NONE_FN_MODE;
|
||||
|
||||
let textCol = g.theme.dark ? "#0f0" : "#080";
|
||||
|
||||
|
@ -33,13 +105,12 @@ function drawAll(){
|
|||
function updateRest(now){
|
||||
writeLine(locale.dow(now),1);
|
||||
writeLine(locale.date(now,1),2);
|
||||
drawInfo(5);
|
||||
}
|
||||
function updateTime(){
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
let now = new Date();
|
||||
writeLine(locale.time(now,1),0);
|
||||
writeLine(flag?" ":"_",3);
|
||||
writeLine(flag?" ":"_ ",3);
|
||||
flag = !flag;
|
||||
if(now.getMinutes() == 0)
|
||||
updateRest(now);
|
||||
|
@ -65,142 +136,13 @@ function writeLine(str,line){
|
|||
var y = marginTop+(line-1)*fontheight+fontheightTime;
|
||||
g.setFont("6x8",fontsize);
|
||||
g.setColor(textCol).setFontAlign(-1,-1);
|
||||
g.clearRect(0,y,((str.length+1)*20),y+fontheight-1);
|
||||
g.clearRect(0,y,((str.length+10)*40),y+fontheightTime-1);
|
||||
writeLineStart(line);
|
||||
g.drawString(str,25,y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function drawInfo(line) {
|
||||
let val;
|
||||
let str = "";
|
||||
let col = textCol; // green
|
||||
|
||||
//console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode);
|
||||
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
col = g.theme.dark ? "#0ff": "#088"; // cyan
|
||||
str = "HRM: " + (hrtOn ? "ON" : "OFF");
|
||||
drawModeLine(line,str,col);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(infoMode) {
|
||||
case NONE_MODE:
|
||||
col = g.theme.bg;
|
||||
str = "";
|
||||
break;
|
||||
case HRT_MODE:
|
||||
str = hrtStr;
|
||||
break;
|
||||
case STEPS_MODE:
|
||||
str = "Steps: " + stepsWidget().getSteps();
|
||||
break;
|
||||
case ID_MODE:
|
||||
val = NRF.getAddress().split(":");
|
||||
str = "Id: " + val[4] + val[5];
|
||||
break;
|
||||
case VER_MODE:
|
||||
str = "Fw: " + process.env.VERSION;
|
||||
break;
|
||||
case MEM_MODE:
|
||||
val = process.memory();
|
||||
str = "Memory: " + Math.round(val.usage*100/val.total) + "%";
|
||||
break;
|
||||
case BATT_MODE:
|
||||
default:
|
||||
str = "Battery: " + E.getBattery() + "%";
|
||||
}
|
||||
|
||||
drawModeLine(line,str,col);
|
||||
}
|
||||
|
||||
function drawModeLine(line, str, col) {
|
||||
g.setColor(col);
|
||||
var y = marginTop+line*fontheight;
|
||||
g.fillRect(0, y, 239, y+fontheight-1);
|
||||
g.setColor(g.theme.bg).setFontAlign(0, 0);
|
||||
g.drawString(str, g.getWidth()/2, y+fontheight/2);
|
||||
}
|
||||
|
||||
function changeInfoMode() {
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
hrtOn = !hrtOn;
|
||||
Bangle.buzz();
|
||||
Bangle.setHRMPower(hrtOn ? 1 : 0);
|
||||
if (hrtOn) infoMode = HRT_MODE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(infoMode) {
|
||||
case NONE_MODE:
|
||||
if (stepsWidget() !== undefined)
|
||||
infoMode = hrtOn ? HRT_MODE : STEPS_MODE;
|
||||
else
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case HRT_MODE:
|
||||
if (stepsWidget() !== undefined)
|
||||
infoMode = STEPS_MODE;
|
||||
else
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case STEPS_MODE:
|
||||
infoMode = ID_MODE;
|
||||
break;
|
||||
case ID_MODE:
|
||||
infoMode = VER_MODE;
|
||||
break;
|
||||
case VER_MODE:
|
||||
infoMode = BATT_MODE;
|
||||
break;
|
||||
case BATT_MODE:
|
||||
infoMode = MEM_MODE;
|
||||
break;
|
||||
case MEM_MODE:
|
||||
default:
|
||||
infoMode = NONE_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
function changeFunctionMode() {
|
||||
//console.log("changeFunctionMode()");
|
||||
switch(functionMode) {
|
||||
case NONE_FN_MODE:
|
||||
functionMode = HRT_FN_MODE;
|
||||
break;
|
||||
case HRT_FN_MODE:
|
||||
default:
|
||||
functionMode = NONE_FN_MODE;
|
||||
}
|
||||
//console.log(functionMode);
|
||||
|
||||
}
|
||||
|
||||
function stepsWidget() {
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
return WIDGETS.activepedom;
|
||||
} else if (WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
if(hrm.confidence > 90){
|
||||
hrtStr = "Hrt: " + hrm.bpm + " bpm";
|
||||
} else {
|
||||
hrtStr = "Hrt: ??? bpm";
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
@ -211,6 +153,5 @@ Bangle.on('lcdPower',function(on) {
|
|||
var click = setInterval(updateTime, 1000);
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clockupdown", btn=>{
|
||||
if (btn<0) changeInfoMode();
|
||||
drawAll();
|
||||
});
|
||||
|
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -2,3 +2,4 @@
|
|||
0.02: Show text if uncalibrated
|
||||
0.03: Eliminate flickering
|
||||
0.04: Fix for Bangle.js 2 and themes
|
||||
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
|
||||
|
|
|
@ -48,7 +48,7 @@ Bangle.on('mag', function(m) {
|
|||
}
|
||||
g.setFontAlign(0,0).setFont("6x8",3);
|
||||
var y = 36;
|
||||
g.clearRect(M-40,y,M+40,y+24);
|
||||
g.clearRect(M-40,24,M+40,48);
|
||||
g.drawString(Math.round(m.heading),M,y,true);
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -3,3 +3,5 @@
|
|||
0.03: Save total distance traveled
|
||||
0.04: Add sensor battery level indicator
|
||||
0.05: Add cadence sensor support
|
||||
0.06: Now read wheel rev as well as cadence sensor
|
||||
Improve connection code
|
||||
|
|
|
@ -9,10 +9,16 @@ Currently the app displays the following data:
|
|||
- maximum speed
|
||||
- trip distance traveled
|
||||
- total distance traveled
|
||||
- an icon with the battery status of the remote sensor
|
||||
- an icon with the battery status of the remote sensor
|
||||
|
||||
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
|
||||
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor.
|
||||
Button 2 switches between the display for cycling speed and cadence.
|
||||
|
||||
Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app.
|
||||
|
||||
# TODO
|
||||
|
||||
* Use Layout Library to provide proper Bangle.js 2 support
|
||||
* Turn CSC sensor support into a library
|
||||
* Support for `Recorder` app, to allow CSC readings to be logged alongside GPS
|
||||
|
|
|
@ -5,6 +5,8 @@ var characteristic;
|
|||
|
||||
const SETTINGS_FILE = 'cscsensor.json';
|
||||
const storage = require('Storage');
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
class CSCSensor {
|
||||
constructor() {
|
||||
|
@ -75,7 +77,7 @@ class CSCSensor {
|
|||
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
|
||||
var ddist = Math.round(100*dist)/100;
|
||||
var tdist = Math.round(this.distFactor*this.totaldist*10)/10;
|
||||
var dspeed = Math.round(10*this.distFactor*this.speed)/10;
|
||||
var dspeed = Math.round(10*this.distFactor*this.speed)/10;
|
||||
var dmins = Math.floor(this.movingTime/60).toString();
|
||||
if (dmins.length<2) dmins = "0"+dmins;
|
||||
var dsecs = (Math.floor(this.movingTime) % 60).toString();
|
||||
|
@ -152,7 +154,7 @@ class CSCSensor {
|
|||
var qChanged = false;
|
||||
if (event.target.uuid == "0x2a5b") {
|
||||
if (event.target.value.getUint8(0, true) & 0x2) {
|
||||
// crank revolution
|
||||
// crank revolution - if enabled
|
||||
const crankRevs = event.target.value.getUint16(1, true);
|
||||
const crankTime = event.target.value.getUint16(3, true);
|
||||
if (crankTime > this.lastCrankTime) {
|
||||
|
@ -161,44 +163,43 @@ class CSCSensor {
|
|||
}
|
||||
this.lastCrankRevs = crankRevs;
|
||||
this.lastCrankTime = crankTime;
|
||||
} else {
|
||||
// wheel revolution
|
||||
var wheelRevs = event.target.value.getUint32(1, true);
|
||||
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
|
||||
if (dRevs>0) {
|
||||
qChanged = true;
|
||||
this.totaldist += dRevs*this.wheelCirc/63360.0;
|
||||
if ((this.totaldist-this.settings.totaldist)>0.1) {
|
||||
this.settings.totaldist = this.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
}
|
||||
}
|
||||
this.lastRevs = wheelRevs;
|
||||
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
|
||||
var wheelTime = event.target.value.getUint16(5, true);
|
||||
var dT = (wheelTime-this.lastTime)/1024;
|
||||
var dBT = (Date.now()-this.lastBangleTime)/1000;
|
||||
this.lastBangleTime = Date.now();
|
||||
if (dT<0) dT+=64;
|
||||
if (Math.abs(dT-dBT)>3) dT = dBT;
|
||||
this.lastTime = wheelTime;
|
||||
this.speed = this.lastSpeed;
|
||||
if (dRevs>0 && dT>0) {
|
||||
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
|
||||
this.speedFailed = 0;
|
||||
this.movingTime += dT;
|
||||
}
|
||||
else {
|
||||
this.speedFailed++;
|
||||
qChanged = false;
|
||||
if (this.speedFailed>3) {
|
||||
this.speed = 0;
|
||||
qChanged = (this.lastSpeed>0);
|
||||
}
|
||||
}
|
||||
this.lastSpeed = this.speed;
|
||||
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
|
||||
}
|
||||
// wheel revolution
|
||||
var wheelRevs = event.target.value.getUint32(1, true);
|
||||
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
|
||||
if (dRevs>0) {
|
||||
qChanged = true;
|
||||
this.totaldist += dRevs*this.wheelCirc/63360.0;
|
||||
if ((this.totaldist-this.settings.totaldist)>0.1) {
|
||||
this.settings.totaldist = this.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
}
|
||||
}
|
||||
this.lastRevs = wheelRevs;
|
||||
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
|
||||
var wheelTime = event.target.value.getUint16(5, true);
|
||||
var dT = (wheelTime-this.lastTime)/1024;
|
||||
var dBT = (Date.now()-this.lastBangleTime)/1000;
|
||||
this.lastBangleTime = Date.now();
|
||||
if (dT<0) dT+=64;
|
||||
if (Math.abs(dT-dBT)>3) dT = dBT;
|
||||
this.lastTime = wheelTime;
|
||||
this.speed = this.lastSpeed;
|
||||
if (dRevs>0 && dT>0) {
|
||||
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
|
||||
this.speedFailed = 0;
|
||||
this.movingTime += dT;
|
||||
}
|
||||
else {
|
||||
this.speedFailed++;
|
||||
qChanged = false;
|
||||
if (this.speedFailed>3) {
|
||||
this.speed = 0;
|
||||
qChanged = (this.lastSpeed>0);
|
||||
}
|
||||
}
|
||||
this.lastSpeed = this.speed;
|
||||
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
|
||||
}
|
||||
if (qChanged && this.qUpdateScreen) this.updateScreen();
|
||||
}
|
||||
|
@ -215,44 +216,47 @@ function getSensorBatteryLevel(gatt) {
|
|||
});
|
||||
}
|
||||
|
||||
function parseDevice(d) {
|
||||
device = d;
|
||||
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Found device", 120, 120).flip();
|
||||
device.gatt.connect().then(function(ga) {
|
||||
gatt = ga;
|
||||
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Connected", 120, 120).flip();
|
||||
return gatt.getPrimaryService("1816");
|
||||
}).then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic("2a5b");
|
||||
}).then(function(c) {
|
||||
characteristic = c;
|
||||
characteristic.on('characteristicvaluechanged', (event)=>mySensor.updateSensor(event));
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
g.clearRect(0, 60, 239, 239).setColor(1, 1, 1).flip();
|
||||
getSensorBatteryLevel(gatt);
|
||||
mySensor.updateScreen();
|
||||
}).catch(function(e) {
|
||||
g.clearRect(0, 60, 239, 239).setColor(1, 0, 0).setFontAlign(0, 0, 0).drawString("ERROR"+e, 120, 120).flip();
|
||||
console.log(e);
|
||||
})}
|
||||
|
||||
function connection_setup() {
|
||||
NRF.setScan();
|
||||
mySensor.screenInit = true;
|
||||
NRF.setScan(parseDevice, { filters: [{services:["1816"]}], timeout: 2000});
|
||||
g.clearRect(0, 48, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
|
||||
g.drawString("Scanning for CSC sensor...", 120, 120);
|
||||
E.showMessage("Scanning for CSC sensor...");
|
||||
NRF.requestDevice({ filters: [{services:["1816"]}]}).then(function(d) {
|
||||
device = d;
|
||||
E.showMessage("Found device");
|
||||
return device.gatt.connect();
|
||||
}).then(function(ga) {
|
||||
gatt = ga;
|
||||
E.showMessage("Connected");
|
||||
return gatt.getPrimaryService("1816");
|
||||
}).then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic("2a5b");
|
||||
}).then(function(c) {
|
||||
characteristic = c;
|
||||
characteristic.on('characteristicvaluechanged', (event)=>mySensor.updateSensor(event));
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
g.reset().clearRect(Bangle.appRect).flip();
|
||||
getSensorBatteryLevel(gatt);
|
||||
mySensor.updateScreen();
|
||||
}).catch(function(e) {
|
||||
E.showMessage(e.toString(), "ERROR");
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
connection_setup();
|
||||
setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20});
|
||||
E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); });
|
||||
setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20});
|
||||
setWatch(function() { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN2, {repeat:true, debounce:20});
|
||||
NRF.on('disconnect', connection_setup);
|
||||
E.on('kill',()=>{
|
||||
if (gatt!=undefined) gatt.disconnect();
|
||||
mySensor.settings.totaldist = mySensor.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, mySensor.settings);
|
||||
});
|
||||
NRF.on('disconnect', connection_setup); // restart if disconnected
|
||||
Bangle.setUI("updown", d=>{
|
||||
if (d<0) { mySensor.reset(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); }
|
||||
if (d==0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }
|
||||
if (d>0) { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); }
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -1,2 +1,4 @@
|
|||
0.01: Initial Release
|
||||
0.02: Replace icon with one found on https://icons8.com
|
||||
0.03: Re-render icon fixing display in settings
|
||||
0.04: Improved UX and display solve time
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# Cube Scramble
|
||||
|
||||
A random scramble generator for the 3x3 Rubik's cube
|
||||
A random scramble generator for the 3x3 Rubik's cube with a basic timer.
|
||||
|
||||
## Future features
|
||||
|
||||
I'm keen to complete this project with
|
||||
|
||||
* Add a timer
|
||||
* Add the ability for times to be stored and exported
|
||||
|
||||
## Requests
|
||||
|
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("AH4A6iIAQCwkBC6MQC4kYxAACwMT/4ACmMe9wAC6IXLj4XD+IXE8IX/C9/zR4oXOmYABC6UTCwQXZjrXKf5IAHC713AAURindAAVBiatDmIXFi4XDuMdC4fRYooX/5nBC6Xc5gABC6UcCwQXF+aPMC471DC6MTCwQXHa4V2szXBC4bXBC5YAQC7se9wAC6MYxAACwJTCAAIXL8IXFQYoX/C/4XtjrXNu1ma4z/JAA4XEgAXRCwgA/AGo"))
|
||||
require("heatshrink").decompress(atob("mEwwhC/AHcRACAWEgIXRiAXEjGIAAWBif/AAUxj3uAAXRC5cfC4fxC4nhC/4Xv+aPFC50zAAIXSiYWCC7Mda5T/JAA4Xeu4ACiMU7oACoMTVocxC4sXC4dxjoXD6LFFC//M4IXS7nMAAIXSjgWCC4vzR5gXHeoYXRiYWCC47XCu1ma4IXDa4IXLACAXdj3uAAXRjGIAAWBKYQABC5fhC4qDFC/4X/C9sda5t2szXGf5IAHC4kAC6IWEAH4A1"))
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Scramble code from: https://raw.githubusercontent.com/bjcarlson42/blog-post-sample-code/master/Rubik's%20Cube%20JavaScript%20Scrambler/part_two.js
|
||||
const makeScramble = () => {
|
||||
const options = ["F", "F2", "F'", "R", "R2", "R'", "U", "U2", "U'", "B", "B2", "B'", "L", "L2", "L'", "D", "D2", "D'"];
|
||||
|
@ -59,16 +58,36 @@ const getRandomInt = max => Math.floor(Math.random() * Math.floor(max)); // retu
|
|||
const getRandomIntBetween = (min, max) => Math.floor(Math.random() * (max - min) + min);
|
||||
|
||||
const presentScramble = () => {
|
||||
g.clear();
|
||||
E.showMessage(makeScramble().join(" "));
|
||||
showPrompt(makeScramble().join(" "), {
|
||||
buttons: {"solve": true, "reset": false}
|
||||
}).then((v) => {
|
||||
if (v) {
|
||||
const start = new Date();
|
||||
showPrompt(" ", {
|
||||
buttons: {"stop": true}
|
||||
}).then(() => {
|
||||
const time = parseFloat(((new Date()).getTime() - start.getTime()) / 1000);
|
||||
showPrompt(String(time.toFixed(3)), {
|
||||
buttons: {"next": true}
|
||||
}).then(() => {
|
||||
presentScramble();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
presentScramble();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showPrompt = (text, options = {}) => {
|
||||
options.title = options.title || "cube scramble";
|
||||
return E.showPrompt(text, options);
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.setLCDPower(1);
|
||||
presentScramble();
|
||||
|
||||
setWatch(() => {
|
||||
presentScramble();
|
||||
}, BTN1, {repeat:true});
|
||||
};
|
||||
|
||||
init();
|
||||
|
|
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 5.6 KiB |