Merge branch 'master' into master
|
@ -1,2 +1,4 @@
|
||||||
apps/animclk/V29.LBM.js
|
apps/animclk/V29.LBM.js
|
||||||
apps/banglerun/rollup.config.js
|
apps/banglerun/rollup.config.js
|
||||||
|
apps/schoolCalendar/fullcalendar/main.js
|
||||||
|
apps/authentiwatch/qr_packed.js
|
||||||
|
|
67
README.md
|
@ -1,10 +1,10 @@
|
||||||
Bangle.js App Loader (and Apps)
|
Bangle.js App Loader (and Apps)
|
||||||
================================
|
================================
|
||||||
|
|
||||||
[](https://travis-ci.org/espruino/BangleApps)
|
[](https://app.travis-ci.com/github/espruino/BangleApps)
|
||||||
|
|
||||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
* 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
|
**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,
|
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
|
## 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
|
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.
|
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...
|
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
|
||||||
* `apps/7chname/app.png` should be a 48px icon
|
* `apps/myappid/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"
|
* 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:
|
* Create an entry in `apps.json` as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
{ "id": "7chname",
|
{ "id": "myappid",
|
||||||
"name": "My app's human readable name",
|
"name": "My app's human readable name",
|
||||||
"shortName" : "Short Name",
|
"shortName" : "Short Name",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"description": "A detailed description of my great app",
|
"description": "A detailed description of my great app",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"7chname.app.js","url":"app.js"},
|
{"name":"myappid.app.js","url":"app.js"},
|
||||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
{"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/)
|
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:
|
(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
|
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
|
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`
|
`myappid.app.js` (if you'd uploaded your app previously), or `New File`
|
||||||
and then enter `7chname.app.js` as the name.
|
and then enter `myappid.app.js` as the name.
|
||||||
|
|
||||||
Now, clicking the `Send to Espruino` icon will load the app directly into
|
Now, clicking the `Send to Espruino` icon will load the app directly into
|
||||||
Espruino **and** will automatically run it.
|
Espruino **and** will automatically run it.
|
||||||
|
@ -115,10 +115,13 @@ and set it to `Load default application`.
|
||||||
## Example Applications
|
## Example Applications
|
||||||
|
|
||||||
To make the process easier we've come up with some example applications that you can use as a base
|
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`
|
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/7chname`, and add `apps/_example_X/add_to_apps.json` to
|
or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to
|
||||||
`apps.json`.
|
`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
|
**If you're making a widget** please start the name with `wid` to make
|
||||||
it easy to find!
|
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
|
"name":"Short Name", // for Bangle.js menu
|
||||||
"icon":"*7chname", // for Bangle.js menu
|
"icon":"*myappid", // for Bangle.js menu
|
||||||
"src":"-7chname", // source file
|
"src":"-myappid", // source file
|
||||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
"type":"widget/clock/app/bootloader", // optional, default "app"
|
||||||
// if this is 'widget' then it's not displayed in the menu
|
// 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
|
// if it's 'clock' then it'll be loaded by default at boot time
|
||||||
|
@ -220,6 +223,7 @@ and which gives information about the app for the Launcher.
|
||||||
"version": "0v01", // the version of this app
|
"version": "0v01", // the version of this app
|
||||||
"description": "...", // long description (can contain markdown)
|
"description": "...", // long description (can contain markdown)
|
||||||
"icon": "icon.png", // icon in apps/
|
"icon": "icon.png", // icon in apps/
|
||||||
|
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
|
||||||
"type":"...", // optional(if app) -
|
"type":"...", // optional(if app) -
|
||||||
// 'app' - an application
|
// 'app' - an application
|
||||||
// 'widget' - a widget
|
// 'widget' - a widget
|
||||||
|
@ -229,6 +233,7 @@ and which gives information about the app for the Launcher.
|
||||||
"tags": "", // comma separated tag list for searching
|
"tags": "", // comma separated tag list for searching
|
||||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
"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'
|
// 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
|
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||||
// that contains more information about this app (usage, etc)
|
// that contains more information about this app (usage, etc)
|
||||||
|
@ -261,6 +266,9 @@ and which gives information about the app for the Launcher.
|
||||||
// (eg it's evaluated as JS)
|
// (eg it's evaluated as JS)
|
||||||
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
||||||
// already exists
|
// 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
|
"data": [ // list of files the app writes to
|
||||||
|
@ -308,10 +316,10 @@ version of what's in `apps.json`:
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("upload").addEventListener("click", function() {
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
sendCustomizedApp({
|
sendCustomizedApp({
|
||||||
id : "7chname",
|
id : "myappid",
|
||||||
storage:[
|
storage:[
|
||||||
{name:"7chname.app.js", url:"app.js", content:app_source_code},
|
{name:"myappid.app.js", url:"app.js", content:app_source_code},
|
||||||
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
{name:"myappid.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -369,40 +377,41 @@ that handles configuring the app.
|
||||||
When the app settings are opened, this function is called with one
|
When the app settings are opened, this function is called with one
|
||||||
argument, `back`: a callback to return to the settings menu.
|
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.
|
of your app - so you should change the example accordingly.
|
||||||
|
|
||||||
Example `settings.js`
|
Example `settings.js`
|
||||||
```js
|
```js
|
||||||
// make sure to enclose the function in parentheses
|
// make sure to enclose the function in parentheses
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
let settings = require('Storage').readJSON('myappid.json',1)||{};
|
||||||
|
if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
|
||||||
function save(key, value) {
|
function save(key, value) {
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
require('Storage').write('app.json',settings);
|
require('Storage').write('myappid.json', settings);
|
||||||
}
|
}
|
||||||
const appMenu = {
|
const appMenu = {
|
||||||
'': {'title': 'App Settings'},
|
'': {'title': 'App Settings'},
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
'Monkeys': {
|
'Monkeys': {
|
||||||
value: settings.monkeys||12,
|
value: settings.monkeys,
|
||||||
onchange: (m) => {save('monkeys', m)}
|
onchange: (m) => {save('monkeys', m)}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
E.showMenu(appMenu)
|
E.showMenu(appMenu)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
In this example the app needs to add `myappid.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.
|
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||||
```json
|
```json
|
||||||
{ "id": "app",
|
{ "id": "myappid",
|
||||||
...
|
...
|
||||||
"storage": [
|
"storage": [
|
||||||
...
|
...
|
||||||
{"name":"app.settings.js","url":"settings.js"},
|
{"name":"myappid.settings.js","url":"settings.js"}
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name":"app.json"}
|
{"name":"myappid.json"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
@ -437,7 +446,7 @@ from the IDE.
|
||||||
|
|
||||||
### Misc Notes
|
### 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.
|
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
theme: jekyll-theme-minimal
|
|
@ -152,6 +152,8 @@
|
||||||
"no-prototype-builtins": "off",
|
"no-prototype-builtins": "off",
|
||||||
"no-redeclare": "off",
|
"no-redeclare": "off",
|
||||||
"no-unreachable": "warn",
|
"no-unreachable": "warn",
|
||||||
|
"no-cond-assign": "warn",
|
||||||
|
"no-useless-catch": "warn",
|
||||||
// TODO: "no-undef": "warn",
|
// TODO: "no-undef": "warn",
|
||||||
"no-undef": "off",
|
"no-undef": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
|
After Width: | Height: | Size: 650 B |
|
@ -0,0 +1,5 @@
|
||||||
|
0.01: Initial version for upload
|
||||||
|
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
|
||||||
|
0.03: Code style cleanup
|
||||||
|
0.04: Set 00:00 to 12:00 for 12 hour time
|
||||||
|
0.05: Display time, even on Thursday
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 93 Dub
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
## Creator
|
||||||
|
[@alainsaas](https://github.com/alainsaas)
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgP/AAnAnEH4Ef+eAiEDAoPDz+T/ff/+T3+T/VAj8z/0f4VP51zDoX/5Hzz/z//f5EBAoP+r4FBFIgPBAAP4v5AFABPvrwSB0YFBrtX/+nCI3u/+vhFhh/q/f/9Fhu4NB187v3n/fvCIf/CIIAFRIUB8EAg3QgJmB4H/iAEB//+/lggqUC//wi4FB8AHBj4FB+H/wEzBgPg/0AkE3BIP8gE8n4VBGIN/IAPAsEA//8v6OBAoUjgEIAoPwkMATIN//BQBgfgg/wAoMH/EHEwILB/gNBgFgAocByEB/ED9AoCAoPAgE4gHwgeAgOYgAVBAoMYAoKECAoIVBAoIfBoCRCAAw="))
|
|
@ -0,0 +1,129 @@
|
||||||
|
// assets
|
||||||
|
function getImg() {
|
||||||
|
return require("heatshrink").decompress(atob("2FRgP/ABnxBRP5BJH+gEfBZHghnAv4JFmA+Bj0PBIn3//4h3An4oDAQJWEEIf8AwMEuFOCofAh/QjAWEg4VEwEAnw2DDoKEHEYPwAoUBmgrDhgUHS4XgAwUD/gVC/g+FAAZgEwEf4YqC/EQFQ4NDFgV/4Z3C/EcCo1974VCLAV/V4d7Co9/Co0PCoX+vk4Ko/HCosCRYX5nwTFkEAr/nCokICoL+B/aCGCoMHCoq3EdoraGCosPz4HBcILEJCocBwEHOwQrIgQrHgoHCFYMEgwVJYoMBsEnCofAnkMNQJXH4D4EbQMPkF/xwrEj+/HIkAoAVDj8QueHCoorDCoUDLwd96J0BKwgrHh4VDv+9CosDx6QCCo4HB//8VwvvXgQVDJIYSBCo/sBwaZBgF/NoYVHgH8V4qYDAwUYlAVFEYbFDDgwAGConogf9Zg8DCpP4cIh0Dg0BGAgVE+gVIgUA+AVI+wVE/xAEh5HDEgn+CpEAbgJCCHQoVBn4VJ/ED4ANDAAQVJ4EPPQPAt4VF4BeDColgj/8h/gFYwJBCpF//k//ANDCAYVIcgP+CpH/54VHCAIVB/4VIwYECCocIAwIVBx4VG9+AMITbCYAYJB34VG/UAj4VI7/9Cgw9CJYXAmBtDMAQsIfYhvCCofyvywGB4QFFgYGC/d+agYVLSgf8+ArG/APBD4QVBgh0CAwNwv/fCo4PCCo94s7VDCohnDAoI7Enlv8BZECoRCDAggAB3/3/gzDMAIVFY4IVE4IPBOoZ9DCpXwCoMvCqKxB//3bYywD4BtFAAPfDooVFFYIVGw4VFB4KZFngNE/uPCovgFYgEBuK+Fg4zFCoIrFCovwgQVF+AVFgPxEYzFEbgQVD4EDCoozBYogVCgYVE8bpGCo4HDCoPzBgoVIL4fAg4MGgAIHCofgCszND8BOHK4x2BCofwXgv4h6vGCps/Co6uDAA/7RgIjDDwTaDABPA//9FaAtDCop0FC5YVDLwoAH8//94GD/wVNCYKNECpwPBQggVPNggVBNp4VFFZwAGCquHCqnzCB4"));
|
||||||
|
}
|
||||||
|
var IMAGEWIDTH = 176;
|
||||||
|
var IMAGEHEIGHT = 81;
|
||||||
|
|
||||||
|
Graphics.prototype.setFontMichroma36 = function() {
|
||||||
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
|
||||||
|
};
|
||||||
|
|
||||||
|
Graphics.prototype.setFontMichroma16 = function(scale) {
|
||||||
|
g.setFontCustom(atob("AAAAGAAYAAAAGAB4A/APwD4AeADgAAAAAAA/8H/4YBjAGMAcwBzAHMAcwBzAHMAYYBh/+D/wAAAAABgAOABwAGAA//h/+AAAAAA4+Hn4YZjhmMOYw5jDmMMYwxjDGOMYYxh/GD4YAAAAADBwcHhgGOAYwBzHHMccxxzHHMcc5xhnGH/4PfAAAAAAAOAB4APgB2AGYAxgHGA4YDBgYGD/+P/4AOAAYAAAAAD+cP547BjsGOwc7BzsHOwc7BzsHOwY7zjv+APgAAAAAD/wf/hmGOYYxhzGHMYcxhzGHOYYZhh3uDP4AeAAAEAA4ADgAOAI4DjgeODw4eDjgOcA7gD8APgA8AAAAAAAAAA58H/4bxjmGMYcxhzGHMYcxhzGHOYYbxh/+DnwAAAAADxgfnBnOOMYwxjDHMMcwxzDHMMY4xhjOH/4P/AAAAAABnAGcAAA"), 46, atob("BAgQCBAQEBAQEBAQBA=="), 16+(scale<<8)+(1<<16));
|
||||||
|
};
|
||||||
|
|
||||||
|
// timer
|
||||||
|
var timervalue = 0;
|
||||||
|
var istimeron = false;
|
||||||
|
var timertick;
|
||||||
|
|
||||||
|
Bangle.on('touch',t=>{
|
||||||
|
if (t == 1) {
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (timervalue < 5*60) { timervalue = 1 ; }
|
||||||
|
else { timervalue -= 5*60; }
|
||||||
|
}
|
||||||
|
else if (t == 2) {
|
||||||
|
Bangle.buzz(30);
|
||||||
|
if (!istimeron) {
|
||||||
|
istimeron = true;
|
||||||
|
timertick = setInterval(countDown, 1000);
|
||||||
|
}
|
||||||
|
timervalue += 60*10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function timeToString(duration) {
|
||||||
|
var hrs = ~~(duration / 3600);
|
||||||
|
var mins = ~~((duration % 3600) / 60);
|
||||||
|
var secs = ~~duration % 60;
|
||||||
|
var ret = "";
|
||||||
|
if (hrs > 0) {
|
||||||
|
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||||
|
}
|
||||||
|
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||||
|
ret += "" + secs;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countDown() {
|
||||||
|
timervalue--;
|
||||||
|
|
||||||
|
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||||
|
|
||||||
|
g.setFontAlign(0, -1, 0);
|
||||||
|
g.setFont("6x8").drawString("Timer", 44, g.getHeight()/2-20);
|
||||||
|
g.setFont("Michroma16").drawString(timeToString(timervalue), 44, g.getHeight()/2-10);
|
||||||
|
|
||||||
|
if (timervalue <= 0) {
|
||||||
|
istimeron = false;
|
||||||
|
clearInterval(timertick);
|
||||||
|
|
||||||
|
Bangle.buzz().then(()=>{
|
||||||
|
return new Promise(resolve=>setTimeout(resolve, 500));
|
||||||
|
}).then(()=>{
|
||||||
|
return Bangle.buzz(1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWelcomeMessage() {
|
||||||
|
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||||
|
g.setFontAlign(0, 0).setFont("6x8");
|
||||||
|
g.drawString("Touch right to", 44, 80);
|
||||||
|
g.drawString("start timer", 44, 88);
|
||||||
|
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// time
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
function getGmt() {
|
||||||
|
var d = new Date();
|
||||||
|
var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return gmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeFromTimezone(offset) {
|
||||||
|
return new Date(getGmt().getTime() + offset * 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueNextDraw() {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||||
|
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||||
|
|
||||||
|
var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
|
||||||
|
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
var locale = require("locale");
|
||||||
|
|
||||||
|
var date = new Date();
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.drawString(locale.date(new Date(),1), 125, 68);
|
||||||
|
g.drawString("PAR "+locale.time(getTimeFromTimezone(1),1), 125, 80);
|
||||||
|
g.drawString("TYO "+locale.time(getTimeFromTimezone(9),1), 125, 88);
|
||||||
|
|
||||||
|
queueNextDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||||
|
draw();
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
showWelcomeMessage();
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.1 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
1.00: Release (2021/12/01)
|
||||||
|
1.01: Grey font when timer is frozen (2021/12/04)
|
|
@ -0,0 +1,16 @@
|
||||||
|
# A Speech Timer
|
||||||
|
|
||||||
|
* A timer designed to help keeping your speeches and presentations to time
|
||||||
|
* Vibrates 1-2-3 times and changes screen color within the target time range.
|
||||||
|
* Example for a 5 to 7 minutes speech: vibrates once at 5:00 (green), twice at 6:00 (yellow), thrice at 7:00 (red).
|
||||||
|
* Use the buttons to start a timer
|
||||||
|
* Swipe left or right to choose different target times
|
||||||
|
* Touching the timer on the upper part of the screen locks (or unlocks) the buttons to prevent accidental changes
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

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

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Creator
|
||||||
|
[@alainsaas](https://github.com/alainsaas)
|
||||||
|
|
||||||
|
Contributions are welcome, send me your Pull Requests!
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgP/AD38g4FD8EAAoeAgE/AoUD/EfAgP+AYMPDgQPBw4FB/F///DAoPwAQPjAQPBAQPxDgJVCAoP4gYaCCwIcBAoM/8P8h0HjEP8f4h0Gp0H4/44lj5+H4/54lzj/jx/5/lyDgIFDh/xAoQRBAoXsuY8Bx4jCAoeEkYFB447CAoRxBOAPxM4RmC8IFD4ZZD/8H/DHDh/+AoaSBUAIABCoYATVwS2Ct4FE84REXQQLCk4RJAo0XGxY="))
|
|
@ -0,0 +1,98 @@
|
||||||
|
Graphics.prototype.setFontMichroma36 = function() {
|
||||||
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAAAAAAAAAAAAAAAAAAAGAAAAA+AAAAD+AAAAP+AAAA/8AAAD/wAAAf/AAAB/4AAAH/gAAAf+AAAB/4AAAH/gAAAf+AAAAfwAAAAfAAAAAcAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AP///8APwAD+APAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAPAeAAAeAPAAAeAPwAD+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAEAAAAAOAAAAAfAAAAA+AAAAB8AAAAD8AAAAH4AAAAPwAAAAPgAAAAfAAAAAf///+Af///+Af///+Af///+AAAAAAAAAAAAAAAAAAAAAAAAAA/Af+AD/A/+AH/B/+AP/D/+APwD4eAPADweAfADweAeADweAeADweAeADweAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAHgeAeAPgeAeAPAeAeAPAeAeAPAeAeAPAeAfAPAeAPw/AeAP/+AeAH/+AeAD/8AeAB/wAOAAAAAAAAAAAAAAAAAAAAAAAAAB8APgAD8AP4AH8AP8AP8AP8APgAB+AfAAAeAeAAAeAeAAAPAeAAAPAeAAAPAeAAAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAPAeAeAeAfAeAeAPx/h+AP///+AH///8AD///4AB/h/gAAAAAAAAAAAAAAAAAAAAAAeAAAAA/AAAAA/AAAAB/AAAAD/AAAAH/AAAAPvAAAAPPAAAAfPAAAA+PAAAB8PAAAD4PAAADwPAAAHwPAAAPgPAAAfAPAAA+APAAA8APAAB8APAAD4APAAHwAPAAPgAPAAPAAPAAfAAPAAf///+Af///+Af///+Af///+AAAAPAAAAAPAAAAAPAAAAAPAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAf/8PgAf/8P4Af/8P8Af/8P8AeB4A+AeB4AeAeDwAeAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAPAeDwAfAeDwAeAeD4A+AeD+D+AeB//8AeB//4AeA//4AAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAA///AAD///wAH///4AH///8AP4fB+APAeAeAfA8AeAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAfA8APAPA+AeAPgeAeAP8fh+AH8f/8AD8P/8AA8H/4AAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAAAAeAAACAeAAAGAeAAAOAeAAAeAeAAA+AeAAD+AeAAH8AeAAP4AeAAfwAeAA/gAeAB/AAeAD+AAeAP4AAeAfwAAeA/gAAeB/AAAeD+AAAeH8AAAefwAAAe/gAAAf/AAAAf+AAAAf8AAAAf4AAAAfgAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAMAAB+B/wAD/j/4AH/3/8AP///+AP//A+AfB+AeAeA+AeAeA+APAeA+APAeA+APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA8APAeA+APAeA+APAeA+APAeA+AOAeA+AeAPh/A+AP///+AP/3/8AH/3/8AB/D/wAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAD/4HAAH/8HwAP/+H4AP5/H8AfAfA8AeAPAeAeAPAeAeAPAeAeAHgfAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHgPAeAHAPAeAPAOAeAPAeAPAPAeAPwfB+AP///8AH///4AD///wAA///AAAAAAAAAAAAAAAAAAAAAAAAAAAB8DwAAB8HwAAB8HwAAB8DwAAAAAAAAAAAAA"), 46, atob("CBIkESMjJCMjIyMjCA=="), 36+(1<<8)+(1<<16));
|
||||||
|
};
|
||||||
|
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
function queueNextDraw() {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 1000 - (Date.now() % 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
var locale = require("locale");
|
||||||
|
|
||||||
|
var bt_current_co2 = 0;
|
||||||
|
var bt_current_voc = 0;
|
||||||
|
var bt_current_pm25 = 0;
|
||||||
|
var bt_current_humi = 0;
|
||||||
|
var bt_current_temp = 0;
|
||||||
|
var bt_last_update = 0;
|
||||||
|
|
||||||
|
var last_update = 0;
|
||||||
|
var bt_co2_history = new Array(10).fill(0);
|
||||||
|
var bt_voc_history = new Array(10).fill(0);
|
||||||
|
var bt_pm25_history = new Array(10).fill(0);
|
||||||
|
var bt_humi_history = new Array(10).fill(0);
|
||||||
|
var bt_temp_history = new Array(10).fill(0);
|
||||||
|
|
||||||
|
var internal_last_update = -1;
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||||
|
|
||||||
|
var date = new Date();
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 56);
|
||||||
|
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.drawString(locale.date(new Date(),1), g.getWidth()/2, 80);
|
||||||
|
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.drawString("CO2", 20, 100);
|
||||||
|
g.drawString("VOC", 55, 100);
|
||||||
|
g.drawString("PM25", 90, 100);
|
||||||
|
g.drawString("Humi", 125, 100);
|
||||||
|
g.drawString("Temp", 160, 100);
|
||||||
|
|
||||||
|
g.setFont("HaxorNarrow7x17");
|
||||||
|
g.drawString(""+bt_current_co2, 18, 110);
|
||||||
|
g.drawString(""+bt_current_voc, 53, 110);
|
||||||
|
g.drawString(""+bt_current_pm25, 88, 110);
|
||||||
|
g.drawString(""+bt_current_humi, 123, 110);
|
||||||
|
g.drawString(""+bt_current_temp, 158, 110);
|
||||||
|
|
||||||
|
if (last_update != bt_last_update) {
|
||||||
|
last_update = bt_last_update;
|
||||||
|
internal_last_update = last_update;
|
||||||
|
if (last_update % 10 == 0) {
|
||||||
|
bt_co2_history.shift(); bt_co2_history.push(bt_current_co2);
|
||||||
|
bt_voc_history.shift(); bt_voc_history.push(bt_current_voc);
|
||||||
|
bt_pm25_history.shift(); bt_pm25_history.push(bt_current_pm25);
|
||||||
|
bt_humi_history.shift(); bt_humi_history.push(bt_current_humi);
|
||||||
|
bt_temp_history.shift(); bt_temp_history.push(bt_current_temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internal_last_update == -1) {
|
||||||
|
g.drawString("Waiting for connection", 88, 164);
|
||||||
|
} else if (internal_last_update > last_update + 5) {
|
||||||
|
g.drawString("Trying to reconnect since " + (internal_last_update - last_update), 88, 164);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
// max height = 32
|
||||||
|
g.drawLine(10+i*2, 150-(Math.min(Math.max(bt_co2_history[i],400), 1200)-400)/25, 10+i*2, 150);
|
||||||
|
g.drawLine(45+i*2, 150-(Math.min(Math.max(bt_voc_history[i],0), 1440)-0)/45, 45+i*2, 150);
|
||||||
|
g.drawLine(80+i*2, 150-(Math.min(Math.max(bt_pm25_history[i],0), 32)-0)/1, 80+i*2, 150);
|
||||||
|
g.drawLine(115+i*2, 150-(Math.min(Math.max(bt_humi_history[i],20), 60)-20)/1.25, 115+i*2, 150);
|
||||||
|
g.drawLine(150+i*2, 150-(Math.min(Math.max(bt_temp_history[i],19), 27)-19)*4, 150+i*2, 150);
|
||||||
|
|
||||||
|
// target humidity level
|
||||||
|
g.setColor("#00F").drawLine(115, 150-(40-20)/1.25, 115+18, 150-(40-20)/1.25);
|
||||||
|
g.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internal_last_update != -1) { internal_last_update++; }
|
||||||
|
queueNextDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
require("FontHaxorNarrow7x17").add(Graphics);
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
draw();
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 146 KiB |
|
@ -0,0 +1,195 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://puck-js.com/puck.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
// Don't forget to enable the Local API on your Awair before using this
|
||||||
|
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||||
|
|
||||||
|
const awair_ip_1 = "192.168.2.2"; // <- INPUT YOUR AWAIR IP ADDRESS HERE
|
||||||
|
const awair_name_1 = "Awair";
|
||||||
|
|
||||||
|
var bt_connection;
|
||||||
|
var is_connected = false;
|
||||||
|
var reconnect_counter = 5;
|
||||||
|
var reconnect_attempt_counter = 1;
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var chart_co2;
|
||||||
|
var chart_voc;
|
||||||
|
var chart_pm;
|
||||||
|
var chart_temperature;
|
||||||
|
var chart_humidity;
|
||||||
|
var dataPoints_1 = [];
|
||||||
|
var posx = 0;
|
||||||
|
|
||||||
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||||
|
$.each(data, function(key, value){
|
||||||
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||||
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||||
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||||
|
});
|
||||||
|
|
||||||
|
posx++;
|
||||||
|
|
||||||
|
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
||||||
|
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||||
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||||
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
||||||
|
});
|
||||||
|
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
||||||
|
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||||
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||||
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
||||||
|
});
|
||||||
|
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
||||||
|
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||||
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||||
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
||||||
|
});
|
||||||
|
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
||||||
|
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||||
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||||
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
||||||
|
});
|
||||||
|
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
||||||
|
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
||||||
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
||||||
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
||||||
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
||||||
|
});
|
||||||
|
|
||||||
|
chart_co2.set("backgroundColor", "#1A202C");
|
||||||
|
chart_voc.set("backgroundColor", "#1A202C");
|
||||||
|
chart_pm.set("backgroundColor", "#1A202C");
|
||||||
|
chart_humidity.set("backgroundColor", "#1A202C");
|
||||||
|
chart_temperature.set("backgroundColor", "#1A202C");
|
||||||
|
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateChart() {
|
||||||
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
||||||
|
$.each(data, function(key, value){
|
||||||
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
||||||
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
||||||
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
||||||
|
});
|
||||||
|
|
||||||
|
posx++;
|
||||||
|
chart_co2.render();
|
||||||
|
chart_voc.render();
|
||||||
|
chart_pm.render();
|
||||||
|
chart_temperature.render();
|
||||||
|
chart_humidity.render();
|
||||||
|
|
||||||
|
chart_co2.title.set("text", "CO2 level (ppm)");
|
||||||
|
chart_voc.title.set("text", "VOC level (ppb)");
|
||||||
|
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
||||||
|
chart_humidity.title.set("text", "Humidity level (%)");
|
||||||
|
chart_temperature.title.set("text", "Temperature level (°C)");
|
||||||
|
|
||||||
|
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
||||||
|
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
||||||
|
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
||||||
|
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
||||||
|
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
||||||
|
let last_update = dataPoints_1['temp'].length-1;
|
||||||
|
if (is_connected && bt_connection.isOpen) {
|
||||||
|
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
||||||
|
|
||||||
|
console.log("Sent data through Bluetooth");
|
||||||
|
} else if (is_connected && !bt_connection.isOpen) {
|
||||||
|
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
||||||
|
reconnect_counter--;
|
||||||
|
|
||||||
|
if (reconnect_counter <= 0) {
|
||||||
|
reconnect_counter = 10 * reconnect_attempt_counter;
|
||||||
|
reconnect_attempt_counter++;
|
||||||
|
|
||||||
|
console.log("Trying to reconnect");
|
||||||
|
bt_connection.reconnect(function(c) {
|
||||||
|
console.log("Reconnect callback");
|
||||||
|
if (!c) {
|
||||||
|
console.log("Couldn't reconnect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bt_connection = c;
|
||||||
|
is_connected = true;
|
||||||
|
reconnect_attempt_counter = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function(){updateChart()}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectBT() {
|
||||||
|
console.log("Connect BT");
|
||||||
|
Puck.connect(function(c) {
|
||||||
|
console.log("Connect callback");
|
||||||
|
if (!c) {
|
||||||
|
console.log("Couldn't connect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bt_connection = c;
|
||||||
|
is_connected = true;
|
||||||
|
reconnect_attempt_counter = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnectBT() {
|
||||||
|
if (is_connected && bt_connection) {
|
||||||
|
bt_connection.close();
|
||||||
|
is_connected = false;
|
||||||
|
console.log("Closed Bluetooth connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="https://canvasjs.com/assets/script/jquery-1.11.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body style="background-color:#1A202C;">
|
||||||
|
|
||||||
|
<p style="color: #F7FAFC">
|
||||||
|
<b>How to use</b>
|
||||||
|
<br/><br/>
|
||||||
|
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
||||||
|
<br/><br/>
|
||||||
|
Step 2: Modify this HTML file to input the IP address of your Awair on top (const awair_ip_1 = "192.168.xx.xx")
|
||||||
|
<br/><br/>
|
||||||
|
Step 3: Launch the Awair Monitor app on your BangleJS
|
||||||
|
<br/><br/>
|
||||||
|
Step 4: Click "Connect BangleJS"
|
||||||
|
<br/><br/>
|
||||||
|
Step 5: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the bluetooth logs
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<center>
|
||||||
|
<button onclick="connectBT();">Connect BangleJS</button>
|
||||||
|
<button onclick="disconnectBT();">Disconnect BangleJS</button>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||||
|
<div id="chartContainer_voc" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||||
|
<div id="chartContainer_pm" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||||
|
<div id="chartContainer_humidity" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||||
|
<div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -5,4 +5,5 @@
|
||||||
0.05: Clock does not start if app Languages is not installed
|
0.05: Clock does not start if app Languages is not installed
|
||||||
0.06: Improve accuracy
|
0.06: Improve accuracy
|
||||||
0.07: Update to use Bangle.setUI instead of setWatch
|
0.07: Update to use Bangle.setUI instead of setWatch
|
||||||
0.08: Use theme colors, Layout library
|
0.08: Use theme colors, Layout library
|
||||||
|
0.09: Fix time/date disappearing after fullscreen notification
|
||||||
|
|
|
@ -24,7 +24,7 @@ function renderBar(l) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const width = this.fraction*l.w;
|
const width = this.fraction*l.w;
|
||||||
g.fillRect(l.x, l.y, width-1, l.y+l.height-1);
|
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = require("Layout");
|
const Layout = require("Layout");
|
||||||
|
@ -78,7 +78,7 @@ function dateText(date) {
|
||||||
return `${dayName} ${dayMonth}`;
|
return `${dayName} ${dayMonth}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw = function draw() {
|
draw = function draw(force) {
|
||||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
layout.time.label = timeText(date);
|
layout.time.label = timeText(date);
|
||||||
|
@ -86,6 +86,10 @@ draw = function draw() {
|
||||||
layout.date.label = dateText(date);
|
layout.date.label = dateText(date);
|
||||||
const SECONDS_PER_MINUTE = 60;
|
const SECONDS_PER_MINUTE = 60;
|
||||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||||
|
if (force) {
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
layout.forgetLazyState();
|
||||||
|
}
|
||||||
layout.render();
|
layout.render();
|
||||||
// schedule update at start of next second
|
// schedule update at start of next second
|
||||||
const millis = date.getMilliseconds();
|
const millis = date.getMilliseconds();
|
||||||
|
@ -96,7 +100,7 @@ draw = function draw() {
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
Bangle.on("lcdPower", function(on) {
|
Bangle.on("lcdPower", function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
draw();
|
draw(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
g.reset().clear();
|
g.reset().clear();
|
||||||
|
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
|
@ -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)
|
|
@ -0,0 +1,47 @@
|
||||||
|
# TheBinWatch
|
||||||
|
|
||||||
|
Binary watch to train Your brain
|
||||||
|
Inspired by the LCD wrist watch from TecRAL from 1989
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- swipe to left or right to change displayed text (date, time, ...)
|
||||||
|
- currently only available for BangeJs2
|
||||||
|
- Widgets will not be shown
|
||||||
|
- If bluetooth connection is not established an icon will show up
|
||||||
|
|
||||||
|
## 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
|
|
@ -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=="))
|
|
@ -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();
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 708 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
|
@ -35,3 +35,11 @@
|
||||||
improve g.stringMetrics polyfill
|
improve g.stringMetrics polyfill
|
||||||
Fix issue where re-running bootupdate could disable existing polyfills
|
Fix issue where re-running bootupdate could disable existing polyfills
|
||||||
0.33: Add E.showScroller polyfill
|
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
|
||||||
|
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||||
|
0.38: Option to log to file if settings.log==2
|
||||||
|
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||||
|
|
|
@ -3,21 +3,34 @@ recalculates, but this avoids us doing a whole bunch of reconfiguration most
|
||||||
of the time. */
|
of the time. */
|
||||||
E.showMessage("Updating boot0...");
|
E.showMessage("Updating boot0...");
|
||||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
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 boot = "";
|
||||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||||
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`;
|
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 += `E.setFlags({pretokenise:1});\n`;
|
||||||
|
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||||
if (s.ble!==false) {
|
if (s.ble!==false) {
|
||||||
if (s.HID) { // Human interface device
|
if (s.HID) { // Human interface device
|
||||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
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 if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));`
|
||||||
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`;
|
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
|
if (s.log==2) { // logging to file
|
||||||
if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||||
|
`;
|
||||||
|
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||||
|
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||||
|
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||||
|
LoopbackA.setConsole(true);\n`;
|
||||||
|
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||||
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||||
/* If not programmable add our own handler for Bluetooth data
|
/* If not programmable add our own handler for Bluetooth data
|
||||||
to allow Gadgetbridge commands to be received*/
|
to allow Gadgetbridge commands to be received*/
|
||||||
|
@ -34,7 +47,10 @@ Bluetooth.on('line',function(l) {
|
||||||
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
||||||
});\n`;
|
});\n`;
|
||||||
} else {
|
} else {
|
||||||
if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
|
||||||
|
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
|
||||||
|
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`;
|
||||||
|
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||||
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
|
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
|
||||||
}
|
}
|
||||||
// we just reset, so BLE should be on.
|
// we just reset, so BLE should be on.
|
||||||
|
@ -48,7 +64,7 @@ boot += `E.setTimeZone(${s.timezone});`;
|
||||||
if (!Bangle.F_BEEPSET) {
|
if (!Bangle.F_BEEPSET) {
|
||||||
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
||||||
if (s.beep===false) boot += `Bangle.beep=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) {
|
return new Promise(function(resolve) {
|
||||||
if ((0|freq)<=0) freq=4000;
|
if ((0|freq)<=0) freq=4000;
|
||||||
if ((0|time)<=0) time=200;
|
if ((0|time)<=0) time=200;
|
||||||
|
@ -71,14 +87,8 @@ boot += `E.on('errorFlag', function(errorFlags) {
|
||||||
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
||||||
// Apply any settings-specific stuff
|
// Apply any settings-specific stuff
|
||||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||||
if (s.quiet && s.qmOptions) boot+=`Bangle.setOptions(${E.toJS(s.qmOptions)});\n`;
|
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||||
if (s.quiet && s.qmBrightness) {
|
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||||
if (s.qmBrightness!=1) boot+=`Bangle.setLCDBrightness(${s.qmBrightness});\n`;
|
|
||||||
} else {
|
|
||||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
|
||||||
}
|
|
||||||
if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
|
||||||
if (s.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`;
|
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
|
// Pre-2v10 firmwares without a theme/setUI
|
||||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||||
|
@ -96,7 +106,7 @@ if (Bangle.swipeHandler) {
|
||||||
Bangle.removeListener("swipe", Bangle.swipeHandler);
|
Bangle.removeListener("swipe", Bangle.swipeHandler);
|
||||||
delete Bangle.swipeHandler;
|
delete Bangle.swipeHandler;
|
||||||
}
|
}
|
||||||
if (Bangle.touchandler) {
|
if (Bangle.touchHandler) {
|
||||||
Bangle.removeListener("touch", Bangle.touchHandler);
|
Bangle.removeListener("touch", Bangle.touchHandler);
|
||||||
delete Bangle.touchHandler;
|
delete Bangle.touchHandler;
|
||||||
}
|
}
|
||||||
|
@ -135,8 +145,8 @@ else if (mode=="updown") {
|
||||||
}
|
}
|
||||||
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
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
|
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.fgG).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(),
|
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.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`;
|
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
|
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
|
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||||
|
@ -176,14 +186,23 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
|
||||||
return lines;
|
return lines;
|
||||||
};\n`;
|
};\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
|
// Append *.boot.js files
|
||||||
|
// These could change bleServices/bleServiceOptions if needed
|
||||||
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
||||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||||
// which would cause an error!
|
// 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);
|
require('Storage').write('.boot0',boot);
|
||||||
delete boot;
|
delete boot;
|
||||||
E.showMessage("Reloading...");
|
E.showMessage("Reloading...");
|
||||||
|
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -1,2 +1,3 @@
|
||||||
0.01: Basic calendar
|
0.01: Basic calendar
|
||||||
0.02: Make Bangle 2 compatible
|
0.02: Make Bangle 2 compatible
|
||||||
|
0.03: Add setting to start week on Sunday
|
||||||
|
|
|
@ -6,3 +6,8 @@ Basic calendar
|
||||||
|
|
||||||
- Use `BTN4` (left screen tap) to go to the previous month
|
- Use `BTN4` (left screen tap) to go to the previous month
|
||||||
- Use `BTN5` (right screen tap) to go to the next month
|
- Use `BTN5` (right screen tap) to go to the next month
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
- Starts on Sunday: whether the calendar should start on Sunday (default is Monday).
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ const gray2 = "#888888";
|
||||||
const gray3 = "#bbbbbb";
|
const gray3 = "#bbbbbb";
|
||||||
const red = "#d41706";
|
const red = "#d41706";
|
||||||
|
|
||||||
|
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||||
|
if (settings.startOnSun === undefined)
|
||||||
|
settings.startOnSun = false;
|
||||||
|
|
||||||
function drawCalendar(date) {
|
function drawCalendar(date) {
|
||||||
g.setBgColor(color4);
|
g.setBgColor(color4);
|
||||||
g.clearRect(0, 0, maxX, maxY);
|
g.clearRect(0, 0, maxX, maxY);
|
||||||
|
@ -61,13 +65,18 @@ function drawCalendar(date) {
|
||||||
);
|
);
|
||||||
|
|
||||||
g.setFont("6x8", fontSize);
|
g.setFont("6x8", fontSize);
|
||||||
const dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
let dowLbls;
|
||||||
|
if (settings.startOnSun) {
|
||||||
|
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
||||||
|
} else {
|
||||||
|
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||||
|
}
|
||||||
dowLbls.forEach((lbl, i) => {
|
dowLbls.forEach((lbl, i) => {
|
||||||
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
|
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
date.setDate(1);
|
date.setDate(1);
|
||||||
const dow = date.getDay();
|
const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
|
||||||
const dowNorm = dow === 0 ? 7 : dow;
|
const dowNorm = dow === 0 ? 7 : dow;
|
||||||
|
|
||||||
const monthMaxDayMap = {
|
const monthMaxDayMap = {
|
||||||
|
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,24 @@
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "calendar.json";
|
||||||
|
var settings = require('Storage').readJSON(FILE, true) || {};
|
||||||
|
if (settings.startOnSun === undefined)
|
||||||
|
settings.startOnSun = true;
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
"" : { "title" : "Calendar" },
|
||||||
|
"< Back" : () => back(),
|
||||||
|
'Start on Sunday': {
|
||||||
|
value: settings.startOnSun,
|
||||||
|
format: v => v?"Yes":"No",
|
||||||
|
onchange: v => {
|
||||||
|
settings.startOnSun = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Bangle.js 2 compatibility
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
g.setBgColor(0, 0, 0);
|
||||||
g.clear().flip();
|
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 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("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
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 = [];
|
var bubbles = [];
|
||||||
for (var i=0;i<10;i++) {
|
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()});
|
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
|
/* 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
|
at a time (image contains a background) too, and there is minimal
|
||||||
flicker. */
|
flicker. */
|
||||||
var mx = 120, my = 120;
|
var mx = W/2.0, my = H/2.0;
|
||||||
bubbles.forEach(f=>{
|
bubbles.forEach(f=>{
|
||||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
f.y-=f.v * b2v;
|
||||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
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();
|
g.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.7 KiB |
|
@ -1,3 +1,5 @@
|
||||||
0.01: New widget and app!
|
0.01: New widget and app!
|
||||||
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
|
0.02: Setting to reset values, timer buzzes at 00:00 and not later (see readme)
|
||||||
0.03: Display only minutes:seconds when less than 1 hour left
|
0.03: Display only minutes:seconds when less than 1 hour left
|
||||||
|
0.04: Change to 7 segment font, move to top widget bar
|
||||||
|
Better auto-update behaviour, less RAM used
|
||||||
|
|
|
@ -5,14 +5,13 @@ The advantage is, that you can still see your normal watchface and other widgets
|
||||||
The widget is always active, but only shown when the timer is on.
|
The widget is always active, but only shown when the timer is on.
|
||||||
Hours, minutes, seconds and timer status can be set with an app.
|
Hours, minutes, seconds and timer status can be set with an app.
|
||||||
|
|
||||||
When there is less than one seconds left on the timer it buzzes.
|
When there is less than one second left on the timer it buzzes.
|
||||||
|
|
||||||
The widget has been tested on Bangle 1 and Bangle 2
|
The widget has been tested on Bangle 1 and Bangle 2
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -28,15 +27,15 @@ There are no settings section in the settings app, timer can be set using an app
|
||||||
* Hours: Set the hours for the timer
|
* Hours: Set the hours for the timer
|
||||||
* Minutes: Set the minutes for the timer
|
* Minutes: Set the minutes for the timer
|
||||||
* Seconds: Set the seconds for the timer
|
* Seconds: Set the seconds for the timer
|
||||||
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
* Timer on: Starts the timer and displays the widget when set to 'On'. You have to leave the app to load the widget which starts the timer. The widget is always there, but only visible when timer is on.
|
||||||
|
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
* Offifical app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
* Official app loader: https://github.com/espruino/BangleApps/tree/master/apps/chronowid (https://banglejs.com/apps/)
|
||||||
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
|
* Forked app loader: https://github.com/Purple-Tentacle/BangleApps/tree/master/apps/chronowid (https://purple-tentacle.github.io/BangleApps/index.html#)
|
||||||
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
|
* Development: https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/chronowid
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
If you have any feature requests, please write here: http://forum.espruino.com/conversations/345972/
|
||||||
|
|
|
@ -3,7 +3,6 @@ Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
const boolFormat = v => v ? "On" : "Off";
|
|
||||||
let settingsChronowid;
|
let settingsChronowid;
|
||||||
|
|
||||||
function updateSettings() {
|
function updateSettings() {
|
||||||
|
@ -12,6 +11,7 @@ function updateSettings() {
|
||||||
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
|
now.getHours() + settingsChronowid.hours, now.getMinutes() + settingsChronowid.minutes, now.getSeconds() + settingsChronowid.seconds);
|
||||||
settingsChronowid.goal = goal.getTime();
|
settingsChronowid.goal = goal.getTime();
|
||||||
storage.writeJSON('chronowid.json', settingsChronowid);
|
storage.writeJSON('chronowid.json', settingsChronowid);
|
||||||
|
if (WIDGETS["chronowid"]) WIDGETS["chronowid"].reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSettings() {
|
function resetSettings() {
|
||||||
|
@ -44,6 +44,7 @@ function showMenu() {
|
||||||
timerMenu.started.value = settingsChronowid.started;
|
timerMenu.started.value = settingsChronowid.started;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'< Back' : ()=>{load();},
|
||||||
'Reset values': function() {
|
'Reset values': function() {
|
||||||
settingsChronowid.hours = 0;
|
settingsChronowid.hours = 0;
|
||||||
settingsChronowid.minutes = 0;
|
settingsChronowid.minutes = 0;
|
||||||
|
@ -84,15 +85,15 @@ function showMenu() {
|
||||||
},
|
},
|
||||||
'Timer on': {
|
'Timer on': {
|
||||||
value: settingsChronowid.started,
|
value: settingsChronowid.started,
|
||||||
format: boolFormat,
|
format: v => v ? "On" : "Off",
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settingsChronowid.started = v;
|
settingsChronowid.started = v;
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
timerMenu['-Exit-'] = ()=>{load();};
|
|
||||||
return E.showMenu(timerMenu);
|
return E.showMenu(timerMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
showMenu();
|
showMenu();
|
||||||
|
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 2.9 KiB |