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
|
||||||
|
|
|
@ -6,4 +6,6 @@ package-lock.json
|
||||||
appdates.csv
|
appdates.csv
|
||||||
.vscode
|
.vscode
|
||||||
.idea/
|
.idea/
|
||||||
_config.yml
|
_config.yml
|
||||||
|
tests/Layout/bin/tmp.*
|
||||||
|
tests/Layout/testresult.bmp
|
||||||
|
|
76
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, nu 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
|
||||||
|
@ -217,8 +220,10 @@ and which gives information about the app for the Launcher.
|
||||||
{ "id": "appid", // 7 character app id
|
{ "id": "appid", // 7 character app id
|
||||||
"name": "Readable name", // readable name
|
"name": "Readable name", // readable name
|
||||||
"shortName": "Short name", // short name for launcher
|
"shortName": "Short name", // short name for launcher
|
||||||
"icon": "icon.png", // icon in apps/
|
"version": "0v01", // the version of this app
|
||||||
"description": "...", // long description (can contain markdown)
|
"description": "...", // long description (can contain markdown)
|
||||||
|
"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
|
||||||
|
@ -226,7 +231,9 @@ and which gives information about the app for the Launcher.
|
||||||
// 'bootloader' - code that runs at startup only
|
// 'bootloader' - code that runs at startup only
|
||||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||||
"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
|
||||||
"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)
|
||||||
|
@ -237,6 +244,11 @@ and which gives information about the app for the Launcher.
|
||||||
// like this one with 'storage','name' and 'id' set up
|
// like this one with 'storage','name' and 'id' set up
|
||||||
// see below for more info
|
// see below for more info
|
||||||
|
|
||||||
|
"customConnect": true, // if supplied, ensure we are connected to a device
|
||||||
|
// before the "custom.html" iframe is loaded. An
|
||||||
|
// onInit function in "custom.html" is then called
|
||||||
|
// with info on the currently connected device.
|
||||||
|
|
||||||
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
|
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
|
||||||
// iframe, and it may interact with the connected Bangle
|
// iframe, and it may interact with the connected Bangle
|
||||||
// to retrieve information from it
|
// to retrieve information from it
|
||||||
|
@ -254,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
|
||||||
|
@ -301,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},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -362,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"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
@ -430,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 |
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset
|
0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset
|
||||||
0.03: Add RMSSD recording
|
0.03: Add RMSSD recording
|
||||||
|
0.04: Modify to work with new heart rate API, but still not sure it's working correctly
|
||||||
|
|
174
apps/HRV/app.js
|
@ -17,22 +17,23 @@ var csv = [
|
||||||
logfile.write(csv.join(",")+"\n");
|
logfile.write(csv.join(",")+"\n");
|
||||||
|
|
||||||
var debugging = true;
|
var debugging = true;
|
||||||
|
var samples = 0; // how many samples have we connected?
|
||||||
|
var collectData = false; // are we currently collecting data?
|
||||||
|
|
||||||
var first_signals = 0; // ignore the first several signals
|
|
||||||
var heartrate = [];
|
|
||||||
var BPM_array = [];
|
var BPM_array = [];
|
||||||
var raw_HR_array = new Float32Array(1536);
|
var raw_HR_array = new Float32Array(1536);
|
||||||
var alternate_array = new Float32Array(3072);
|
var alternate_array = new Float32Array(3072);
|
||||||
var pulse_array = [];
|
var pulse_array = [];
|
||||||
var pulsecount = 0;
|
|
||||||
var cutoff_threshold = 0.5;
|
var cutoff_threshold = 0.5;
|
||||||
var sample_frequency = 51.6;
|
var sample_frequency = 51.6;
|
||||||
var gap_threshold = 0.15;
|
var gap_threshold = 0.15;
|
||||||
var hr_min = 40;
|
|
||||||
var hr_max = 160;
|
|
||||||
var movement = 0;
|
var movement = 0;
|
||||||
|
|
||||||
function storeMyData(data, file_type) {
|
var px = g.getWidth()/2;
|
||||||
|
var py = g.getHeight()/2;
|
||||||
|
var accel; // interval for acceleration logging
|
||||||
|
|
||||||
|
function storeMyData(data, file_type) { "ram"
|
||||||
log = raw_HR_array;
|
log = raw_HR_array;
|
||||||
// shift elements backwards - note the 4, because a Float32 is 4 bytes
|
// shift elements backwards - note the 4, because a Float32 is 4 bytes
|
||||||
log.set(new Float32Array(log.buffer, 4 /*bytes*/));
|
log.set(new Float32Array(log.buffer, 4 /*bytes*/));
|
||||||
|
@ -41,59 +42,58 @@ function storeMyData(data, file_type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function average(samples) {
|
function average(samples) {
|
||||||
var sum = 0;
|
return E.sum(samples) / samples.length; // faster builtin
|
||||||
|
/* var sum = 0;
|
||||||
for (var i = 0; i < samples.length; i++) {
|
for (var i = 0; i < samples.length; i++) {
|
||||||
sum += parseFloat(samples[i]);
|
sum += parseFloat(samples[i]);
|
||||||
}
|
}
|
||||||
var avg = sum / samples.length;
|
var avg = sum / samples.length;
|
||||||
return avg;
|
return avg;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
function StandardDeviation (array) {
|
function StandardDeviation (array) {
|
||||||
const n = array.length;
|
const n = array.length;
|
||||||
const mean = array.reduce((a, b) => a + b) / n;
|
const mean = E.sum(array) / n; //array.reduce((a, b) => a + b) / n;
|
||||||
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
|
//return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
|
||||||
|
return Math.sqrt(E.variance(array, mean));
|
||||||
}
|
}
|
||||||
|
|
||||||
function turn_off() {
|
function turn_off() {
|
||||||
Bangle.setHRMPower(0);
|
Bangle.setHRMPower(0);
|
||||||
|
|
||||||
var accel = setInterval(function () {
|
|
||||||
movement = movement + Bangle.getAccel().diff;
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("processing 1/5", 120, 120);
|
g.drawString("processing 1/5", px, py);
|
||||||
|
|
||||||
rolling_average(raw_HR_array,5);
|
rolling_average(raw_HR_array,5);
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("processing 2/5", 120, 120);
|
g.drawString("processing 2/5", px, py);
|
||||||
|
|
||||||
upscale();
|
upscale();
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("processing 3/5", 120, 120);
|
g.drawString("processing 3/5", px, py);
|
||||||
|
|
||||||
rolling_average(alternate_array,5);
|
rolling_average(alternate_array,5);
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("processing 4/5", 120, 120);
|
g.drawString("processing 4/5", px, py);
|
||||||
|
|
||||||
apply_cutoff();
|
apply_cutoff();
|
||||||
find_peaks();
|
find_peaks();
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("processing 5/5", 120, 120);
|
g.drawString("processing 5/5", px, py);
|
||||||
|
|
||||||
calculate_HRV();
|
calculate_HRV();
|
||||||
}
|
}
|
||||||
|
|
||||||
function bernstein(A, B, C, D, E, t) {
|
function bernstein(A, B, C, D, E, t) { "ram"
|
||||||
s = 1 - t;
|
s = 1 - t;
|
||||||
x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t)
|
x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t)
|
||||||
+ (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4));
|
+ (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4));
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
function upscale() {
|
function upscale() { "ram"
|
||||||
var index = 0;
|
var index = 0;
|
||||||
for (let i = raw_HR_array.length - 1; i > 5; i -= 5) {
|
for (let i = raw_HR_array.length - 1; i > 5; i -= 5) {
|
||||||
p0 = raw_HR_array[i];
|
p0 = raw_HR_array[i];
|
||||||
|
@ -110,19 +110,18 @@ function upscale() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rolling_average(values, count) {
|
function rolling_average(values, count) { "ram"
|
||||||
var temp_array = [];
|
var temp_array = [];
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
temp_array = [];
|
temp_array = [];
|
||||||
for (let x = 0; x < count; x++)
|
for (let x = 0; x < count; x++)
|
||||||
temp_array.push(values[i + x]);
|
temp_array.push(values[i + x]);
|
||||||
|
|
||||||
values[i] = average(temp_array);
|
values[i] = average(temp_array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply_cutoff() {
|
function apply_cutoff() { "ram"
|
||||||
var x;
|
var x;
|
||||||
for (let i = 0; i < alternate_array.length; i++) {
|
for (let i = 0; i < alternate_array.length; i++) {
|
||||||
x = alternate_array[i];
|
x = alternate_array[i];
|
||||||
|
@ -132,7 +131,7 @@ function apply_cutoff() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function find_peaks() {
|
function find_peaks() { "ram"
|
||||||
var previous;
|
var previous;
|
||||||
var previous_slope = 0;
|
var previous_slope = 0;
|
||||||
var slope;
|
var slope;
|
||||||
|
@ -157,17 +156,17 @@ function find_peaks() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function RMSSD(samples){
|
function RMSSD(samples){ "ram"
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
var square = 0;
|
var square = 0;
|
||||||
var data = [];
|
var data = [];
|
||||||
var value = 0;
|
var value = 0;
|
||||||
|
|
||||||
for (let i = 0; i < samples.length-1; i++) {
|
for (let i = 0; i < samples.length-1; i++) {
|
||||||
value = Math.abs(samples[i]-samples[i+1])*((1 / (sample_frequency * 2)) * 1000);
|
value = Math.abs(samples[i]-samples[i+1])*((1 / (sample_frequency * 2)) * 1000);
|
||||||
data.push(value);
|
data.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
square = data[i] * data[i];
|
square = data[i] * data[i];
|
||||||
Math.round(square);
|
Math.round(square);
|
||||||
|
@ -192,7 +191,7 @@ function calculate_HRV() {
|
||||||
gap_average = average(temp_array);
|
gap_average = average(temp_array);
|
||||||
var calculatedHR = (sample_frequency*60)/(gap_average/2);
|
var calculatedHR = (sample_frequency*60)/(gap_average/2);
|
||||||
if(option == 0)
|
if(option == 0)
|
||||||
g.flip();
|
Bangle.setLCDPower(1);
|
||||||
g.clear();
|
g.clear();
|
||||||
//var display_stdv = StandardDeviation(pulse_array).toFixed(1);
|
//var display_stdv = StandardDeviation(pulse_array).toFixed(1);
|
||||||
var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0);
|
var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0);
|
||||||
|
@ -200,14 +199,13 @@ function calculate_HRV() {
|
||||||
g.drawString("SDNN:" + SDNN
|
g.drawString("SDNN:" + SDNN
|
||||||
+"\nRMSSD:" + RMS_SD
|
+"\nRMSSD:" + RMS_SD
|
||||||
+ "\nHR:" + calculatedHR.toFixed(0)
|
+ "\nHR:" + calculatedHR.toFixed(0)
|
||||||
+"\nSample Count:" + temp_array.length, 120, 120);
|
+"\nSample Count:" + temp_array.length, px, py);
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
if(option == 0){
|
if(option == 0) { // single run
|
||||||
Bangle.buzz(500,1);
|
Bangle.buzz(500,1);
|
||||||
clearInterval(routine);
|
option = null;
|
||||||
}
|
drawButtons();
|
||||||
|
} else {
|
||||||
else{
|
|
||||||
var csv = [
|
var csv = [
|
||||||
0|getTime(),
|
0|getTime(),
|
||||||
temp_array.length,
|
temp_array.length,
|
||||||
|
@ -219,85 +217,87 @@ function calculate_HRV() {
|
||||||
];
|
];
|
||||||
logfile.write(csv.join(",")+"\n");
|
logfile.write(csv.join(",")+"\n");
|
||||||
|
|
||||||
movement = 0;
|
|
||||||
// for (let i = 0; i < raw_HR_array.length; i++) {
|
// for (let i = 0; i < raw_HR_array.length; i++) {
|
||||||
// raw_HR_array[i] = null;
|
// raw_HR_array[i] = null;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
turn_on();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function btn1Pressed() {
|
function btn1Pressed() {
|
||||||
if(option === null){
|
if(option === null){
|
||||||
clearInterval(accel);
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("one-off assessment", 120, 120);
|
g.drawString("one-off assessment", px, py);
|
||||||
option = 0;
|
option = 0;
|
||||||
Bangle.setHRMPower(1);
|
|
||||||
|
turn_on();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function btn3Pressed() {
|
function btn3Pressed() {
|
||||||
if(option === null){
|
if(option === null){
|
||||||
logfile.write(""); //reset HRV log
|
logfile.write(""); //reset HRV log
|
||||||
clearInterval(accel);
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString("continuous mode", 120, 120);
|
g.drawString("continuous mode", px, py);
|
||||||
option = 1;
|
option = 1;
|
||||||
Bangle.setHRMPower(1);
|
|
||||||
}
|
turn_on();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var routine = setInterval(function () {
|
function turn_on() {
|
||||||
clearInterval(accel);
|
|
||||||
first_signals = 0; // ignore the first several signals
|
|
||||||
pulsecount = 0;
|
|
||||||
BPM_array = [];
|
BPM_array = [];
|
||||||
heartrate = [];
|
|
||||||
pulse_array = [];
|
pulse_array = [];
|
||||||
|
samples = 0;
|
||||||
|
if (accel) clearInterval(accel);
|
||||||
|
movement = 0;
|
||||||
|
accel = setInterval(function () {
|
||||||
|
movement = movement + Bangle.getAccel().diff;
|
||||||
|
}, 1000);
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1);
|
||||||
}, 180000);
|
collectData = true;
|
||||||
|
}
|
||||||
|
|
||||||
var accel = setInterval(function () {
|
function drawButtons() {
|
||||||
movement = movement + Bangle.getAccel().diff;
|
g.setColor("#00ff7f");
|
||||||
}, 1000);
|
g.setFont("6x8", 2);
|
||||||
|
g.setFontAlign(-1,1);
|
||||||
|
g.drawString("continuous", 120, 210);
|
||||||
|
g.drawString("one-time", 140, 50);
|
||||||
|
g.setColor("#ffffff");
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.setColor("#00ff7f");
|
|
||||||
g.setFont("6x8", 2);
|
|
||||||
g.setFontAlign(-1,1);
|
|
||||||
g.drawString("continuous", 120, 210);
|
|
||||||
g.setFontAlign(-1,1);
|
|
||||||
g.drawString("one-time", 140, 50);
|
|
||||||
|
|
||||||
|
drawButtons();
|
||||||
|
|
||||||
|
g.setFont("6x8", 2);
|
||||||
g.setColor("#ffffff");
|
g.setColor("#ffffff");
|
||||||
g.setFontAlign(0, 0); // center font
|
g.setFontAlign(0, 0); // center font
|
||||||
g.drawString("check app README", 120, 120);
|
g.drawString("check app README\nfor more info", px, py);
|
||||||
g.drawString("for more info", 120, 140);
|
|
||||||
|
|
||||||
setWatch(btn1Pressed, BTN1, {repeat:true});
|
setWatch(btn1Pressed, BTN1, {repeat:true});
|
||||||
setWatch(btn3Pressed, BTN3, {repeat:true});
|
setWatch(btn3Pressed, BTN3, {repeat:true});
|
||||||
|
|
||||||
Bangle.on('HRM', function (hrm) {
|
|
||||||
if(option == 0)
|
|
||||||
g.flip();
|
Bangle.on('HRM-raw', function (e) {
|
||||||
if (first_signals < 3) {
|
if (!collectData) return;
|
||||||
g.clear();
|
storeMyData(e.raw, 0);
|
||||||
g.drawString("setting up...\nremain still " + first_signals * 20 + "%", 120, 120);
|
if (!(samples & 7)) {
|
||||||
first_signals++;
|
Bangle.setLCDPower(1);
|
||||||
}
|
g.clearRect(0, py-10, g.getWidth(), py+22);
|
||||||
else {
|
if (samples < 100)
|
||||||
BPM_array = hrm.raw;
|
g.drawString("setting up...\nremain still " + samples + "%", px, py, true);
|
||||||
if(hrm.bpm > hr_min && hrm.bpm < hr_max)
|
else
|
||||||
heartrate.push(hrm.bpm);
|
g.drawString("logging: " + (samples*100/raw_HR_array.length).toFixed(0) + "%", px, py, true);
|
||||||
if (pulsecount < 7) {
|
}
|
||||||
for (let i = 0; i < 256; i++) {
|
if (samples > raw_HR_array.length) {
|
||||||
storeMyData(BPM_array[i], 0);
|
collectData = false;
|
||||||
}
|
turn_off();
|
||||||
g.clear();
|
}
|
||||||
g.drawString("logging: " + ((pulsecount/6)*100).toFixed(0) + "%", 120, 120);
|
samples++;
|
||||||
}
|
|
||||||
if(pulsecount == 6)
|
|
||||||
turn_off();
|
|
||||||
pulsecount++;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
{ "id": "7chname",
|
{ "id": "7chname",
|
||||||
"name": "My app's human readable name",
|
"name": "My app's human readable name",
|
||||||
"shortName":"Short Name",
|
"shortName":"Short Name",
|
||||||
"icon": "app.png",
|
|
||||||
"version":"0.01",
|
"version":"0.01",
|
||||||
"description": "A detailed description of my great app",
|
"description": "A detailed description of my great app",
|
||||||
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"7chname.app.js","url":"app.js"},
|
{"name":"7chname.app.js","url":"app.js"},
|
||||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
{ "id": "7chname",
|
{ "id": "7chname",
|
||||||
"name": "My widget's human readable name",
|
"name": "My widget's human readable name",
|
||||||
"shortName":"Short Name",
|
"shortName":"Short Name",
|
||||||
"icon": "widget.png",
|
|
||||||
"version":"0.01",
|
"version":"0.01",
|
||||||
"description": "A detailed description of my great widget",
|
"description": "A detailed description of my great widget",
|
||||||
"tags": "widget",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
"tags": "widget",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"7chname.wid.js","url":"widget.js"}
|
{"name":"7chname.wid.js","url":"widget.js"}
|
||||||
|
|
|
@ -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 |
|
@ -5,3 +5,8 @@
|
||||||
0.05: Actual pixels as of 27 Apr 2020
|
0.05: Actual pixels as of 27 Apr 2020
|
||||||
0.06: Actual pixels as of 12 Jun 2020
|
0.06: Actual pixels as of 12 Jun 2020
|
||||||
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.09: Actual Bangle.js 1 pixels as of 13 Oct 2021
|
||||||
|
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||||
|
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||||
|
0.12: Actual pixels as of 29th Nov 2021
|
||||||
|
|
After Width: | Height: | Size: 15 KiB |
|
@ -1 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Use the new multiplatform 'Layout' library
|
||||||
|
0.03: Exit as first menu option, dont show decimal places for seconds
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("oFAwhC/AH4A2xAedhGIwA+dAAI+dAAJAcEAJ/fD/4f5hAJIYSo6JIihaKxBATxAACC4YnBAAQ+TD7o/CFRJfTP5a/eb6pEOD/7JFwczAA0ybgwfUkUjD4QaDD6syiMSD4ZABCIa8JD547BCIY/ZHQQ+LD6Z/cHZYf/f6E4D6uCD4gACD6wgBkQACBAYfWABYf/D/4fPwf/ABgf/D6czABBf/P/5f/P/5f/D+WIABwfuAH4A/ADo="))
|
require("heatshrink").decompress(atob("mEwwgsphAXWxGACyoABDCwWVC/4XcXY8IDZsIxAXVXYQZDDwIGEC6gwMI5IvNGAJfVR5DXyPgczAAwSEC58iC4R3DC50xiMjC4QTBXY4XNGAIPCF6QTCI6gXCYYwXQcQ4XemUiC6IRBa4wXOABQX/C9OD/4AKC5WPC+Hzaw0zI/5H/C83zI/mIUo4ACnAXLABgXI"))
|
||||||
|
|
|
@ -8,6 +8,9 @@ function getFileName(n) {
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
var menu = {
|
var menu = {
|
||||||
"" : { title : "Accel Logger" },
|
"" : { title : "Accel Logger" },
|
||||||
|
"Exit" : function() {
|
||||||
|
load();
|
||||||
|
},
|
||||||
"File No" : {
|
"File No" : {
|
||||||
value : fileNumber,
|
value : fileNumber,
|
||||||
min : 0,
|
min : 0,
|
||||||
|
@ -21,9 +24,6 @@ function showMenu() {
|
||||||
"View Logs" : function() {
|
"View Logs" : function() {
|
||||||
viewLogs();
|
viewLogs();
|
||||||
},
|
},
|
||||||
"Exit" : function() {
|
|
||||||
load();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ function viewLog(n) {
|
||||||
var records = 0, l = "", ll="";
|
var records = 0, l = "", ll="";
|
||||||
while ((l=f.readLine())!==undefined) {records++;ll=l;}
|
while ((l=f.readLine())!==undefined) {records++;ll=l;}
|
||||||
var length = 0;
|
var length = 0;
|
||||||
if (ll) length = (ll.split(",")[0]|0)/1000;
|
if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 );
|
||||||
|
|
||||||
var menu = {
|
var menu = {
|
||||||
"" : { title : "Log "+n }
|
"" : { title : "Log "+n }
|
||||||
|
@ -90,16 +90,25 @@ function startRecord(force) {
|
||||||
// display
|
// display
|
||||||
g.clear(1);
|
g.clear(1);
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
var w = g.getWidth();
|
|
||||||
var h = g.getHeight();
|
var Layout = require("Layout");
|
||||||
g.setColor("#ff0000").fillRect(0,h-48,w,h);
|
var layout = new Layout({ type: "v", c: [
|
||||||
g.setColor("#ffffff").setFont("6x8",2).setFontAlign(0,0).drawString("RECORDING", w/2,h-24);
|
{type:"txt", font:"6x8", label:"Samples", pad:2},
|
||||||
g.setFont("6x8").drawString("Samples:",w/2,h/3 - 20);
|
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||||
g.setFont("6x8").drawString("Time:",w/2,h*2/3 - 20);
|
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||||
g.setFont("6x8",2).setFontAlign(0,0,1).drawString("STOP",w-10,h/2);
|
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||||
|
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||||
|
]
|
||||||
|
},{btns:[ // Buttons...
|
||||||
|
{label:"STOP", cb:()=>{
|
||||||
|
Bangle.removeListener('accel', accelHandler);
|
||||||
|
showMenu();
|
||||||
|
}}
|
||||||
|
]});
|
||||||
|
layout.render();
|
||||||
|
|
||||||
// now start writing
|
// now start writing
|
||||||
f = require("Storage").open(getFileName(fileNumber), "w");
|
var f = require("Storage").open(getFileName(fileNumber), "w");
|
||||||
f.write("Time (ms),X,Y,Z\n");
|
f.write("Time (ms),X,Y,Z\n");
|
||||||
var start = getTime();
|
var start = getTime();
|
||||||
var sampleCount = 0;
|
var sampleCount = 0;
|
||||||
|
@ -113,17 +122,14 @@ function startRecord(force) {
|
||||||
accel.z*8192].map(n=>Math.round(n)).join(",")+"\n");
|
accel.z*8192].map(n=>Math.round(n)).join(",")+"\n");
|
||||||
|
|
||||||
sampleCount++;
|
sampleCount++;
|
||||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
layout.samples.label = sampleCount;
|
||||||
g.drawString(" "+sampleCount+" ",w/2,h/3,true);
|
layout.time.label = Math.round(t)+"s";
|
||||||
g.drawString(" "+Math.round(t)+"s ",w/2,h*2/3,true);
|
layout.render(layout.samples);
|
||||||
|
layout.render(layout.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.setPollInterval(80); // 12.5 Hz
|
Bangle.setPollInterval(80); // 12.5 Hz - the default
|
||||||
Bangle.on('accel', accelHandler);
|
Bangle.on('accel', accelHandler);
|
||||||
setWatch(()=>{
|
|
||||||
Bangle.removeListener('accel', accelHandler);
|
|
||||||
showMenu();
|
|
||||||
}, BTN2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -46,7 +46,6 @@ function getData() {
|
||||||
});
|
});
|
||||||
return new Promise(resolve=>{
|
return new Promise(resolve=>{
|
||||||
Puck.eval(`require("Storage").read(${JSON.stringify(fn)})`,csv=>{
|
Puck.eval(`require("Storage").read(${JSON.stringify(fn)})`,csv=>{
|
||||||
fileData[fn] = csv.trim();
|
|
||||||
var el = document.querySelector(`.card-body[fn='${fn}']`);
|
var el = document.querySelector(`.card-body[fn='${fn}']`);
|
||||||
el.innerHTML = '<canvas width="400" height="100"></canvas>';
|
el.innerHTML = '<canvas width="400" height="100"></canvas>';
|
||||||
var c = el.firstChild;
|
var c = el.firstChild;
|
||||||
|
|
|
@ -9,3 +9,4 @@
|
||||||
0.12: Fix regression after 0.11
|
0.12: Fix regression after 0.11
|
||||||
0.13: Fix broken date padding (fix #376)
|
0.13: Fix broken date padding (fix #376)
|
||||||
0.14: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
0.14: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
||||||
|
0.15: Add color scheme support
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Analogue Clock
|
||||||
|
|
||||||
|

|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
const locale = require('locale');
|
const locale = require('locale');
|
||||||
const p = Math.PI / 2;
|
const p = Math.PI / 2;
|
||||||
const pRad = Math.PI / 180;
|
const pRad = Math.PI / 180;
|
||||||
const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
|
const faceWidth = g.getWidth()/2 - 20; // watch face radius (240/2 - 24px for widget area)
|
||||||
const widgetHeight=24+1;
|
const widgetHeight=24+1;
|
||||||
let timer = null;
|
let timer = null;
|
||||||
let currentDate = new Date();
|
let currentDate = new Date();
|
||||||
const centerX = g.getWidth() / 2;
|
const centerX = g.getWidth() / 2;
|
||||||
const centerY = (g.getWidth() / 2) + widgetHeight/2;
|
const centerY = (g.getWidth() / 2) + widgetHeight/2;
|
||||||
|
g.theme.dark=false;
|
||||||
|
let colSecA = g.theme.dark ? "#00A" : "#58F"; // before the second
|
||||||
|
let colSecB = g.theme.dark ? "#58F" : "#00A"; // after the second
|
||||||
|
let colSec1 = g.theme.dark ? "#F83" : "#000"; // ON the second
|
||||||
|
|
||||||
const seconds = (angle) => {
|
const seconds = (angle) => {
|
||||||
const a = angle * pRad;
|
const a = angle * pRad;
|
||||||
|
@ -46,40 +49,35 @@ const drawAll = () => {
|
||||||
// draw all secs
|
// draw all secs
|
||||||
|
|
||||||
for (let i = 0; i < 60; i++) {
|
for (let i = 0; i < 60; i++) {
|
||||||
if (i > currentSec) {
|
g.setColor((i > currentSec) ? colSecA : colSecB);
|
||||||
g.setColor(0, 0, 0.6);
|
|
||||||
} else {
|
|
||||||
g.setColor(0.3, 0.3, 1);
|
|
||||||
}
|
|
||||||
seconds((360 * i) / 60);
|
seconds((360 * i) / 60);
|
||||||
}
|
}
|
||||||
onSecond();
|
onSecond();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetSeconds = () => {
|
const resetSeconds = () => {
|
||||||
g.setColor(0, 0, 0.6);
|
g.setColor(colSecA);
|
||||||
for (let i = 0; i < 60; i++) {
|
for (let i = 0; i < 60; i++) {
|
||||||
seconds((360 * i) / 60);
|
seconds((360 * i) / 60);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSecond = () => {
|
const onSecond = () => {
|
||||||
g.setColor(0.3, 0.3, 1);
|
g.setColor(colSecB);
|
||||||
seconds((360 * currentDate.getSeconds()) / 60);
|
seconds((360 * currentDate.getSeconds()) / 60);
|
||||||
if (currentDate.getSeconds() === 59) {
|
if (currentDate.getSeconds() === 59) {
|
||||||
resetSeconds();
|
resetSeconds();
|
||||||
onMinute();
|
onMinute();
|
||||||
}
|
}
|
||||||
g.setColor(1, 0.7, 0.2);
|
g.setColor(colSec1);
|
||||||
currentDate = new Date();
|
currentDate = new Date();
|
||||||
seconds((360 * currentDate.getSeconds()) / 60);
|
seconds((360 * currentDate.getSeconds()) / 60);
|
||||||
g.setColor(1, 1, 1);
|
g.setColor(g.theme.fg);
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawDate = () => {
|
const drawDate = () => {
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(1, 0, 0);
|
g.setColor("#f00");
|
||||||
g.setFont('6x8', 2);
|
g.setFont('6x8', 2);
|
||||||
|
|
||||||
const dayString = locale.dow(currentDate, true);
|
const dayString = locale.dow(currentDate, true);
|
||||||
|
@ -89,7 +87,7 @@ const drawDate = () => {
|
||||||
// console.log(`${dayString}|${dateString}`);
|
// console.log(`${dayString}|${dateString}`);
|
||||||
// center date
|
// center date
|
||||||
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
||||||
const t = centerY + 37;
|
const t = centerY + faceWidth*0.37;
|
||||||
g.drawString(dateDisplay, l, t, true);
|
g.drawString(dateDisplay, l, t, true);
|
||||||
// console.log(l, t);
|
// console.log(l, t);
|
||||||
};
|
};
|
||||||
|
@ -99,7 +97,7 @@ const onMinute = () => {
|
||||||
resetSeconds();
|
resetSeconds();
|
||||||
}
|
}
|
||||||
// clear existing hands
|
// clear existing hands
|
||||||
g.setColor(0, 0, 0);
|
g.setColor(g.theme.bg);
|
||||||
// Hour
|
// Hour
|
||||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||||
// Minute
|
// Minute
|
||||||
|
@ -107,10 +105,10 @@ const onMinute = () => {
|
||||||
|
|
||||||
// get new date, then draw new hands
|
// get new date, then draw new hands
|
||||||
currentDate = new Date();
|
currentDate = new Date();
|
||||||
g.setColor(1, 0.9, 0.9);
|
g.setColor(g.theme.fg);
|
||||||
// Hour
|
// Hour
|
||||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||||
g.setColor(1, 1, 0.9);
|
g.setColor(g.theme.fg);
|
||||||
// Minute
|
// Minute
|
||||||
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
||||||
drawDate();
|
drawDate();
|
||||||
|
@ -137,8 +135,9 @@ g.clear();
|
||||||
resetSeconds();
|
resetSeconds();
|
||||||
startTimers();
|
startTimers();
|
||||||
drawAll();
|
drawAll();
|
||||||
|
|
||||||
|
// Show launcher when button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
|
||||||
|
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -9,3 +9,7 @@
|
||||||
0.09: Add per alarm auto-snooze option
|
0.09: Add per alarm auto-snooze option
|
||||||
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
||||||
0.11: Respect Quiet Mode
|
0.11: Respect Quiet Mode
|
||||||
|
0.12: Fix widget for bangle 2, now uses theme
|
||||||
|
Widgets now shown on Alarm screen
|
||||||
|
0.13: Alarm widget state now updates when setting/resetting an alarm
|
||||||
|
0.14: Order of 'back' menu item
|
||||||
|
|
|
@ -18,9 +18,11 @@ function showAlarm(alarm) {
|
||||||
var buzzCount = 10;
|
var buzzCount = 10;
|
||||||
if (alarm.msg)
|
if (alarm.msg)
|
||||||
msg += "\n"+alarm.msg;
|
msg += "\n"+alarm.msg;
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
E.showPrompt(msg,{
|
E.showPrompt(msg,{
|
||||||
title:"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) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
||||||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||||
rp : true, // repeat
|
rp : true, // repeat
|
||||||
as : false, // auto snooze
|
as : false, // auto snooze
|
||||||
|
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
|
||||||
}
|
}
|
||||||
];*/
|
];*/
|
||||||
|
|
||||||
|
@ -18,6 +19,12 @@ function formatTime(t) {
|
||||||
return hrs+":"+("0"+mins).substr(-2);
|
return hrs+":"+("0"+mins).substr(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMins(t) {
|
||||||
|
mins = (0|t)%60;
|
||||||
|
hrs = 0|(t/60);
|
||||||
|
return hrs+":"+("0"+mins).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentHr() {
|
function getCurrentHr() {
|
||||||
var time = new Date();
|
var time = new Date();
|
||||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||||
|
@ -25,17 +32,25 @@ function getCurrentHr() {
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': 'Alarms' },
|
'': { 'title': 'Alarm/Timer' },
|
||||||
'New Alarm': ()=>editAlarm(-1)
|
/*LANG*/'< Back' : ()=>{load();},
|
||||||
|
/*LANG*/'New Alarm': ()=>editAlarm(-1),
|
||||||
|
/*LANG*/'New Timer': ()=>editTimer(-1)
|
||||||
};
|
};
|
||||||
alarms.forEach((alarm,idx)=>{
|
alarms.forEach((alarm,idx)=>{
|
||||||
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
if (alarm.timer) {
|
||||||
if (alarm.rp) txt += " (repeat)";
|
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
|
||||||
|
} else {
|
||||||
|
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
|
||||||
|
if (alarm.rp) txt += /*LANG*/" (repeat)";
|
||||||
|
}
|
||||||
menu[txt] = function() {
|
menu[txt] = function() {
|
||||||
editAlarm(idx);
|
if (alarm.timer) editTimer(idx);
|
||||||
|
else editAlarm(idx);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
menu['< Back'] = ()=>{load();};
|
|
||||||
|
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||||
return E.showMenu(menu);
|
return E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,26 +70,27 @@ function editAlarm(alarmIndex) {
|
||||||
as = a.as;
|
as = a.as;
|
||||||
}
|
}
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': 'Alarms' },
|
'': { '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
|
||||||
|
@ -92,12 +108,66 @@ 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) {
|
||||||
|
menu[/*LANG*/"> Delete"] = function() {
|
||||||
|
alarms.splice(alarmIndex,1);
|
||||||
|
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||||
|
showMainMenu();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editTimer(alarmIndex) {
|
||||||
|
var newAlarm = alarmIndex<0;
|
||||||
|
var hrs = 0;
|
||||||
|
var mins = 5;
|
||||||
|
var en = true;
|
||||||
|
if (!newAlarm) {
|
||||||
|
var a = alarms[alarmIndex];
|
||||||
|
mins = (0|a.timer)%60;
|
||||||
|
hrs = 0|(a.timer/60);
|
||||||
|
en = a.on;
|
||||||
|
}
|
||||||
|
const menu = {
|
||||||
|
'': { 'title': /*LANG*/'Timer' },
|
||||||
|
/*LANG*/'Hours': {
|
||||||
|
value: hrs,
|
||||||
|
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||||
|
},
|
||||||
|
/*LANG*/'Minutes': {
|
||||||
|
value: mins,
|
||||||
|
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||||
|
},
|
||||||
|
/*LANG*/'Enabled': {
|
||||||
|
value: en,
|
||||||
|
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
|
||||||
|
onchange: v=>en=v
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function getTimer() {
|
||||||
|
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
|
||||||
|
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
|
||||||
|
// Save alarm
|
||||||
|
return {
|
||||||
|
on : en,
|
||||||
|
timer : (hrs*60)+mins,
|
||||||
|
hr : hr,
|
||||||
|
rp : false, as: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
menu["> Save"] = function() {
|
||||||
|
if (newAlarm) alarms.push(getTimer());
|
||||||
|
else alarms[alarmIndex] = getTimer();
|
||||||
|
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||||
|
showMainMenu();
|
||||||
|
};
|
||||||
if (!newAlarm) {
|
if (!newAlarm) {
|
||||||
menu["> Delete"] = function() {
|
menu["> Delete"] = function() {
|
||||||
alarms.splice(alarmIndex,1);
|
alarms.splice(alarmIndex,1);
|
||||||
|
@ -105,7 +175,6 @@ function editAlarm(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);
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
(() => {
|
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||||
alarms = alarms.filter(alarm=>alarm.on);
|
},reload:function() {
|
||||||
if (!alarms.length) return; // no alarms, no widget!
|
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
|
||||||
delete alarms;
|
}
|
||||||
// add the widget
|
};
|
||||||
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
|
WIDGETS["alarm"].reload();
|
||||||
g.setColor(-1);
|
|
||||||
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
|
||||||
}};
|
|
||||||
})()
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Add BTN2 -> launcher
|
0.02: Add BTN2 -> launcher
|
||||||
|
0.03: Update to use setUI
|
||||||
|
|
|
@ -114,5 +114,5 @@ if (g.drawImages) {
|
||||||
E.showMessage("Please update\nBangle.js firmware\nto use this clock","analogimgclk");
|
E.showMessage("Please update\nBangle.js firmware\nto use this clock","analogimgclk");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when button pressed
|
||||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
Bangle.setUI("clock");
|
||||||
|
|
|
@ -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: Fix bug if image clock wasn't installed
|
0.02: Fix bug if image clock wasn't installed
|
||||||
|
0.03: Update to use setUI
|
||||||
|
|
|
@ -102,5 +102,5 @@ if (g.drawImages) {
|
||||||
} else {
|
} else {
|
||||||
E.showMessage("Please update\nBangle.js firmware\nto use this clock","animclk");
|
E.showMessage("Please update\nBangle.js firmware\nto use this clock","animclk");
|
||||||
}
|
}
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when button pressed
|
||||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
Bangle.setUI("clock");
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Load widgets after setUI so widclk knows when to hide
|
||||||
|
0.03: Clock now shows day of week under date.
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA=="))
|
After Width: | Height: | Size: 759 B |
After Width: | Height: | Size: 696 B |
|
@ -1,4 +1,6 @@
|
||||||
0.01: First version
|
0.01: First version
|
||||||
0.02: Moved arrow image load to global scope
|
0.02: Moved arrow image load to global scope
|
||||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||||
0.04: removed LCD1.write() as it was keeping LCD on
|
0.04: removed LED1.write() as it was keeping LCD on
|
||||||
|
0.05: Turn compass off when screen off
|
||||||
|
Calibrate at start if no info
|
||||||
|
|
|
@ -36,6 +36,10 @@ charge.
|
||||||
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
||||||
|
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
* detect when calibration data is missing
|
||||||
|
|
||||||
## Acknowledgement
|
## Acknowledgement
|
||||||
|
|
||||||
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
|
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
|
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
|
||||||
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
|
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
|
||||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||||
var intervalRef;
|
var intervalRef;
|
||||||
|
@ -7,6 +7,7 @@ var bearing=0; // always point north
|
||||||
var heading = 0;
|
var heading = 0;
|
||||||
var oldHeading = 0;
|
var oldHeading = 0;
|
||||||
var candraw = false;
|
var candraw = false;
|
||||||
|
var isCalibrating = false;
|
||||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||||
|
|
||||||
function flip1(x,y) {
|
function flip1(x,y) {
|
||||||
|
@ -29,7 +30,7 @@ function drawCompass(hd) {
|
||||||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||||
hd=hd*Math.PI/180;
|
hd=hd*Math.PI/180;
|
||||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||||
|
|
||||||
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
||||||
var poly = [
|
var poly = [
|
||||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||||
|
@ -40,16 +41,16 @@ function drawCompass(hd) {
|
||||||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||||
];
|
];
|
||||||
|
|
||||||
buf1.fillPoly(poly);
|
buf1.fillPoly(poly);
|
||||||
flip1(56, 56);
|
flip1(56, 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
// stops violent compass swings and wobbles, takes 3ms
|
// stops violent compass swings and wobbles, takes 3ms
|
||||||
function newHeading(m,h){
|
function newHeading(m,h){
|
||||||
var s = Math.abs(m - h);
|
var s = Math.abs(m - h);
|
||||||
var delta = (m>h)?1:-1;
|
var delta = (m>h)?1:-1;
|
||||||
if (s>=180){s=360-s; delta = -delta;}
|
if (s>=180){s=360-s; delta = -delta;}
|
||||||
if (s<2) return h;
|
if (s<2) return h;
|
||||||
var hd = h + delta*(1 + Math.round(s/5));
|
var hd = h + delta*(1 + Math.round(s/5));
|
||||||
if (hd<0) hd+=360;
|
if (hd<0) hd+=360;
|
||||||
|
@ -76,7 +77,7 @@ function tiltfixread(O,S){
|
||||||
return psi;
|
return psi;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reading() {
|
function reading(m) {
|
||||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||||
heading = newHeading(d,heading);
|
heading = newHeading(d,heading);
|
||||||
var dir = bearing - heading;
|
var dir = bearing - heading;
|
||||||
|
@ -97,18 +98,19 @@ function reading() {
|
||||||
function calibrate(){
|
function calibrate(){
|
||||||
var max={x:-32000, y:-32000, z:-32000},
|
var max={x:-32000, y:-32000, z:-32000},
|
||||||
min={x:32000, y:32000, z:32000};
|
min={x:32000, y:32000, z:32000};
|
||||||
var ref = setInterval(()=>{
|
function onMag(m) {
|
||||||
var m = Bangle.getCompass();
|
|
||||||
max.x = m.x>max.x?m.x:max.x;
|
max.x = m.x>max.x?m.x:max.x;
|
||||||
max.y = m.y>max.y?m.y:max.y;
|
max.y = m.y>max.y?m.y:max.y;
|
||||||
max.z = m.z>max.z?m.z:max.z;
|
max.z = m.z>max.z?m.z:max.z;
|
||||||
min.x = m.x<min.x?m.x:min.x;
|
min.x = m.x<min.x?m.x:min.x;
|
||||||
min.y = m.y<min.y?m.y:min.y;
|
min.y = m.y<min.y?m.y:min.y;
|
||||||
min.z = m.z<min.z?m.z:min.z;
|
min.z = m.z<min.z?m.z:min.z;
|
||||||
}, 100);
|
}
|
||||||
|
Bangle.on('mag', onMag);
|
||||||
|
Bangle.setCompassPower(1, "app");
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
if(ref) clearInterval(ref);
|
Bangle.removeListener('mag', onMag);
|
||||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||||
var avg = (delta.x+delta.y+delta.z)/3;
|
var avg = (delta.x+delta.y+delta.z)/3;
|
||||||
|
@ -132,6 +134,7 @@ function docalibrate(e,first){
|
||||||
flip1(56,56);
|
flip1(56,56);
|
||||||
|
|
||||||
calibrate().then((r)=>{
|
calibrate().then((r)=>{
|
||||||
|
isCalibrating = false;
|
||||||
require("Storage").write("magnav.json",r);
|
require("Storage").write("magnav.json",r);
|
||||||
Bangle.buzz();
|
Bangle.buzz();
|
||||||
CALIBDATA = r;
|
CALIBDATA = r;
|
||||||
|
@ -142,27 +145,39 @@ function docalibrate(e,first){
|
||||||
startdraw();
|
startdraw();
|
||||||
setTimeout(setButtons,1000);
|
setTimeout(setButtons,1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first===undefined) first=false;
|
|
||||||
|
if (first === undefined) first = false;
|
||||||
|
|
||||||
stopdraw();
|
stopdraw();
|
||||||
clearWatch();
|
clearWatch();
|
||||||
if (first)
|
isCalibrating = true;
|
||||||
|
|
||||||
|
if (first)
|
||||||
E.showAlert(msg,title).then(action.bind(null,true));
|
E.showAlert(msg,title).then(action.bind(null,true));
|
||||||
else
|
else
|
||||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startdraw(){
|
function startdraw(){
|
||||||
|
Bangle.setCompassPower(1, "app");
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
candraw = true;
|
candraw = true;
|
||||||
intervalRef = setInterval(reading,500);
|
if (intervalRef) clearInterval(intervalRef);
|
||||||
|
intervalRef = setInterval(reading,200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopdraw() {
|
function stopdraw() {
|
||||||
candraw=false;
|
candraw=false;
|
||||||
if(intervalRef) {clearInterval(intervalRef);}
|
|
||||||
|
Bangle.setCompassPower(0, "app");
|
||||||
|
if (intervalRef) {
|
||||||
|
clearInterval(intervalRef);
|
||||||
|
intervalRef = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setButtons(){
|
function setButtons(){
|
||||||
|
@ -170,8 +185,9 @@ function setButtons(){
|
||||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('lcdPower',function(on) {
|
Bangle.on('lcdPower',function(on) {
|
||||||
|
if (isCalibrating) return;
|
||||||
if (on) {
|
if (on) {
|
||||||
startdraw();
|
startdraw();
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.setCompassPower(1);
|
|
||||||
startdraw();
|
|
||||||
setButtons();
|
setButtons();
|
||||||
|
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
if (CALIBDATA) startdraw(); else docalibrate({},true);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: Create astral clock app
|
0.01: Create astral clock app
|
||||||
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
||||||
|
0.03: Update to use Bangle.setUI instead of setWatch
|
||||||
|
|
|
@ -503,8 +503,8 @@ function coord_to_horizon(utc, ra, dec, lat, lon, h) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// "mean_sidereal_time" returns the Mean Sidereal Time in units of degrees.
|
// "mean_sidereal_time" returns the Mean Sidereal Time in units of degrees.
|
||||||
// Use lon = 0 to get the Greenwich MST.
|
// Use lon = 0 to get the Greenwich MST.
|
||||||
// East longitudes are positive; West longitudes are negative
|
// East longitudes are positive; West longitudes are negative
|
||||||
//
|
//
|
||||||
// returns: time in degrees
|
// returns: time in degrees
|
||||||
|
@ -523,7 +523,7 @@ function mean_sidereal_time(lon) {
|
||||||
var c = Math.floor(365.25 * year);
|
var c = Math.floor(365.25 * year);
|
||||||
var da = Math.floor(30.6001 * (month + 1));
|
var da = Math.floor(30.6001 * (month + 1));
|
||||||
|
|
||||||
// days since J2000.0
|
// days since J2000.0
|
||||||
var jd = b + c + da - 730550.5 + day
|
var jd = b + c + da - 730550.5 + day
|
||||||
+ (hour + mins / 60.0 + secs / 3600.0) / 24.0;
|
+ (hour + mins / 60.0 + secs / 3600.0) / 24.0;
|
||||||
|
|
||||||
|
@ -796,10 +796,11 @@ Bangle.on('lcdPower', on => {
|
||||||
Bangle.setCompassPower(1);
|
Bangle.setCompassPower(1);
|
||||||
Bangle.setGPSPower(1);
|
Bangle.setGPSPower(1);
|
||||||
|
|
||||||
// Buttons
|
// Show launcher when button pressed
|
||||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
Bangle.setClockMode();
|
||||||
|
|
||||||
setWatch(function () {
|
Bangle.setUI("clockupdown", btn => {
|
||||||
|
if (btn==0) {
|
||||||
if (!processing) {
|
if (!processing) {
|
||||||
if (!modeswitch) {
|
if (!modeswitch) {
|
||||||
modeswitch = true;
|
modeswitch = true;
|
||||||
|
@ -809,12 +810,11 @@ setWatch(function () {
|
||||||
else
|
else
|
||||||
modeswitch = false;
|
modeswitch = false;
|
||||||
}
|
}
|
||||||
}, BTN3, { repeat: true });
|
} else {
|
||||||
|
|
||||||
setWatch(function () {
|
|
||||||
if (!processing)
|
if (!processing)
|
||||||
ready_to_compute = true;
|
ready_to_compute = true;
|
||||||
}, BTN1, { repeat: true });
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setWatch(function () {
|
setWatch(function () {
|
||||||
if (!astral_settings.astral_default) {
|
if (!astral_settings.astral_default) {
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
|
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
|
||||||
|
0.03: Bangle 2 support
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
Bangle.setLCDMode("doublebuffered");
|
var BTNL, BTNR, BTNU, BTNA;
|
||||||
|
if (process.env.HWVERSION==2) {
|
||||||
|
var tap = {};
|
||||||
|
// use tapping on screen for left,right,accel
|
||||||
|
Bangle.on('drag',e=>tap=e);
|
||||||
|
BTNL = { read : _=>tap.b && tap.x < 58};
|
||||||
|
BTNR = { read : _=>tap.b && tap.x > 117};
|
||||||
|
BTNU = { read : _=>tap.b && tap.x > 58 && tap.x < 117};
|
||||||
|
// use button for fire
|
||||||
|
BTNA = BTN1;
|
||||||
|
} else {
|
||||||
|
// use hard buttons
|
||||||
|
BTNL = BTN4;
|
||||||
|
BTNR = BTN5;
|
||||||
|
BTNU = BTN1;
|
||||||
|
BTNA = BTN2;
|
||||||
|
Bangle.setLCDMode("doublebuffered");
|
||||||
|
}
|
||||||
var W = g.getWidth();
|
var W = g.getWidth();
|
||||||
var H = g.getHeight();
|
var H = g.getHeight();
|
||||||
g.setFontAlign(0,-1);
|
g.clear().setFontAlign(0,-1);
|
||||||
var BTNL = BTN4;
|
|
||||||
var BTNR = BTN5;
|
|
||||||
var BTNU = BTN1;
|
|
||||||
var BTNA = BTN2;
|
|
||||||
|
|
||||||
function newAst(x,y) {
|
function newAst(x,y) {
|
||||||
var a = {
|
var a = {
|
||||||
|
@ -92,8 +104,7 @@ function onFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
|
g.drawString(score,W-20,0);
|
||||||
g.drawString(score,120,0);
|
|
||||||
var rs = Math.PI*0.8;
|
var rs = Math.PI*0.8;
|
||||||
g.drawPoly([
|
g.drawPoly([
|
||||||
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,
|
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,
|
||||||
|
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
0.01: First release
|
||||||
|
0.02: Fix JSON save format
|
||||||
|
0.03: Add "Calculating" placeholder, update JSON save format
|
||||||
|
0.04: Fix tapping at very bottom of list, exit on inactivity
|
|
@ -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 |
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
|
@ -1,4 +1,5 @@
|
||||||
(() => {
|
(() => {
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
let intervalID;
|
let intervalID;
|
||||||
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: Initial version of Balltastic released! Happy!
|
0.01: Initial version of Balltastic released! Happy!
|
||||||
|
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
|
@ -1,5 +1,6 @@
|
||||||
Bangle.setLCDBrightness(1);
|
Bangle.setLCDBrightness(1);
|
||||||
Bangle.setLCDMode("doublebuffered");
|
Bangle.setLCDMode("doublebuffered");
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
|
|
||||||
let points = 0;
|
let points = 0;
|
||||||
let level = 1;
|
let level = 1;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App
|
||||||
|
Part of smartPPE project https://jorgepramos.github.io/Smart_PPE/index.html
|
||||||
|
|
||||||
|
# BangleBridge
|
||||||
|
|
||||||
|
Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App.
|
||||||
|
|
||||||
|
## Full Project
|
||||||
|
|
||||||
|
Part of smartPPE project [SmartPEE](https://jorgepramos.github.io/Smart_PPE/index.html).
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
00堽<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C>??<0C><><EFBFBD><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><0C><><0C><><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<0C><><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<0C><><EFBFBD><0C><>''''<27><><EFBFBD><0C><>''''<27><><EFBFBD><0C><>'''''<27><><EFBFBD><EFBFBD>'<27><>''''''<27><><EFBFBD><EFBFBD>'??'''''''<27><><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
@ -0,0 +1,302 @@
|
||||||
|
(() => {
|
||||||
|
/**
|
||||||
|
* Widget measurements
|
||||||
|
* Description:
|
||||||
|
* name: connection.wid.js
|
||||||
|
*icon: conectionIcon.icon
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Font
|
||||||
|
|
||||||
|
g.setFont("Vector", 100);
|
||||||
|
//variabangle.Sensorss
|
||||||
|
let acclS, bttS, compssS, gpsS, hrmS, stepS; //Strings
|
||||||
|
let accelN, compssN, gpsN, hrmN, stepN; //Num
|
||||||
|
let prueba = 1;
|
||||||
|
let data = [0, 0, 0, 0, 0, 0];
|
||||||
|
//Constants for redabangle.Sensors code
|
||||||
|
let storage = require('Storage');
|
||||||
|
let deCom = require('heatshrink');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Sensors code
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Jorge
|
||||||
|
*/
|
||||||
|
function accel() {
|
||||||
|
|
||||||
|
Bangle.on('accel', function (acc) {
|
||||||
|
// acc = {x,y,z,diff,mag}
|
||||||
|
accelN = acc;
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||||
|
data[3] = accelN;
|
||||||
|
}, 2 * 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function btt() {
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
bttS = E.getBattery(); //return String
|
||||||
|
data[2] = E.getBattery();
|
||||||
|
}, 15 * 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function compss() {
|
||||||
|
|
||||||
|
Bangle.setCompassPower(1);
|
||||||
|
Bangle.on('mag', function (mag) {
|
||||||
|
// mag = {x,y,z,dx,dy,dz,heading}
|
||||||
|
compssN = mag;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||||
|
"B: " + compssN.dx + " ## " + compssN.dy + " ## " + compssN.dz + " ## " + "\n" +
|
||||||
|
"C: " + compssN.heading; //return String
|
||||||
|
data[4] = compssN;
|
||||||
|
}, 2 * 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function gps() {
|
||||||
|
|
||||||
|
Bangle.setGPSPower(1);
|
||||||
|
Bangle.on('GPS', function (gps) {
|
||||||
|
// gps = {lat,lon,alt,speed,etc}
|
||||||
|
gpsN = gps;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||||
|
"C: " + gpsN.satellites + " ## " + gpsN.fix; //return String
|
||||||
|
// work out how to display the current time
|
||||||
|
var d = new Date();
|
||||||
|
var year = d.getFullYear();
|
||||||
|
|
||||||
|
var month = d.getMonth() + 1;
|
||||||
|
var finalMonth = 0;
|
||||||
|
if (month < 10) {
|
||||||
|
finalMonth = "0" + month;
|
||||||
|
} else {
|
||||||
|
finalMonth = month;
|
||||||
|
}
|
||||||
|
var day = d.getDate();
|
||||||
|
var finalDay = 0;
|
||||||
|
if (day < 10) {
|
||||||
|
finalDay = "0" + day;
|
||||||
|
} else {
|
||||||
|
finalDay = day;
|
||||||
|
}
|
||||||
|
var h = d.getHours(),
|
||||||
|
m = d.getMinutes();
|
||||||
|
var finalh = 0;
|
||||||
|
if (h < 10) {
|
||||||
|
finalh = "0" + h;
|
||||||
|
} else {
|
||||||
|
finalh = h;
|
||||||
|
}
|
||||||
|
var finalM = 0;
|
||||||
|
if (m < 10) {
|
||||||
|
finalM = "0" + m;
|
||||||
|
} else {
|
||||||
|
finalM = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = d.getSeconds();
|
||||||
|
var finalS = 0;
|
||||||
|
if (s < 10) {
|
||||||
|
finalS = "0" + s;
|
||||||
|
} else {
|
||||||
|
finalS = s;
|
||||||
|
}
|
||||||
|
var z = d.getMilliseconds();
|
||||||
|
var zFinal = new String(z);
|
||||||
|
zFinal = zFinal.replace('.', '');
|
||||||
|
var completeTime = year + "-" + finalMonth + "-" + finalDay + "T" + finalh + ":" + finalM + ":" + finalS + "." + z + "Z";
|
||||||
|
var time = h + ":" + ("0" + m).substr(-2);
|
||||||
|
gpsN.time = completeTime;
|
||||||
|
data[5] = gpsN;
|
||||||
|
}, 2 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
//2021-06-11T19:21:58.000Z
|
||||||
|
|
||||||
|
function hrm() {
|
||||||
|
|
||||||
|
let msr = [0, 0, 0, 0, 0];
|
||||||
|
let lastInsert = -1;
|
||||||
|
|
||||||
|
function roundInsert(nueva) {
|
||||||
|
let indexFinal = (lastInsert + 1) % (msr.length);
|
||||||
|
//console.log("Index ==> "+ index);
|
||||||
|
msr[indexFinal] = nueva;
|
||||||
|
|
||||||
|
item = nueva;
|
||||||
|
lastInsert = indexFinal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(nueva) {
|
||||||
|
|
||||||
|
let normalize = 0;
|
||||||
|
roundInsert(nueva);
|
||||||
|
|
||||||
|
|
||||||
|
msr.forEach(function (number) {
|
||||||
|
normalize += number;
|
||||||
|
});
|
||||||
|
normalize = normalize / msr.length;
|
||||||
|
|
||||||
|
return normalize;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
if (!isNaN(hrmN)) {
|
||||||
|
|
||||||
|
|
||||||
|
hrmN = normalize(hrmN);
|
||||||
|
var roundedRate = parseFloat(hrmN).toFixed(2);
|
||||||
|
hrmS = String.valueOf(roundedRate); //return String
|
||||||
|
//console.log("array----->" + msr);
|
||||||
|
data[0] = roundedRate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}, 2 * 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function steps() {
|
||||||
|
|
||||||
|
Bangle.on('step', s => {
|
||||||
|
|
||||||
|
stepN = s;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
|
||||||
|
stepS = String.valueOf(stepN); //return String
|
||||||
|
data[1] = stepN;
|
||||||
|
}, 2 * 1000);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSensors() {
|
||||||
|
|
||||||
|
//need power control
|
||||||
|
Bangle.setHRMPower(1);
|
||||||
|
|
||||||
|
Bangle.on('HRM', function (hrm) {
|
||||||
|
hrmN = hrm.bpm;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
console.log("Sensors are being Init....");
|
||||||
|
accel();
|
||||||
|
btt();
|
||||||
|
compss();
|
||||||
|
gps();
|
||||||
|
hrm();
|
||||||
|
steps();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var flip = 1;
|
||||||
|
Bangle.on('lcdPower', function (on) {
|
||||||
|
/*
|
||||||
|
prueba ++;
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
g.setFont("Vector", 45);
|
||||||
|
g.drawString(prueba,100,200);*/
|
||||||
|
if (flip == 1) { //when off
|
||||||
|
|
||||||
|
flip = 0;
|
||||||
|
//Bangle.buzz(1000);
|
||||||
|
g.clear();
|
||||||
|
} else { //when on
|
||||||
|
|
||||||
|
flip = 1;
|
||||||
|
g.setFont("Vector", 30);
|
||||||
|
g.drawString(data[0], 65, 180);
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.drawImage(storage.read("banglebridge.watch.img"),this.x + 1,this.y + 1);
|
||||||
|
g.drawImage(storage.read("banglebridge.heart.img"), 145, 167);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Finally add widget
|
||||||
|
|
||||||
|
|
||||||
|
initSensors();
|
||||||
|
// Bangle.drawWidgets();
|
||||||
|
// Terminal.println("Running BangleBridge");
|
||||||
|
data[0] = 80.5;
|
||||||
|
g.setFont("Vector", 30);
|
||||||
|
g.drawString(data[0], 65, 180);
|
||||||
|
// Bangle.drawWidgets();
|
||||||
|
setInterval(function () {
|
||||||
|
//console.log("---------------------------------------------------------------");
|
||||||
|
//console.log(data);
|
||||||
|
//Bluetooth.println(data[0]);
|
||||||
|
var measurement = {
|
||||||
|
hrm: data[0],
|
||||||
|
step: data[1],
|
||||||
|
batt: data[2],
|
||||||
|
acc: data[3],
|
||||||
|
com: data[4],
|
||||||
|
gps: data[5]
|
||||||
|
};
|
||||||
|
/* g.clear();
|
||||||
|
g.drawString(compssS,100,200);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bluetooth.println(JSON.stringify(measurement) + "#");
|
||||||
|
//draw();
|
||||||
|
|
||||||
|
}, 5 * 1000);
|
||||||
|
|
||||||
|
WIDGETS["banglebridge"]={
|
||||||
|
area: "tl",
|
||||||
|
width: 10,
|
||||||
|
draw: draw,
|
||||||
|
};
|
||||||
|
})(); //End of Widget
|
After Width: | Height: | Size: 3.4 KiB |