1
0
Fork 0

Merge remote-tracking branch 'upstream/master'

merge
master
marko 2021-11-29 19:24:12 -05:00
commit 5a560b4574
640 changed files with 36430 additions and 5667 deletions

View File

@ -1,2 +1,4 @@
apps/animclk/V29.LBM.js
apps/banglerun/rollup.config.js
apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ appdates.csv
.vscode
.idea/
_config.yml
tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp

View File

@ -1,10 +1,10 @@
Bangle.js App Loader (and Apps)
================================
[![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps)
[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
submitting code to this repository you confirm that you are happy with it being MIT licensed,
@ -49,25 +49,25 @@ easily distinguish between file types, we use the following:
## Adding your app to the menu
* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js
* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
try and keep filenames short to avoid overflowing the buffer.
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
* Create a folder called `apps/<id>`, lets assume `apps/myappid`
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
* `apps/7chname/app.png` should be a 48px icon
* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
* `apps/myappid/app.png` should be a 48px icon
* Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
* Create an entry in `apps.json` as follows:
```
{ "id": "7chname",
{ "id": "myappid",
"name": "My app's human readable name",
"shortName" : "Short Name",
"icon": "app.png",
"description": "A detailed description of my great app",
"tags": "",
"storage": [
{"name":"7chname.app.js","url":"app.js"},
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
{"name":"myappid.app.js","url":"app.js"},
{"name":"myappid.img","url":"app-icon.js","evaluate":true}
],
},
```
@ -95,12 +95,12 @@ Be aware of the delay between commits and updates on github.io - it can take a f
Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
(4 discs), upload your files into the places described in your JSON:
* `app-icon.js` -> `7chname.img`
* `app-icon.js` -> `myappid.img`
Now load `app.js` up in the editor, and click the down-arrow to the bottom
right of the `Send to Espruino` icon. Click `Storage` and then either choose
`7chname.app.js` (if you'd uploaded your app previously), or `New File`
and then enter `7chname.app.js` as the name.
`myappid.app.js` (if you'd uploaded your app previously), or `New File`
and then enter `myappid.app.js` as the name.
Now, clicking the `Send to Espruino` icon will load the app directly into
Espruino **and** will automatically run it.
@ -115,10 +115,13 @@ and set it to `Load default application`.
## Example Applications
To make the process easier we've come up with some example applications that you can use as a base
when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app`
or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app`
or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to
`apps.json`.
**Note:** the max filename length is 28 chars, so we suggest an app ID of under
20 so that when `.app.js`/etc gets added to the end the filename isn't cropped.
**If you're making a widget** please start the name with `wid` to make
it easy to find!
@ -192,8 +195,8 @@ and which gives information about the app for the Launcher.
```
{
"name":"Short Name", // for Bangle.js menu
"icon":"*7chname", // for Bangle.js menu
"src":"-7chname", // source file
"icon":"*myappid", // for Bangle.js menu
"src":"-myappid", // source file
"type":"widget/clock/app/bootloader", // optional, default "app"
// if this is 'widget' then it's not displayed in the menu
// if it's 'clock' then it'll be loaded by default at boot time
@ -217,8 +220,10 @@ and which gives information about the app for the Launcher.
{ "id": "appid", // 7 character app id
"name": "Readable name", // readable name
"shortName": "Short name", // short name for launcher
"icon": "icon.png", // icon in apps/
"version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
"type":"...", // optional(if app) -
// 'app' - an application
// 'widget' - a widget
@ -226,7 +231,9 @@ and which gives information about the app for the Launcher.
// 'bootloader' - code that runs at startup only
// 'RAM' - code that runs and doesn't upload anything to storage
"tags": "", // comma separated tag list for searching
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file
// that contains more information about this app (usage, etc)
@ -259,6 +266,9 @@ and which gives information about the app for the Launcher.
// (eg it's evaluated as JS)
"noOverwrite":true // if supplied, this file will not be overwritten if it
// already exists
"supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device
// types named in the array. This allows different versions of
// the app to be uploaded for different platforms
},
]
"data": [ // list of files the app writes to
@ -306,10 +316,10 @@ version of what's in `apps.json`:
<script>
document.getElementById("upload").addEventListener("click", function() {
sendCustomizedApp({
id : "7chname",
id : "myappid",
storage:[
{name:"7chname.app.js", url:"app.js", content:app_source_code},
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
{name:"myappid.app.js", url:"app.js", content:app_source_code},
{name:"myappid.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
]
});
});
@ -367,40 +377,37 @@ 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)||{};
function save(key, value) {
settings[key] = value;
require('Storage').write('app.json',settings);
}
function get(key, def) { return require('Settings').get('myappid', key, def); }
function set(key, value) { require('Settings').set('myappid', key, value); }
const appMenu = {
'': {'title': 'App Settings'},
'< Back': back,
'Monkeys': {
value: settings.monkeys||12,
onchange: (m) => {save('monkeys', m)}
value: get('monkeys', 12),
onchange: (m) => set('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"}
]
},
```
@ -435,7 +442,7 @@ from the IDE.
### Misc Notes
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `7chname.json`, then load it at startup.
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `myappid.json`, then load it at startup.
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-minimal

5059
apps.json

File diff suppressed because it is too large Load Diff

View File

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

BIN
apps/93dub/93dub.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

3
apps/93dub/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: Initial version for upload
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
0.03: Code style cleanup

11
apps/93dub/README.md Normal file
View File

@ -0,0 +1,11 @@
# 93 Dub
![](screenshot.png)
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)

1
apps/93dub/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBG2XwAgcPC6P/h//AAIDBA4Pwh/w+AGBAgIDBC4oVDAAITBCAIIBAYIBBAgIvHh4YCFgQPBAoIvCCwoAWIQYAQGLgAWI6bQVdQiiDOyAX/C/7+IAIYvSh4RBAYIXLAwJAHC6ZFCF5yn/C7wDBBAJ3EVAKBDC5QLBYAoLFC5nwCgoXlL44vSL653sL4QXBL6DvXC9YCBACIXCZ4YAQFaYAgPAhqCa4SDFLoZpICYIXDQKLyCDIQXVAAKI0AAYA=="))

137
apps/93dub/app.js Normal file
View File

@ -0,0 +1,137 @@
// 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;
}
}
//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();

BIN
apps/93dub/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -2,13 +2,14 @@
{ "id": "7chname",
"name": "My app's human readable name",
"shortName":"Short Name",
"icon": "app.png",
"version":"0.01",
"description": "A detailed description of my great app",
"icon": "app.png",
"tags": "",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.app.js","url":"app.js"},
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
]
}
}

View File

@ -2,11 +2,12 @@
{ "id": "7chname",
"name": "My widget's human readable name",
"shortName":"Short Name",
"icon": "widget.png",
"version":"0.01",
"description": "A detailed description of my great widget",
"tags": "widget",
"icon": "widget.png",
"type": "widget",
"tags": "widget",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.wid.js","url":"widget.js"}

View File

@ -0,0 +1 @@
0.01: Beta version for Bangle 2 (2021/11/28)

View File

@ -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
![](screenshot.png)
## Creator
[@alainsaas](https://github.com/alainsaas)

View File

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

129
apps/a_clock_timer/app.js Normal file
View File

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

BIN
apps/a_clock_timer/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -6,3 +6,6 @@
0.06: Actual pixels as of 12 Jun 2020
0.07: Pressing a button now exits immediately (fix #618)
0.08: Make about (mostly) work on non-240px screens
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

43
apps/about/app-bangle1.js Normal file

File diff suppressed because one or more lines are too long

72
apps/about/app-bangle2.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Use the new multiplatform 'Layout' library
0.03: Exit as first menu option, dont show decimal places for seconds

View File

@ -8,6 +8,9 @@ function getFileName(n) {
function showMenu() {
var menu = {
"" : { title : "Accel Logger" },
"Exit" : function() {
load();
},
"File No" : {
value : fileNumber,
min : 0,
@ -21,9 +24,6 @@ function showMenu() {
"View Logs" : function() {
viewLogs();
},
"Exit" : function() {
load();
},
};
E.showMenu(menu);
}
@ -34,7 +34,7 @@ function viewLog(n) {
var records = 0, l = "", ll="";
while ((l=f.readLine())!==undefined) {records++;ll=l;}
var length = 0;
if (ll) length = (ll.split(",")[0]|0)/1000;
if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 );
var menu = {
"" : { title : "Log "+n }
@ -94,18 +94,17 @@ function startRecord(force) {
var Layout = require("Layout");
var layout = new Layout({ type: "v", c: [
{type:"txt", font:"6x8", label:"Samples", pad:2},
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5},
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
{type:"txt", font:"6x8", label:"Time", pad:2},
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5},
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:true},
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
]
},[ // Buttons...
},{btns:[ // Buttons...
{label:"STOP", cb:()=>{
Bangle.removeListener('accel', accelHandler);
showMenu();
}}
]);
layout.update();
]});
layout.render();
// now start writing

View File

@ -46,7 +46,6 @@ function getData() {
});
return new Promise(resolve=>{
Puck.eval(`require("Storage").read(${JSON.stringify(fn)})`,csv=>{
fileData[fn] = csv.trim();
var el = document.querySelector(`.card-body[fn='${fn}']`);
el.innerHTML = '<canvas width="400" height="100"></canvas>';
var c = el.firstChild;

4
apps/aclock/README.md Normal file
View File

@ -0,0 +1,4 @@
# Analogue Clock
![](screenshot_analog.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -9,3 +9,7 @@
0.09: Add per alarm auto-snooze option
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
0.11: Respect Quiet Mode
0.12: Fix widget for bangle 2, now uses theme
Widgets now shown on Alarm screen
0.13: Alarm widget state now updates when setting/resetting an alarm
0.14: Order of 'back' menu item

View File

@ -18,8 +18,10 @@ function showAlarm(alarm) {
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showPrompt(msg,{
title:"ALARM!",
title:alarm.timer ? "TIMER!" : "ALARM!",
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;

View File

@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat
as : false, // auto snooze
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
}
];*/
@ -18,6 +19,12 @@ function formatTime(t) {
return hrs+":"+("0"+mins).substr(-2);
}
function formatMins(t) {
mins = (0|t)%60;
hrs = 0|(t/60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
@ -25,17 +32,25 @@ function getCurrentHr() {
function showMainMenu() {
const menu = {
'': { 'title': 'Alarms' },
'New Alarm': ()=>editAlarm(-1)
'': { 'title': 'Alarm/Timer' },
'< Back' : ()=>{load();},
'New Alarm': ()=>editAlarm(-1),
'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
if (alarm.timer) {
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
} else {
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
}
menu[txt] = function() {
editAlarm(idx);
if (alarm.timer) editTimer(idx);
else editAlarm(idx);
};
});
menu['< Back'] = ()=>{load();};
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
@ -55,7 +70,8 @@ function editAlarm(alarmIndex) {
as = a.as;
}
const menu = {
'': { 'title': 'Alarms' },
'': { 'title': 'Alarm' },
'< Back' : showMainMenu,
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
@ -105,7 +121,60 @@ function editAlarm(alarmIndex) {
showMainMenu();
};
}
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
function editTimer(alarmIndex) {
var newAlarm = alarmIndex<0;
var hrs = 0;
var mins = 5;
var en = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
mins = (0|a.timer)%60;
hrs = 0|(a.timer/60);
en = a.on;
}
const menu = {
'': { 'title': 'Timer' },
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Minutes': {
value: mins,
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Enabled': {
value: en,
format: v=>v?"On":"Off",
onchange: v=>en=v
}
};
function getTimer() {
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
// Save alarm
return {
on : en,
timer : (hrs*60)+mins,
hr : hr,
rp : false, as: false
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getTimer());
else alarms[alarmIndex] = getTimer();
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
}
return E.showMenu(menu);
}

View File

@ -1,11 +1,7 @@
(() => {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
alarms = alarms.filter(alarm=>alarm.on);
if (!alarms.length) return; // no alarms, no widget!
delete alarms;
// add the widget
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
g.setColor(-1);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
}};
})()
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();

5
apps/android/ChangeLog Normal file
View File

@ -0,0 +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
apps/android/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4MA///xF9FstggwFDuEOAoc//gFJv/+AoZHBAgUB8/nwAFCBYIFCgYFB4AFHABdjCIPGAoPzAoPPAvpHFMpYFFPosAnk8NgYFdjEYfMo="))

3
apps/android/app.js Normal file
View File

@ -0,0 +1,3 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
eval(require("Storage").read("android.settings.js"))(()=>load());

BIN
apps/android/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

71
apps/android/boot.js Normal file
View File

@ -0,0 +1,71 @@
(function() {
function gbSend(message) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
var _GB = global.GB;
global.GB = (event) => {
// feed a copy to other handlers if there were any
if (_GB) setTimeout(_GB,0,Object.assign({},event));
/* TODO: Call handling, fitness */
var HANDLERS = {
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
"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
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
// {t:"find", n:bool} // find my phone
"find" : function() {
if (Bangle.findDeviceInterval) {
clearInterval(Bangle.findDeviceInterval);
delete Bangle.findDeviceInterval;
}
if (event.n) // Ignore quiet mode: we always want to find our watch
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
},
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
"musicstate" : function() {
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
},
// {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);
};
// 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=>{
gbSend({ t: "act", stp: health.steps, hrm: health.bpm });
});
// Music control
Bangle.musicControl = cmd => {
// play/pause/next/previous/volumeup/volumedown
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?
};
})();

18
apps/android/settings.js Normal file
View File

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

3
apps/antonclk/ChangeLog Normal file
View File

@ -0,0 +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.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA=="))

61
apps/antonclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/antonclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

View File

@ -1,4 +1,6 @@
0.01: First version
0.02: Moved arrow image load to global scope
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
0.04: removed LCD1.write() as it was keeping LCD on
0.04: removed LED1.write() as it was keeping LCD on
0.05: Turn compass off when screen off
Calibrate at start if no info

View File

@ -1,5 +1,5 @@
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
var intervalRef;
@ -7,6 +7,7 @@ var bearing=0; // always point north
var heading = 0;
var oldHeading = 0;
var candraw = false;
var isCalibrating = false;
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
function flip1(x,y) {
@ -29,7 +30,7 @@ function drawCompass(hd) {
if (Math.abs(hd - oldHeading) < 2) return 0;
hd=hd*Math.PI/180;
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
// using polar cordinates, 64,64 is the offset from the 0,0 origin
var poly = [
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
@ -40,16 +41,16 @@ function drawCompass(hd) {
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
];
buf1.fillPoly(poly);
flip1(56, 56);
}
// stops violent compass swings and wobbles, takes 3ms
function newHeading(m,h){
function newHeading(m,h){
var s = Math.abs(m - h);
var delta = (m>h)?1:-1;
if (s>=180){s=360-s; delta = -delta;}
if (s>=180){s=360-s; delta = -delta;}
if (s<2) return h;
var hd = h + delta*(1 + Math.round(s/5));
if (hd<0) hd+=360;
@ -76,7 +77,7 @@ function tiltfixread(O,S){
return psi;
}
function reading() {
function reading(m) {
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
heading = newHeading(d,heading);
var dir = bearing - heading;
@ -97,18 +98,19 @@ function reading() {
function calibrate(){
var max={x:-32000, y:-32000, z:-32000},
min={x:32000, y:32000, z:32000};
var ref = setInterval(()=>{
var m = Bangle.getCompass();
function onMag(m) {
max.x = m.x>max.x?m.x:max.x;
max.y = m.y>max.y?m.y:max.y;
max.z = m.z>max.z?m.z:max.z;
min.x = m.x<min.x?m.x:min.x;
min.y = m.y<min.y?m.y:min.y;
min.z = m.z<min.z?m.z:min.z;
}, 100);
}
Bangle.on('mag', onMag);
Bangle.setCompassPower(1, "app");
return new Promise((resolve) => {
setTimeout(()=>{
if(ref) clearInterval(ref);
Bangle.removeListener('mag', onMag);
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
var avg = (delta.x+delta.y+delta.z)/3;
@ -132,6 +134,7 @@ function docalibrate(e,first){
flip1(56,56);
calibrate().then((r)=>{
isCalibrating = false;
require("Storage").write("magnav.json",r);
Bangle.buzz();
CALIBDATA = r;
@ -142,27 +145,39 @@ function docalibrate(e,first){
startdraw();
setTimeout(setButtons,1000);
}
}
if (first===undefined) first=false;
}
if (first === undefined) first = false;
stopdraw();
clearWatch();
if (first)
isCalibrating = true;
if (first)
E.showAlert(msg,title).then(action.bind(null,true));
else
else
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
}
function startdraw(){
Bangle.setCompassPower(1, "app");
g.clear();
g.setColor(1,1,1);
Bangle.drawWidgets();
candraw = true;
intervalRef = setInterval(reading,500);
if (intervalRef) clearInterval(intervalRef);
intervalRef = setInterval(reading,200);
}
function stopdraw() {
candraw=false;
if(intervalRef) {clearInterval(intervalRef);}
Bangle.setCompassPower(0, "app");
if (intervalRef) {
clearInterval(intervalRef);
intervalRef = undefined;
}
}
function setButtons(){
@ -170,8 +185,9 @@ function setButtons(){
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
}
Bangle.on('lcdPower',function(on) {
if (isCalibrating) return;
if (on) {
startdraw();
} else {
@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) {
}
});
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
Bangle.loadWidgets();
Bangle.setCompassPower(1);
startdraw();
setButtons();
Bangle.setLCDPower(1);
if (CALIBDATA) startdraw(); else docalibrate({},true);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
0.01: First release

View File

@ -0,0 +1,26 @@
# Authentiwatch - 2FA Authenticator
## 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)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))

300
apps/authentiwatch/app.js Normal file
View File

@ -0,0 +1,300 @@
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 },
};
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
// 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(token) {
var tick;
var d = new Date();
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 = "";
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 = "Not supported";
}
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;
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, (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 = 5;
}
// digits just below label
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
g.drawString(state.otp, (x1 + x2) / 2 + adj, 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 d = new Date();
if (state.curtoken != -1) {
var t = tokens[state.curtoken];
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(t);
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 - draw it again in 1sec
if (state.drawtimer) {
clearTimeout(state.drawtimer);
}
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
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("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
}
}
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 = -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.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);
newy = Math.max(0, newy);
if (newy != state.listy) {
state.listy = newy;
draw();
}
}
function onSwipe(e) {
if (e == 1) {
Bangle.showLauncher();
}
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
tokens[state.curtoken].period--;
require("Storage").writeJSON("authentiwatch.json", tokens);
state.nextTime = 0;
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);
}
}
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(){Bangle.showLauncher();}, 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();

BIN
apps/authentiwatch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

View File

@ -0,0 +1,373 @@
<!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)'];
/* Array of TOTP tokens */
var 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, '&#x25bc;', 1);
}
markup += '</td><td>';
if (i > 0) {
markup += tokenButton('moveToken', i, '&#x25b2;', -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'] + ')';
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').read(${JSON.stringify('authentiwatch.json')})`,data=>{
Util.hideModal();
try {
tokens = JSON.parse(data);
updateTokens();
} catch {
tokens = [];
}
});
}
/* Save settings as a JSON file on the watch.
*/
function saveTokens() {
Util.showModal('Saving...');
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(tokens)})\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>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -5,3 +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.09: Fix time/date disappearing after fullscreen notification

View File

@ -3,167 +3,106 @@
* A simple digital clock showing seconds as a bar
**/
// Check settings for what type our clock should be
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
let locale = require('locale')
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
let locale = require("locale");
{ // add some more info to locale
let date = new Date()
date.setFullYear(1111)
date.setMonth(1, 3) // februari: months are zero-indexed
const localized = locale.date(date, true)
locale.dayFirst = /3.*2/.test(localized)
let date = new Date();
date.setFullYear(1111);
date.setMonth(1, 3); // februari: months are zero-indexed
const localized = locale.date(date, true);
locale.dayFirst = /3.*2/.test(localized);
locale.hasMeridian = false
if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed
locale.hasMeridian = (locale.meridian(date) !== '')
locale.hasMeridian = false;
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
locale.hasMeridian = (locale.meridian(date)!=="");
}
}
const screen = {
width: g.getWidth(),
height: g.getWidth(),
middle: g.getWidth() / 2,
center: g.getHeight() / 2,
Bangle.loadWidgets();
function renderBar(l) {
if (!this.fraction) {
// zero-size fillRect stills draws one line of pixels, we don't want that
return;
}
const width = this.fraction*l.w;
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
}
// hardcoded "settings"
const settings = {
time: {
color: -1,
font: '6x8',
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
middle: screen.middle,
center: screen.center,
ampm: {
color: -1,
font: '6x8',
size: 2,
const Layout = require("Layout");
const layout = new Layout({
type: "v", c: [
{
type: "h", c: [
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
],
},
},
date: {
color: -1,
font: 'Vector',
size: 20,
middle: screen.height - 20, // at bottom of screen
center: screen.center,
},
bar: {
color: -1,
top: 155, // just below time
thickness: 6, // matches 24h time "pixel" size
},
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
{height: 40},
{id: "date", type: "txt", font: "10%", valign: 1},
],
}, {lazy: true});
// adjustments based on screen size and whether we display am/pm
let thickness; // bar thickness, same as time font "pixel block" size
if (is12Hour) {
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
thickness = Math.floor((g.getWidth()-24)/(5*6));
} else {
layout.ampm.label = "";
thickness = Math.floor(g.getWidth()/(5*6));
}
layout.bar.height = thickness+1;
layout.time.font = "6x8:"+thickness;
layout.update();
const SECONDS_PER_MINUTE = 60
const timeText = function (date) {
function timeText(date) {
if (!is12Hour) {
return locale.time(date, true)
return locale.time(date, true);
}
const date12 = new Date(date.getTime())
const hours = date12.getHours()
if (hours === 0) {
date12.setHours(12)
} else if (hours > 12) {
date12.setHours(hours - 12)
const date12 = new Date(date.getTime());
const hours = date12.getHours();
if (hours===0) {
date12.setHours(12);
} else if (hours>12) {
date12.setHours(hours-12);
}
return locale.time(date12, true)
return locale.time(date12, true);
}
const ampmText = function (date) {
return is12Hour ? locale.meridian(date) : ''
function ampmText(date) {
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
}
const dateText = function (date) {
function dateText(date) {
const dayName = locale.dow(date, true),
month = locale.month(date, true),
day = date.getDate()
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
return `${dayName} ${dayMonth}`
day = date.getDate();
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
return `${dayName} ${dayMonth}`;
}
const drawDateTime = function (date) {
const t = settings.time
g.setColor(t.color)
g.setFont(t.font, t.size)
g.setFontAlign(0, 0) // centered
g.drawString(timeText(date), t.center, t.middle, true)
if (is12Hour && locale.hasMeridian) {
const a = settings.time.ampm
g.setColor(a.color)
g.setFont(a.font, a.size)
g.setFontAlign(1, -1) // right top
// at right edge of screen, aligned with time bottom
const left = screen.width - a.size * 2,
top = t.middle + t.size - a.size
g.drawString(ampmText(date), left, top, true)
draw = function draw(force) {
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
const date = new Date();
layout.time.label = timeText(date);
layout.ampm.label = ampmText(date);
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();
setTimeout(draw, 1000-millis);
};
const d = settings.date
g.setColor(d.color)
g.setFont(d.font, d.size)
g.setFontAlign(0, 0) // centered
g.drawString(dateText(date), d.center, d.middle, true)
}
const drawBar = function (date) {
const b = settings.bar
const seconds = date.getSeconds()
if (seconds === 0) {
// zero-size rect stills draws one line of pixels, we don't want that
return
}
const fraction = seconds / SECONDS_PER_MINUTE,
width = fraction * screen.width
g.setColor(b.color)
g.fillRect(0, b.top, width, b.top + b.thickness)
}
const clearScreen = function () {
g.setColor(0)
const timeTop = settings.time.middle - (settings.time.size * 4)
g.fillRect(0, timeTop, screen.width, screen.height)
}
let lastSeconds, tTick
const tick = function () {
g.reset()
const date = new Date()
const seconds = date.getSeconds()
if (lastSeconds > seconds) {
// new minute
clearScreen()
drawDateTime(date)
}
// the bar only gets larger, so drawing on top of the previous one is fine
drawBar(date)
lastSeconds = seconds
// schedule next update
const millis = date.getMilliseconds()
tTick = setTimeout(tick, 1000-millis)
}
const start = function () {
lastSeconds = 99 // force redraw
tick()
}
const stop = function () {
if (tTick) {
clearTimeout(tTick)
tTick = undefined
}
}
// clean app screen
g.clear()
Bangle.loadWidgets()
Bangle.drawWidgets()
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.on('lcdPower', function (on) {
Bangle.on("lcdPower", function(on) {
if (on) {
start()
} else {
stop()
draw(true);
}
})
start()
});
g.reset().clear();
Bangle.drawWidgets();
draw();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,3 +1,6 @@
0.02: Modified for use with new bootloader and firmware
0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal.
0.04: Update to use Bangle.setUI instead of setWatch
0.05: Update *on* the minute rather than every 15 secs
Now show widgets
Make compatible with themes, and Bangle.js 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,7 +1,7 @@
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
// https://github.com/eska-muc/BangleApps
const fields = [4, 4, 11, 4];
const offset = 20;
const offset = 24;
const width = g.getWidth() - 2 * offset;
const height = g.getHeight() - 2 * offset;
const rowHeight = height / 4;
@ -10,11 +10,23 @@ var show_date = false;
var show_time = false;
var yy = 0;
rowlights = [];
time_digit = [];
var rowlights = [];
var time_digit = [];
function drawBerlinClock() {
g.clear();
// 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 draw() {
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
var now = new Date();
// show date below the clock
@ -24,8 +36,7 @@ function drawBerlinClock() {
var day = now.getDate();
var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
var strWidth = g.stringWidth(dateString);
g.setColor(1, 1, 1);
g.setFontAlign(-1,-1);
g.setColor(g.theme.fg).setFontAlign(-1,-1);
g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4);
}
@ -50,8 +61,7 @@ function drawBerlinClock() {
x2 = (col + 1) * boxWidth + offset;
y2 = (row + 1) * rowHeight + offset;
g.setColor(1, 1, 1);
g.drawRect(x1, y1, x2, y2);
g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2);
if (col < rowlights[row]) {
if (row === 2) {
if (((col + 1) % 3) === 0) {
@ -65,46 +75,42 @@ function drawBerlinClock() {
g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
}
if (row == 3 && show_time) {
g.setColor(1,1,1);
g.setFontAlign(0,0);
g.setColor(g.theme.fg).setFontAlign(0,0);
g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2);
}
}
}
queueDraw();
}
function toggleDate() {
show_date = ! show_date;
drawBerlinClock();
draw();
}
function toggleTime() {
show_time = ! show_time;
drawBerlinClock();
draw();
}
// special function to handle display switch on
Bangle.on('lcdPower', (on) => {
g.clear();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
Bangle.drawWidgets();
// call your app function here
drawBerlinClock();
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// refesh every 15 sec
setInterval(drawBerlinClock, 15E3);
// Show launcher when button pressed, handle up/down
Bangle.setUI("clockupdown", dir=> {
if (dir<0) toggleTime();
if (dir>0) toggleDate();
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawBerlinClock();
if (BTN3) {
// Toggle date display, when BTN3 is pressed
setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"});
// Toggle date display, when BTN3 is pressed
setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"});
}
// Show launcher when button pressed
Bangle.setUI("clock");
draw();

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

4
apps/binwatch/ChangeLog Normal file
View File

@ -0,0 +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)

47
apps/binwatch/README.md Normal file
View File

@ -0,0 +1,47 @@
# TheBinWatch
Binary watch to train Your brain
Inspired by the LCD wrist watch from TecRAL from 1989
![](screenshot.png)
![](screenshot2.png)
## 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
## 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

View File

@ -0,0 +1 @@
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=="))

379
apps/binwatch/app.js Normal file
View File

@ -0,0 +1,379 @@
/***************************************************
* BINARY WATCH
* for Bangle 1 / 2
* inspired by RAL tec binary wrist watch
*
* TODO:
* - vibrate on full hour
* -
****************************************************/
/* reuqirements */
require("Font7x11Numeric7Seg").add(Graphics);
require("Font5x7Numeric7Seg").add(Graphics);
/* constants and definitions */
/* Bangle 2: 176 x 176 */
/* month images */
var month = [
/* JAN */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AAMf/0D8AFBkM/9EvwMAgcM/3B30YgE4uEOh354EB4eAuFz90Ah0cgeDx9wgFw8Ecjk7wEDw8A8AIBgEcnEHg4IBgFh4EYnEDHYMF/8AwBID/BODgN/4EgAoI0BgODwExGgkDzg0FAII0D88A8PAnAIBAIMOgPBBAPAiBpCgPAQIOAmFwg0P/B5BwcAiE/JYYAHA"))},
/* FEB */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("v/8n/+g/+if/hP/wM/8c/4Of8Ez/FwgE4gEHgFOAYUA8Ed4ADBgEcge4AYMAgeAu4DCgFwhwQBEIMOgPcAYMAgPAjN/4G/8EX/kf/EP/kB/+F/8C/+Ar/xGQkBGTE7wADBMIMHMotMgEGv+A7/hEYOf/EH/hvBh6FBIIKYFA"))},
/* MAR */ {width : 52, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+gf/hP/gV/6FP/HP8kz/cGv8OgHDwFwuEE8McnEHg8A905BgcO8ecBiM4BgMwuEGoeEi/8gX/wE4gH/4Ef/AMFx0QDIcA8BADnEOgIzCufABgk+Bglx+AMEh+OBgdwvnghk4gcGgfsgFDgEQoEeSgvg"))},
/* APR */ {width : 52, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/4gf/hP/oV/4FP/HP9kz/EGv8OgPDwEguEE8EcnEHg8A9wMCuFwhwMTgAMBmFwg1f+EX/kC/+D/8A//AJIIMFxwZCgFwgAmCgEHnBNDgFz4AMEnwMEuPwBgkPxwMDuF88EMBgMGgfsgFDRgNAjyUF8A="))},
/* MAY */ {width : 52, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+AoMJ/8Cv/QAwPP8kz/cEgEugHDwFwsEG8McnEHg8A905BgVwh3jzgMRnAMBmAMBoeEi/8BgNgnEA//Ah/4BgcB/+OiAZCBgPgIARTB90BGYUAhwMahk4gYMBpkAocAiEP+CSDIAOAAwYMB"))},
/* JUN */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AAcD8ADBkMAhEvwIJBhkA4O+jEAnFwh0O/PAgPDwFwufugEOjkDwePuEAuHgjkcneAgeHgHgBAMAjk4g8HBAMAsPAjE4gY7BggCBwBPLkACBGgMBweAmI0EgecGgoBBGgfngHh4E4BAIBBh0B4IIB4EQmEEBAPA/0An5qBg0P/ED/xNBiAKBh6PCAAw="))},
/* JUL */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AA8hgEImAFBgcMgHB4AGBnFwh0OAoMB4eAuFwAwMOjkDweAAwNw8EcjggCw8A8HgAwMcnEHg40CsPAjE4AwUEAQIgCABMgGgcBGgMBGgo/BGggKBGgYBB8PAnA0BBQMOgJpC4EQmEENIX+gE/wFn/EP/ED/0Cv/gBQMP8EP/5QGA"))},
/* AUG */ {width : 52, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("n/8AocE/+gAgMP/1n+0QgGA//HgHhwEYl/wuEOjkDw8Ag4MB4E4uEABilhBgcv/EcgOCgEB/+AwBBB/AMBAgMCj/ngFgAwNw/wmCgImBBgIzDhwzFBikGhkBgUAs0AkEf4EH+A3Bgf+gBLBAwIMD"))},
/* SEP */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("h/4j//g/+gf/wn/4M/8ABB5/wmf4mEAjkAg8Ap0AgeAgHgjvAgFwBwMD3EAhwOCu8AgIOCh3ggE4BwMB7gjCBwMYv/Ar/wi/8j/8IYMB/+BIYIODDwIyCLIMHGQYGB8JBDB4IyCAoMDw5BDB4JBDgEEMoZ6Cn/A8A6B8FP/kYgEf/EH/4eCA"))},
/* OCT */ {width : 50, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("h/gg/+j//w/8gf/h//+H+gF/wP//OAkHADAXgjlwAoU4g8cAgMYh0B44pCgeAuIYBgfADAnwnEDDAUcghCDgRMIsACBkAYFGKZKDngYFgJjBwAYCPgX4DAMHPgQYBgB8C8EGgAA="))},
/* NOV */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("vkAgf4AoMX4GA/+ABIN8mEP8EggP350MgMGgF+vvDwFw8Ef4+4uEOjgiBu8OgIOBv8A8PAnFwEQMcnEHBwP8gOHgFh4EdHYNAgEQgJLFggFEhPAjFwg0cg4jDGQPnGQk8GQkPI4IyB8PDKwYOB+BWBMoMHnkOgHAn+A98BwEIh/4jnAHgX+gaGBAAcggAA=="))},
/* DEC */ {width : 49, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/gj//gP/5/4iYFC2f4hn/CAOcgMHgEBwEOgPDwEB4AJB8PAgHggeAuHggFwBoM4uEAnANBjgDBjgNBgwDBh0AiEAgowBAAQ6BwEAggFBv/BwAwBsIwWhwwDnEHAYIiBjhhDgEN/0Dn/Aj/hO4M/+Ef/JABv/8g/+A=="))},
/* MAI */ {width : 44, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+gEJ/8Cv/QgnP8kz/cA50A4eAuEc8McnEHgPOnIKD8ecBR04BQMwhlDwkX/kAoE4gH/4EABQlOiAVD8A2EgIrDBS0MnEDgHMGQMAiEEPwo="))},
/* OKT */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("g/wAQMP//B/8DgPh//8j/AuF8n//jECh0fDAUA8PH4AGB8EcnIhBsEcgeHvkAj0DwFw98AgYjBh0dDAN4h0A4eAEgQDBl/4gFAE4MD/5OE3/ggIyBhk4gcAuAyCBIIyDIIIyDAgOAGQMBGQNwh8B4E4BwMB8BlCBIM8gF/AgMYg+Aj/wmA3B+EB/hBChiYGA"))},
/* DEZ */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("n/wh//w//xP/gV/8F//Of4Fn/EH/04gUODAUHgHh4AFBnHgjk4BYUcgeHAoMB8eAuHgAwN4uEOjgFBh4jB4eAgED4ADBl/4gFwB4MD/4DBgQCB3/gC4PghgyBgPAGQl4gYyDjwgBGQQrBh0BGQVwDQM4F4MMLIJlEg3/gOfPAPgn/gk/+j/+h/8IoPh//gA="))}
];
var imgSquid = {width : 88, height : 26, bpp : 1, buffer : require("heatshrink").decompress(atob("gE/AYUYgEH////0B//gBQM8BQgDB/AKHh/A/gKBvwKBAgMOj8AnwKHBAIMBgH/BQgmCAoPnBQl4AoOAgPnwAKDuEAgYKB4YKIgfD4AKDMAMB4EDwIKIg+B8AKIgAKIh8A+AKHh0AuAKHj0AvBMG4EcgE4K458Bnh4HnEAjiOHBwMeBQpKBEgMOXQ/wBwIKDaAZQBg4KDcwT0BAAOHfgoKHgE/wDaBAAL8DA="))};
var imgNoBT = {width : 20, height : 20, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("///8mSpM/AoP/yUT/8yuYGB5AMB/1MyYUBkmT/P85MP+USBwOT8mQ/8JBwXyoVnyGSv8//Mhk14pMn//8BYNMwmSp/+pFJkgyBDoMkkgODpOSuQOE5M/KgIOCsmfz/JknPhMyof5n+Ss/wzMhn4OBk1+smQLoWTn/mHAM/+VJz4KBwhZBEYJ/CkM8yZVBAAQxBCgP/A="))};
const V2_X_STEP = 26;
const V2_Y_STEP = 34;
const V2_TIME_Y_OFFSET = 8;
const V2_HX = 36;
const V2_HY = 0 + V2_TIME_Y_OFFSET;
const V2_MX = 10;
const V2_MY = 51 + V2_TIME_Y_OFFSET;
const V2_SX = 10;
const V2_SY = 95 + V2_TIME_Y_OFFSET;
const V2_BT_X = 137; /* 145, 35 */
const V2_BT_Y = 20;
const V2_DX = 100;
const V2_DY = 141;
const V2_BAT_POS_X = 21;
const V2_BAT_POS_Y = 40;
const V2_BAT_SIZE_X = 13;
const V2_BAT_SIZE_Y = 2;
const V2_SCREEN_SIZE_X = 176;
const V2_SCREEN_SIZE_Y = 176;
const V2_BACKGROUND_IMAGE = "binwatch.bg176.img";
const V2_BG_COLOR = 0;
const V2_FG_COLOR = 1;
/* Bangle 1: 240 x 240 */
const V1_X_STEP = 35;
const V1_Y_STEP = 46;
const V1_TIME_Y_OFFSET = 41;
const V1_HX = 48;
const V1_HY = 0 + V1_TIME_Y_OFFSET;
const V1_MX = 14;
const V1_MY = 55 + V1_TIME_Y_OFFSET;
const V1_SX = 14;
const V1_SY = 110 + V1_TIME_Y_OFFSET;
const V1_BT_X = 41;
const V1_BT_Y = 14;
//var BT_X = 20, BT_Y = 14;
const V1_DX = 160;
const V1_DY = 205;
const V1_BAT_POS_X = 175;
const V1_BAT_POS_Y = 21;
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 = "binwatch.bg240.img";
const V1_BG_COLOR = 1;
const V1_FG_COLOR = 0;
/* runtime settings */
var x_step = 0;
var y_step = 0;
var time_y_offset = 0;
var hx = 0, hy = 0;
var mx = 0, my = 0;
var sx = 0, sy = 0;
var bt_x = 0, bt_y = 0;
var dx = 0, dy = 0;
var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y;
var backgroundImage = "";
var screen_size_x = 0;
var screen_size_y = 0;
var bg_color = 0;
var fg_color = 1;
/* global variables */
var showDateTime = 2; /* show noting, time or date */
var cg;
var cgimg;
/* local functions */
/**
* function drawSquare(...)
*
* go through all bits and draw a square if a bit
* is set. So we get the binary representation
* of the value
* used to draw block for hours, mintutes, seconds, date
*
* @param gfx: graphic object to use
* @param x: x-coordinate of 1st the square
* @param y: y-coordinate of 1st the square
* @param data: data conatining the bit information
* @param numOfBits: number of bits to draw
*/
function drawSquare(gfx, x, y, data, numOfBits) {
for(i = numOfBits; i > 0 ; i--) {
if( (data & 1) != 0) {
gfx.fillRect(x + (i - 1) * x_step, y,
x + i * x_step , y + y_step);
}
data >>= 1; /* shift one bit right */
}
}
/**
* function drawBinary(...)
* draw the time in binary format
* default display for geeks and real men
* @param h: hours
* @param m: minutes
* @param s: seconds
*/
function drawBinary(gfx, hour, minute, second) {
gfx.clear(0);
if(hour > 12) {
hour -= 12; /* we use for bit for hours so we only display 12 hours*/
}
drawSquare(gfx, hx, hy, hour, 4); /* set hour */
drawSquare(gfx, mx, my, minute, 6); /* set minute */
drawSquare(gfx, sx, sy, second, 6); /* set second */
}
/**
* function drawTime(...)
* show time under the graphic
* for wimps and commies
*
* @param h: hours
* @param m: minutes
* @param s: seconds
*/
function drawTime(gfx, h, m, s) {
var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2)+ ":" + ("0"+s).substr(-2);
gfx.setFontAlign(0,-1); // align right bottom
gfx.setFont("7x11Numeric7Seg", 2);
gfx.drawString(time, gfx.getWidth() / 2, dy + 1, false /*clear background*/);
}
/**
* function drawDate(...)
* show date under the graphic
* (optionally)
*
* @param gfx: graphic object to use
* @param d: date object
*/
var vMonth = 0;
function drawDate(gfx, d) {
var dateString = ""
+ ("0" + d.getDate()).substr(-2)
// + " "
// + ("0" + d.getMonth()).substr(-2)
// + " "
// + ("0" + d.getFullYear()).substr(-2)
;
gfx.setFontAlign(-1,-1); // align right bottom
gfx.setFont("7x11Numeric7Seg",2); /* draw the current time font */
gfx.drawString(dateString, dx, dy + 1, false /* don't clear background*/);
gfx.drawImage(month[d.getMonth()], 40, dy);
}
function toggleDateTime() {
showDateTime++;
if(showDateTime > 2){
showDateTime = 0;
}
}
function updateVTime() {
vMonth++;
if(vMonth >= 12 + 3) {
vMonth = 0;
}
second++;
if(second > 59) {
second = 0;
minute++;
if(minute > 59) {
minute = 0;
hour++;
if(hour > 12) {
hour = 0;
}
}
}
}
/**
* function drawBattery(...)
* fill the battery symbol with blocks
* according to the battery level
*
* @param gfx: graphic object
* @param level: current battery level
*/
function drawBattery(gfx, level) {
var pos_y = bat_pos_y - 1;
var stepLevel = Math.round((level + 10) / 20);
for(i = 0; i < stepLevel; i++) {
pos_y -= bat_size_y + 2;
gfx.fillRect(bat_pos_x, pos_y,
bat_pos_x + bat_size_x, pos_y + bat_size_y);
}
}
/**
* function drawBattery(...)
* fill the battery symbol with blocks
* according to the battery level
*
* @param gfx: graphic object
* @param level: current battery level
*/
function drawBT(gfx, status) {
if(!status) {
gfx.drawImage(imgNoBT, bt_x, bt_y);
}
}
function setRuntimeValues(resolution) {
if(240 == resolution) {
x_step = V1_X_STEP;
y_step = V1_Y_STEP;
time_y_offset = V1_TIME_Y_OFFSET;
hx = V1_HX;
hy = V1_HY;
mx = V1_MX;
my = V1_MY;
sx = V1_SX;
sy = V1_SY;
bt_x = V1_BT_X;
bt_y = V1_BT_Y;
dx = V1_DX;
dy = V1_DY;
screen_size_x = V1_SCREEN_SIZE_X;
screen_size_y = V1_SCREEN_SIZE_Y;
backgroundImage = V1_BACKGROUND_IMAGE;
bat_pos_x = V1_BAT_POS_X;
bat_pos_y = V1_BAT_POS_Y;
bat_size_x = V1_BAT_SIZE_X;
bat_size_y = V1_BAT_SIZE_Y;
setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"});
} else {
x_step = V2_X_STEP;
y_step = V2_Y_STEP;
time_y_offset = V2_TIME_Y_OFFSET;
hx = V2_HX;
hy = V2_HY;
mx = V2_MX;
my = V2_MY;
sx = V2_SX;
sy = V2_SY;
bt_x = V2_BT_X;
bt_y = V2_BT_Y;
dx = V2_DX;
dy = V2_DY;
screen_size_x = V2_SCREEN_SIZE_X;
screen_size_y = V2_SCREEN_SIZE_Y;
backgroundImage = V2_BACKGROUND_IMAGE;
bat_pos_x = V2_BAT_POS_X;
bat_pos_y = V2_BAT_POS_Y;
bat_size_x = V2_BAT_SIZE_X;
bat_size_y = V2_BAT_SIZE_Y;
Bangle.on('swipe', function(direction) { toggleDateTime(direction);});
}
cg = Graphics.createArrayBuffer(
screen_size_x,screen_size_y, 1, {msb:true});
cgimg = {width:screen_size_x, height:screen_size_y, bpp:1,
transparent:0, buffer:cg.buffer};
}
var hour = 0, minute = 1, second = 50;
var batVLevel = 20;
function draw() {
var d = new Date();
var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
g.reset();
drawBinary(cg, h, m, s);
switch(showDateTime) {
case 1:
drawTime(cg, h, m, s);
break;
case 2:
drawDate(cg, d);
break;
default:
cg.drawImage(imgSquid, cg.getWidth() / 2 - 44, dy);
}
drawBattery(cg, /*batVLevel*/ E.getBattery());
batVLevel += 2;
if(batVLevel > 100) {
batVLevel = 0;
}
updateVTime();
g.clear();
g.drawImages([{image:cgimg},
{image:require("Storage").read(backgroundImage)}
]);
drawBT(g, NRF.getSecurityStatus().connected);
// Bangle.drawWidgets();
const millis = d.getMilliseconds();
setTimeout(draw, 1000-millis);
// Bangle.loadWidgets();
}
// Show launcher when button pressed
Bangle.setUI("clock");
setRuntimeValues(g.getWidth());
g.reset().clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();

BIN
apps/binwatch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
apps/binwatch/bt-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

4
apps/boldclk/README.md Normal file
View File

@ -0,0 +1,4 @@
# Bold Clock
![](screenshot_bold.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -29,3 +29,14 @@
0.28: Fix double clock load after settings are changed
0.29: Update boot0 to avoid code block (faster execution)
Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js
0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now
0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics
0.32: Fix single quote error in g.wrapString polyfill
improve g.stringMetrics polyfill
Fix issue where re-running bootupdate could disable existing polyfills
0.33: Add E.showScroller polyfill
0.34: Use Storage.hash if available
Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS)
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

View File

@ -15,25 +15,5 @@ if (!clockApp) {
clockApp = require("Storage").read(clockApp.src);
}
if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`;
// check to see if our clock is wrong - if it is use GPS time
if ((new Date()).getFullYear()<2000) {
E.showMessage("Searching for\nGPS time");
Bangle.on("GPS",function cb(g) {
Bangle.setGPSPower(0);
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()>2200)) {
// GPS receiver's time not set - just boot clock anyway
eval(clockApp);
delete clockApp;
return;
}
// We have a GPS time. Set time and reboot (to load alarms properly)
setTime(g.time.getTime()/1000);
load();
});
Bangle.setGPSPower(1);
} else {
eval(clockApp);
delete clockApp;
}
eval(clockApp);
delete clockApp;

View File

@ -3,17 +3,24 @@ recalculates, but this avoids us doing a whole bunch of reconfiguration most
of the time. */
E.showMessage("Updating boot0...");
var s = require('Storage').readJSON('setting.json',1)||{};
var isB2 = process.env.HWVERSION; // Is Bangle.js 2
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
var boot = "";
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))!=${CRC}) { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`;
} else {
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`;
}
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
boot += `E.setFlags({pretokenise:1});\n`;
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
if (s.ble!==false) {
if (s.HID) { // Human interface device
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));`
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`;
boot += `NRF.setServices({}, {uart:true, hid:Bangle.HID});\n`;
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
}
}
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
@ -48,7 +55,7 @@ boot += `E.setTimeZone(${s.timezone});`;
if (!Bangle.F_BEEPSET) {
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n`
else if (s.beep=="vib") boot += `Bangle.beep = function (time, freq) {
else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) {
return new Promise(function(resolve) {
if ((0|freq)<=0) freq=4000;
if ((0|time)<=0) time=200;
@ -81,9 +88,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\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
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.theme) {
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
}
delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it
boot += `Bangle.setUI=function(mode, cb) {
if (Bangle.btnWatches) {
@ -94,7 +103,7 @@ if (Bangle.swipeHandler) {
Bangle.removeListener("swipe", Bangle.swipeHandler);
delete Bangle.swipeHandler;
}
if (Bangle.touchandler) {
if (Bangle.touchHandler) {
Bangle.removeListener("touch", Bangle.touchHandler);
delete Bangle.touchHandler;
}
@ -131,13 +140,66 @@ else if (mode=="updown") {
throw new Error("Unknown UI mode");
};\n`;
}
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill
boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);b<c&&(c=b);g.setColor(g.theme.fg);for(var d=0;d<l;d++){var m=d+c;if(0>m||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(),
k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`;
}
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.imageMetrics=function(src) {
if (src[0]) return {width:src[0],height:src[1]};
else if ('object'==typeof src) return {
width:("width" in src) ? src.width : src.getWidth(),
height:("height" in src) ? src.height : src.getHeight()};
var im = E.toString(src);
return {width:im.charCodeAt(0), height:im.charCodeAt(1)};
};\n`;
}
delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.stringMetrics=function(txt) {
txt = txt.toString().split("\\n");
return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length};
};\n`;
}
delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.wrapString=function(str, maxWidth) {
var lines = [];
for (var unwrappedLine of str.split("\\n")) {
var words = unwrappedLine.split(" ");
var line = words.shift();
for (var word of words) {
if (g.stringWidth(line + " " + word) > maxWidth) {
lines.push(line);
line = word;
} else {
line += " " + word;
}
}
lines.push(line);
}
return lines;
};\n`;
}
delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight());
(lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`;
}
// Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
// which would cause an error!
boot += require('Storage').read(bootFile)+";\n";
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
});
// update ble
boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`;
// write file
require('Storage').write('.boot0',boot);
delete boot;
E.showMessage("Reloading...");

1
apps/bthrm/ChangeLog Normal file
View File

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

45
apps/bthrm/README.md Normal file
View File

@ -0,0 +1,45 @@
# Bluetooth Heart Rate Monitor
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
This means it's compatible with many Bangle.js apps including:
* [Heart Rate Widget](https://banglejs.com/apps/#widhrt)
* [Heart Rate Recorder](https://banglejs.com/apps/#heart)
It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm)
as that requires live sensor data (rather than just BPM readings).
## Usage
Just install the app, then install an app that uses the heart rate monitor.
Once installed it'll automatically try and connect to the first bluetooth
heart rate monitor it finds.
**To disable this and return to normal HRM, uninstall the app**
## Compatible Heart Rate Monitors
This works with any heart rate monitor providing the standard Bluetooth
Heart Rate Service (`180D`) and characteristic (`2A37`).
So far it has been tested on:
* CooSpo Bluetooth Heart Rate Monitor
## Internals
This replaces `Bangle.setHRMPower` with its own implementation.
## TODO
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
* A widget to show connection state?
* Specify a specific device by address?
## Creator
Gordon Williams

1
apps/bthrm/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA=="))

BIN
apps/bthrm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

79
apps/bthrm/boot.js Normal file
View File

@ -0,0 +1,79 @@
(function() {
var log = function() {};//print
var gatt;
var status;
Bangle.isHRMOn = function() {
return (status=="searching" || status=="connecting") || (gatt!==undefined);
}
Bangle.setHRMPower = function(isOn, app) {
// Do app power handling
if (!app) app="?";
log("setHRMPower ->", isOn, app);
if (Bangle._PWR===undefined) Bangle._PWR={};
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
isOn = Bangle._PWR.HRM.length;
// so now we know if we're really on
if (isOn) {
log("setHRMPower on", app);
if (!Bangle.isHRMOn()) {
log("HRM not already on");
status = "searching";
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
log("Found device "+device.id);
status = "connecting";
device.on('gattserverdisconnected', function(reason) {
gatt = undefined;
});
return device.gatt.connect();
}).then(function(g) {
log("Connected");
gatt = g;
return gatt.getPrimaryService(0x180D);
}).then(function(service) {
return service.getCharacteristic(0x2A37);
}).then(function(characteristic) {
log("Got characteristic");
characteristic.on('characteristicvaluechanged', function(event) {
var dv = event.target.value;
var flags = dv.getUint8(0);
// 0 = 8 or 16 bit
// 1,2 = sensor contact
// 3 = energy expended shown
// 4 = RR interval
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
/* var idx = 2 + (flags&1); // index of next field
if (flags&8) idx += 2; // energy expended
if (flags&16) {
var interval = dv.getUint16(idx,1); // in milliseconds
}*/
Bangle.emit('HRM',{
bpm:bpm,
confidence:100
});
});
return characteristic.startNotifications();
}).then(function() {
log("Ready");
status = "ok";
}).catch(function(err) {
log("Error",err);
gatt = undefined;
status = "error";
});
}
} else { // not on
log("setHRMPower off", app);
if (gatt) {
log("HRM connected - disconnecting");
status = undefined;
try {gatt.disconnect();}catch(e) {
log("HRM disconnect error", e);
}
gatt = undefined;
}
}
};
})();

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: fix precision rounding issue + no reset when equals pressed
0.03: Support for different screen sizes and touchscreen
0.04: Display current operation on LHS

View File

@ -199,8 +199,7 @@ function doMath(x, y, operator) {
function displayOutput(num) {
var len;
var minusMarge = 0;
g.setColor(0);
g.fillRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
g.setColor(-1);
if (num === Infinity || num === -Infinity || isNaN(num)) {
// handle division by 0
@ -244,6 +243,10 @@ function displayOutput(num) {
}
g.setFontAlign(1,0);
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);
if (operator) {
g.setFont('Vector', 22).setFontAlign(1,0);
g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2);
}
}
var wasPressedEquals = false;
var hasPressedNumber = false;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Bangle.js 2 compatibility

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

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