|
@ -1,2 +1,4 @@
|
|||
apps/animclk/V29.LBM.js
|
||||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
|
|
|
@ -7,4 +7,5 @@ appdates.csv
|
|||
.vscode
|
||||
.idea/
|
||||
_config.yml
|
||||
|
||||
tests/Layout/bin/tmp.*
|
||||
tests/Layout/testresult.bmp
|
||||
|
|
75
README.md
|
@ -1,10 +1,10 @@
|
|||
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 **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
||||
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
||||
|
||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||
|
@ -49,25 +49,25 @@ easily distinguish between file types, we use the following:
|
|||
|
||||
## Adding your app to the menu
|
||||
|
||||
* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js
|
||||
* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js
|
||||
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
|
||||
try and keep filenames short to avoid overflowing the buffer.
|
||||
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
|
||||
* Create a folder called `apps/<id>`, lets assume `apps/myappid`
|
||||
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
|
||||
* `apps/7chname/app.png` should be a 48px icon
|
||||
* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
|
||||
* `apps/myappid/app.png` should be a 48px icon
|
||||
* Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
|
||||
* Create an entry in `apps.json` as follows:
|
||||
|
||||
```
|
||||
{ "id": "7chname",
|
||||
{ "id": "myappid",
|
||||
"name": "My app's human readable name",
|
||||
"shortName" : "Short Name",
|
||||
"icon": "app.png",
|
||||
"description": "A detailed description of my great app",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"myappid.app.js","url":"app.js"},
|
||||
{"name":"myappid.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
},
|
||||
```
|
||||
|
@ -95,12 +95,12 @@ Be aware of the delay between commits and updates on github.io - it can take a f
|
|||
Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
|
||||
(4 discs), upload your files into the places described in your JSON:
|
||||
|
||||
* `app-icon.js` -> `7chname.img`
|
||||
* `app-icon.js` -> `myappid.img`
|
||||
|
||||
Now load `app.js` up in the editor, and click the down-arrow to the bottom
|
||||
right of the `Send to Espruino` icon. Click `Storage` and then either choose
|
||||
`7chname.app.js` (if you'd uploaded your app previously), or `New File`
|
||||
and then enter `7chname.app.js` as the name.
|
||||
`myappid.app.js` (if you'd uploaded your app previously), or `New File`
|
||||
and then enter `myappid.app.js` as the name.
|
||||
|
||||
Now, clicking the `Send to Espruino` icon will load the app directly into
|
||||
Espruino **and** will automatically run it.
|
||||
|
@ -115,10 +115,13 @@ and set it to `Load default application`.
|
|||
## Example Applications
|
||||
|
||||
To make the process easier we've come up with some example applications that you can use as a base
|
||||
when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app`
|
||||
or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
|
||||
when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app`
|
||||
or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to
|
||||
`apps.json`.
|
||||
|
||||
**Note:** the max filename length is 28 chars, so we suggest an app ID of under
|
||||
20 so that when `.app.js`/etc gets added to the end the filename isn't cropped.
|
||||
|
||||
**If you're making a widget** please start the name with `wid` to make
|
||||
it easy to find!
|
||||
|
||||
|
@ -192,8 +195,8 @@ and which gives information about the app for the Launcher.
|
|||
```
|
||||
{
|
||||
"name":"Short Name", // for Bangle.js menu
|
||||
"icon":"*7chname", // for Bangle.js menu
|
||||
"src":"-7chname", // source file
|
||||
"icon":"*myappid", // for Bangle.js menu
|
||||
"src":"-myappid", // source file
|
||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
||||
// if this is 'widget' then it's not displayed in the menu
|
||||
// if it's 'clock' then it'll be loaded by default at boot time
|
||||
|
@ -217,8 +220,10 @@ and which gives information about the app for the Launcher.
|
|||
{ "id": "appid", // 7 character app id
|
||||
"name": "Readable name", // readable name
|
||||
"shortName": "Short name", // short name for launcher
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"version": "0v01", // the version of this app
|
||||
"description": "...", // long description (can contain markdown)
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
|
||||
"type":"...", // optional(if app) -
|
||||
// 'app' - an application
|
||||
// 'widget' - a widget
|
||||
|
@ -226,7 +231,9 @@ and which gives information about the app for the Launcher.
|
|||
// 'bootloader' - code that runs at startup only
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
"tags": "", // comma separated tag list for searching
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
|
@ -259,6 +266,9 @@ and which gives information about the app for the Launcher.
|
|||
// (eg it's evaluated as JS)
|
||||
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
||||
// already exists
|
||||
"supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device
|
||||
// types named in the array. This allows different versions of
|
||||
// the app to be uploaded for different platforms
|
||||
},
|
||||
]
|
||||
"data": [ // list of files the app writes to
|
||||
|
@ -306,10 +316,10 @@ version of what's in `apps.json`:
|
|||
<script>
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
sendCustomizedApp({
|
||||
id : "7chname",
|
||||
id : "myappid",
|
||||
storage:[
|
||||
{name:"7chname.app.js", url:"app.js", content:app_source_code},
|
||||
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||
{name:"myappid.app.js", url:"app.js", content:app_source_code},
|
||||
{name:"myappid.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -367,40 +377,37 @@ that handles configuring the app.
|
|||
When the app settings are opened, this function is called with one
|
||||
argument, `back`: a callback to return to the settings menu.
|
||||
|
||||
Usually it will save any information in `app.json` where `app` is the name
|
||||
Usually it will save any information in `myappid.json` where `myappid` is the name
|
||||
of your app - so you should change the example accordingly.
|
||||
|
||||
Example `settings.js`
|
||||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.json',settings);
|
||||
}
|
||||
function get(key, def) { return require('Settings').get('myappid', key, def); }
|
||||
function set(key, value) { require('Settings').set('myappid', key, value); }
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: settings.monkeys||12,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
value: get('monkeys', 12),
|
||||
onchange: (m) => set('monkeys', m)
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
```json
|
||||
{ "id": "app",
|
||||
{ "id": "myappid",
|
||||
...
|
||||
"storage": [
|
||||
...
|
||||
{"name":"app.settings.js","url":"settings.js"},
|
||||
{"name":"myappid.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
{"name":"myappid.json"}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
@ -435,7 +442,7 @@ from the IDE.
|
|||
|
||||
### Misc Notes
|
||||
|
||||
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `7chname.json`, then load it at startup.
|
||||
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `myappid.json`, then load it at startup.
|
||||
|
||||
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
theme: jekyll-theme-minimal
|
|
@ -152,6 +152,8 @@
|
|||
"no-prototype-builtins": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-unreachable": "warn",
|
||||
"no-cond-assign": "warn",
|
||||
"no-useless-catch": "warn",
|
||||
// TODO: "no-undef": "warn",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
|
|
After Width: | Height: | Size: 650 B |
|
@ -0,0 +1,3 @@
|
|||
0.01: Initial version for upload
|
||||
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
|
||||
0.03: Code style cleanup
|
|
@ -0,0 +1,11 @@
|
|||
# 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)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBG2XwAgcPC6P/h//AAIDBA4Pwh/w+AGBAgIDBC4oVDAAITBCAIIBAYIBBAgIvHh4YCFgQPBAoIvCCwoAWIQYAQGLgAWI6bQVdQiiDOyAX/C/7+IAIYvSh4RBAYIXLAwJAHC6ZFCF5yn/C7wDBBAJ3EVAKBDC5QLBYAoLFC5nwCgoXlL44vSL653sL4QXBL6DvXC9YCBACIXCZ4YAQFaYAgPAhqCa4SDFLoZpICYIXDQKLyCDIQXVAAKI0AAYA=="))
|
|
@ -0,0 +1,137 @@
|
|||
// get 12 hour status, code from barclock
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
|
||||
// define background
|
||||
var imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
|
||||
|
||||
// define fonts
|
||||
// reg number first char 48 28 by 41
|
||||
var fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
// tiny font for percentage first char 48 6 by 8
|
||||
var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
|
||||
// date font first char 48 12 by 15
|
||||
var fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
|
||||
// define days of the week images
|
||||
var imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
|
||||
var imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
|
||||
var imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
|
||||
var imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
|
||||
var imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
|
||||
var imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
|
||||
var imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
|
||||
|
||||
|
||||
|
||||
// define icons
|
||||
var imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
|
||||
var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
|
||||
var img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
|
||||
var imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
|
||||
|
||||
//vars
|
||||
var separator = true;
|
||||
var is24hr = !is12Hour;
|
||||
var leadingZero = true;
|
||||
|
||||
//the following 2 sections are used from waveclk to schedule minutely updates
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
g.clear();
|
||||
g.drawImage(imgBg,0,0);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
function draw(){
|
||||
drawBackground();
|
||||
var date = new Date();
|
||||
var h = date.getHours(), m = date.getMinutes();
|
||||
var d = date.getDate(), w = date.getDay();
|
||||
g.reset();
|
||||
g.setBgColor(0,0,0);
|
||||
g.setColor(1,1,1);
|
||||
|
||||
//draw 24 hr indicator and 12 hr specific behavior
|
||||
if (is24hr){
|
||||
g.drawImage(img24hr,32, 65);
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
}
|
||||
} else if (h > 12) {
|
||||
g.drawImage(imgPM,40, 70);
|
||||
h = h - 12;
|
||||
if (leadingZero){
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
}
|
||||
|
||||
//draw separator
|
||||
if (separator){
|
||||
g.drawImage(imgSep, 85,98);}
|
||||
|
||||
//draw day of week
|
||||
var imgW = null;
|
||||
if (w == 0) {imgW = imgSun;}
|
||||
if (w == 1) {imgW = imgMon;}
|
||||
if (w == 2) {imgW = imgTue;}
|
||||
if (w == 3) {imgW = imgWed;}
|
||||
if (w == 4) {imgW = imgThr;}
|
||||
if (w == 5) {imgW = imgFri;}
|
||||
if (w == 6) {imgW = imgSat;}
|
||||
g.drawImage(imgW, 85, 63);
|
||||
|
||||
|
||||
// draw nums
|
||||
// draw time
|
||||
g.setColor(0,0,0);
|
||||
g.setBgColor(1,1,1);
|
||||
g.setFontCustom(fontNum, 48, 28, 41);
|
||||
if (h<10) {
|
||||
if (leadingZero) {
|
||||
h = ("0"+h).substr(-2);
|
||||
} else {
|
||||
h = " " + h;
|
||||
}
|
||||
}
|
||||
g.drawString(h, 25, 90, true);
|
||||
g.drawString(("0"+m).substr(-2), 92, 90, true);
|
||||
// draw date
|
||||
g.setFontCustom(fontDate, 48, 12, 15);
|
||||
g.drawString(("0"+d).substr(-2), 123,63, true);
|
||||
|
||||
// widget redraw
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
draw();
|
||||
|
||||
//the following section is also from waveclk
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -2,13 +2,14 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great app",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My widget's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great widget",
|
||||
"tags": "widget",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.wid.js","url":"widget.js"}
|
||||
|
|
|
@ -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 |
|
@ -6,3 +6,6 @@
|
|||
0.06: Actual pixels as of 12 Jun 2020
|
||||
0.07: Pressing a button now exits immediately (fix #618)
|
||||
0.08: Make about (mostly) work on non-240px screens
|
||||
0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021
|
||||
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
|
|
After Width: | Height: | Size: 15 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Use the new multiplatform 'Layout' library
|
||||
0.03: Exit as first menu option, dont show decimal places for seconds
|
||||
|
|
|
@ -8,6 +8,9 @@ function getFileName(n) {
|
|||
function showMenu() {
|
||||
var menu = {
|
||||
"" : { title : "Accel Logger" },
|
||||
"Exit" : function() {
|
||||
load();
|
||||
},
|
||||
"File No" : {
|
||||
value : fileNumber,
|
||||
min : 0,
|
||||
|
@ -21,9 +24,6 @@ function showMenu() {
|
|||
"View Logs" : function() {
|
||||
viewLogs();
|
||||
},
|
||||
"Exit" : function() {
|
||||
load();
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ function viewLog(n) {
|
|||
var records = 0, l = "", ll="";
|
||||
while ((l=f.readLine())!==undefined) {records++;ll=l;}
|
||||
var length = 0;
|
||||
if (ll) length = (ll.split(",")[0]|0)/1000;
|
||||
if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 );
|
||||
|
||||
var menu = {
|
||||
"" : { title : "Log "+n }
|
||||
|
@ -94,18 +94,17 @@ function startRecord(force) {
|
|||
var Layout = require("Layout");
|
||||
var layout = new Layout({ type: "v", c: [
|
||||
{type:"txt", font:"6x8", label:"Samples", pad:2},
|
||||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:true},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
]
|
||||
},[ // Buttons...
|
||||
},{btns:[ // Buttons...
|
||||
{label:"STOP", cb:()=>{
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
}}
|
||||
]);
|
||||
layout.update();
|
||||
]});
|
||||
layout.render();
|
||||
|
||||
// now start writing
|
||||
|
|
|
@ -46,7 +46,6 @@ function getData() {
|
|||
});
|
||||
return new Promise(resolve=>{
|
||||
Puck.eval(`require("Storage").read(${JSON.stringify(fn)})`,csv=>{
|
||||
fileData[fn] = csv.trim();
|
||||
var el = document.querySelector(`.card-body[fn='${fn}']`);
|
||||
el.innerHTML = '<canvas width="400" height="100"></canvas>';
|
||||
var c = el.firstChild;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Analogue Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -9,3 +9,7 @@
|
|||
0.09: Add per alarm auto-snooze option
|
||||
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
||||
0.11: Respect Quiet Mode
|
||||
0.12: Fix widget for bangle 2, now uses theme
|
||||
Widgets now shown on Alarm screen
|
||||
0.13: Alarm widget state now updates when setting/resetting an alarm
|
||||
0.14: Order of 'back' menu item
|
||||
|
|
|
@ -18,8 +18,10 @@ function showAlarm(alarm) {
|
|||
var buzzCount = 10;
|
||||
if (alarm.msg)
|
||||
msg += "\n"+alarm.msg;
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showPrompt(msg,{
|
||||
title:"ALARM!",
|
||||
title:alarm.timer ? "TIMER!" : "ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
|
|
|
@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
|||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
as : false, // auto snooze
|
||||
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
|
||||
}
|
||||
];*/
|
||||
|
||||
|
@ -18,6 +19,12 @@ function formatTime(t) {
|
|||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function formatMins(t) {
|
||||
mins = (0|t)%60;
|
||||
hrs = 0|(t/60);
|
||||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentHr() {
|
||||
var time = new Date();
|
||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
|
@ -25,17 +32,25 @@ function getCurrentHr() {
|
|||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'New Alarm': ()=>editAlarm(-1)
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'< Back' : ()=>{load();},
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
if (alarm.timer) {
|
||||
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
|
||||
} else {
|
||||
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
}
|
||||
menu[txt] = function() {
|
||||
editAlarm(idx);
|
||||
if (alarm.timer) editTimer(idx);
|
||||
else editAlarm(idx);
|
||||
};
|
||||
});
|
||||
menu['< Back'] = ()=>{load();};
|
||||
|
||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -55,7 +70,8 @@ function editAlarm(alarmIndex) {
|
|||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'': { 'title': 'Alarm' },
|
||||
'< Back' : showMainMenu,
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
|
@ -105,7 +121,60 @@ function editAlarm(alarmIndex) {
|
|||
showMainMenu();
|
||||
};
|
||||
}
|
||||
menu['< Back'] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editTimer(alarmIndex) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var hrs = 0;
|
||||
var mins = 5;
|
||||
var en = true;
|
||||
if (!newAlarm) {
|
||||
var a = alarms[alarmIndex];
|
||||
mins = (0|a.timer)%60;
|
||||
hrs = 0|(a.timer/60);
|
||||
en = a.on;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Timer' },
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
}
|
||||
};
|
||||
function getTimer() {
|
||||
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
|
||||
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
|
||||
// Save alarm
|
||||
return {
|
||||
on : en,
|
||||
timer : (hrs*60)+mins,
|
||||
hr : hr,
|
||||
rp : false, as: false
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
if (newAlarm) alarms.push(getTimer());
|
||||
else alarms[alarmIndex] = getTimer();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["> Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
(() => {
|
||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
||||
alarms = alarms.filter(alarm=>alarm.on);
|
||||
if (!alarms.length) return; // no alarms, no widget!
|
||||
delete alarms;
|
||||
// add the widget
|
||||
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
|
||||
g.setColor(-1);
|
||||
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
}};
|
||||
})()
|
||||
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
},reload:function() {
|
||||
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS["alarm"].reload();
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Remove messages on disconnect
|
||||
Fix music control
|
||||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
|
@ -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" });
|
||||
// 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);
|
||||
})
|
|
@ -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.02: Moved arrow image load to global scope
|
||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||
0.04: removed LCD1.write() as it was keeping LCD on
|
||||
0.04: removed LED1.write() as it was keeping LCD on
|
||||
0.05: Turn compass off when screen off
|
||||
Calibrate at start if no info
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
|
||||
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
|
||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
var intervalRef;
|
||||
|
@ -7,6 +7,7 @@ var bearing=0; // always point north
|
|||
var heading = 0;
|
||||
var oldHeading = 0;
|
||||
var candraw = false;
|
||||
var isCalibrating = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function flip1(x,y) {
|
||||
|
@ -29,7 +30,7 @@ function drawCompass(hd) {
|
|||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||
hd=hd*Math.PI/180;
|
||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||
|
||||
|
||||
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
||||
var poly = [
|
||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||
|
@ -40,16 +41,16 @@ function drawCompass(hd) {
|
|||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||
];
|
||||
|
||||
|
||||
buf1.fillPoly(poly);
|
||||
flip1(56, 56);
|
||||
}
|
||||
|
||||
// stops violent compass swings and wobbles, takes 3ms
|
||||
function newHeading(m,h){
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
|
@ -76,7 +77,7 @@ function tiltfixread(O,S){
|
|||
return psi;
|
||||
}
|
||||
|
||||
function reading() {
|
||||
function reading(m) {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
var dir = bearing - heading;
|
||||
|
@ -97,18 +98,19 @@ function reading() {
|
|||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
function onMag(m) {
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
}
|
||||
Bangle.on('mag', onMag);
|
||||
Bangle.setCompassPower(1, "app");
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
Bangle.removeListener('mag', onMag);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
|
@ -132,6 +134,7 @@ function docalibrate(e,first){
|
|||
flip1(56,56);
|
||||
|
||||
calibrate().then((r)=>{
|
||||
isCalibrating = false;
|
||||
require("Storage").write("magnav.json",r);
|
||||
Bangle.buzz();
|
||||
CALIBDATA = r;
|
||||
|
@ -142,27 +145,39 @@ function docalibrate(e,first){
|
|||
startdraw();
|
||||
setTimeout(setButtons,1000);
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
}
|
||||
|
||||
if (first === undefined) first = false;
|
||||
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
if (first)
|
||||
isCalibrating = true;
|
||||
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
function startdraw(){
|
||||
Bangle.setCompassPower(1, "app");
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
intervalRef = setInterval(reading,500);
|
||||
if (intervalRef) clearInterval(intervalRef);
|
||||
intervalRef = setInterval(reading,200);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
|
||||
Bangle.setCompassPower(0, "app");
|
||||
if (intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
|
@ -170,8 +185,9 @@ function setButtons(){
|
|||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||
}
|
||||
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (isCalibrating) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
|
@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) {
|
|||
}
|
||||
});
|
||||
|
||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
startdraw();
|
||||
setButtons();
|
||||
|
||||
Bangle.setLCDPower(1);
|
||||
if (CALIBDATA) startdraw(); else docalibrate({},true);
|
||||
|
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: First release
|
|
@ -0,0 +1,26 @@
|
|||
# Authentiwatch - 2FA Authenticator
|
||||
|
||||
## Supports
|
||||
|
||||
* Google Authenticator compatible 2-factor authentication
|
||||
* Hash calculations:
|
||||
* Bangle 1: SHA1 only (same as Google Authenticator)
|
||||
* Bangle 2: All (SHA1, SHA256, SHA512)
|
||||
* Timed (TOTP) and Counter (HOTP) modes
|
||||
* Custom periods
|
||||
* Between 6 and 10 digits
|
||||
* Phone/PC configuration web page:
|
||||
* Add/edit/delete/arrange tokens
|
||||
* Scan QR codes
|
||||
* Produce scannable QR codes
|
||||
|
||||
## Usage
|
||||
|
||||
* Use the Phone/PC web page interface to manage the tokens stored on the watch.
|
||||
* Tokens are stored *ONLY* on the watch.
|
||||
* Swipe right to exit to the app launcher.
|
||||
* Swipe left on selected counter token to advance the counter to the next value.
|
||||
|
||||
## Creator
|
||||
|
||||
Andrew Gregory (andrew.gregory at gmail)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))
|
|
@ -0,0 +1,300 @@
|
|||
const tokenentryheight = 46;
|
||||
// Hash functions
|
||||
const crypto = require("crypto");
|
||||
const algos = {
|
||||
"SHA512":{sha:crypto.SHA512,retsz:64,blksz:128},
|
||||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||
};
|
||||
|
||||
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
|
||||
|
||||
// QR Code Text
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret}
|
||||
//
|
||||
// ${algorithm} : one of SHA1 / SHA256 / SHA512
|
||||
// ${digits} : one of 6 / 8
|
||||
// ${period} : one of 30 / 60
|
||||
// ${url} : a domain name "example.com"
|
||||
// ${secret} : the seed code
|
||||
|
||||
function b32decode(seedstr) {
|
||||
// RFC4648
|
||||
var i, buf = 0, bitcount = 0, retstr = "";
|
||||
for (i in seedstr) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0);
|
||||
if (c != -1) {
|
||||
buf <<= 5;
|
||||
buf |= c;
|
||||
bitcount += 5;
|
||||
if (bitcount >= 8) {
|
||||
retstr += String.fromCharCode(buf >> (bitcount - 8));
|
||||
buf &= (0xFF >> (16 - bitcount));
|
||||
bitcount -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
retstr += String.fromCharCode(buf << (8 - bitcount));
|
||||
}
|
||||
var retbuf = new Uint8Array(retstr.length);
|
||||
for (i in retstr) {
|
||||
retbuf[i] = retstr.charCodeAt(i);
|
||||
}
|
||||
return retbuf;
|
||||
}
|
||||
function do_hmac(key, message, algo) {
|
||||
var a = algos[algo];
|
||||
// RFC2104
|
||||
if (key.length > a.blksz) {
|
||||
key = a.sha(key);
|
||||
}
|
||||
var istr = new Uint8Array(a.blksz + message.length);
|
||||
var ostr = new Uint8Array(a.blksz + a.retsz);
|
||||
for (var i = 0; i < a.blksz; ++i) {
|
||||
var c = (i < key.length) ? key[i] : 0;
|
||||
istr[i] = c ^ 0x36;
|
||||
ostr[i] = c ^ 0x5C;
|
||||
}
|
||||
istr.set(message, a.blksz);
|
||||
ostr.set(a.sha(istr), a.blksz);
|
||||
var ret = a.sha(ostr);
|
||||
// RFC4226 dynamic truncation
|
||||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||
return v.getUint32(0) & 0x7FFFFFFF;
|
||||
}
|
||||
function hotp(token) {
|
||||
var tick;
|
||||
var d = new Date();
|
||||
if (token.period > 0) {
|
||||
// RFC6238 - timed
|
||||
var seconds = Math.floor(d.getTime() / 1000);
|
||||
tick = Math.floor(seconds / token.period);
|
||||
} else {
|
||||
// RFC4226 - counter
|
||||
tick = -token.period;
|
||||
}
|
||||
var msg = new Uint8Array(8);
|
||||
var v = new DataView(msg.buffer);
|
||||
v.setUint32(0, tick >> 16 >> 16);
|
||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||
var ret = "";
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = "Not supported";
|
||||
}
|
||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||
}
|
||||
|
||||
var state = {
|
||||
listy: 0,
|
||||
prevcur:0,
|
||||
curtoken:-1,
|
||||
nextTime:0,
|
||||
otp:"",
|
||||
rem:0,
|
||||
hide:0
|
||||
};
|
||||
|
||||
function drawToken(id, r) {
|
||||
var x1 = r.x;
|
||||
var y1 = r.y;
|
||||
var x2 = r.x + r.w - 1;
|
||||
var y2 = r.y + r.h - 1;
|
||||
var adj;
|
||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||
if (id == state.curtoken) {
|
||||
// current token
|
||||
g.setColor(g.theme.fgH);
|
||||
g.setBgColor(g.theme.bgH);
|
||||
g.setFont("Vector", 16);
|
||||
// center just below top line
|
||||
g.setFontAlign(0, -1, 0);
|
||||
adj = y1;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setFont("Vector", 30);
|
||||
// center in box
|
||||
g.setFontAlign(0, 0, 0);
|
||||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period);
|
||||
g.fillRect(x1, y2 - 4, xr, y2 - 1);
|
||||
adj = 0;
|
||||
} else {
|
||||
// counter - draw triangle as swipe hint
|
||||
let yc = (y1 + y2) / 2;
|
||||
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
|
||||
adj = 5;
|
||||
}
|
||||
// digits just below label
|
||||
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
|
||||
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
g.drawLine(x1, y1, x2, y1);
|
||||
g.drawLine(x1, y2, x2, y2);
|
||||
g.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
if (state.curtoken != -1) {
|
||||
var t = tokens[state.curtoken];
|
||||
if (d.getTime() > state.nextTime) {
|
||||
if (state.hide == 0) {
|
||||
// auto-hide the current token
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nextTime = 0;
|
||||
} else {
|
||||
// time to generate a new token
|
||||
var r = hotp(t);
|
||||
state.nextTime = r.next;
|
||||
state.otp = r.hotp;
|
||||
if (t.period <= 0) {
|
||||
state.hide = 1;
|
||||
}
|
||||
state.hide--;
|
||||
}
|
||||
}
|
||||
state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000));
|
||||
}
|
||||
if (tokens.length > 0) {
|
||||
var drewcur = false;
|
||||
var id = Math.floor(state.listy / tokenentryheight);
|
||||
var y = id * tokenentryheight + Bangle.appRect.y - state.listy;
|
||||
while (id < tokens.length && y < Bangle.appRect.y2) {
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight});
|
||||
if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
|
||||
drewcur = true;
|
||||
}
|
||||
id += 1;
|
||||
y += tokenentryheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - draw it again in 1sec
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
}
|
||||
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
|
||||
if (tokens[state.curtoken].period <= 0) {
|
||||
state.hide = 0;
|
||||
}
|
||||
} else {
|
||||
// de-select the current token if it is scrolled out of view
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nexttime = 0;
|
||||
}
|
||||
} else {
|
||||
g.setFont("Vector", 30);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
}
|
||||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
|
||||
if (id == state.curtoken || tokens.length == 0) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
if (id != -1) {
|
||||
var y = id * tokenentryheight - state.listy;
|
||||
if (y < 0) {
|
||||
state.listy += y;
|
||||
y = 0;
|
||||
}
|
||||
y += tokenentryheight;
|
||||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
}
|
||||
state.nextTime = 0;
|
||||
state.curtoken = id;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
|
||||
newy = Math.max(0, newy);
|
||||
if (newy != state.listy) {
|
||||
state.listy = newy;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function onSwipe(e) {
|
||||
if (e == 1) {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||
tokens[state.curtoken].period--;
|
||||
require("Storage").writeJSON("authentiwatch.json", tokens);
|
||||
state.nextTime = 0;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function bangle1Btn(e) {
|
||||
if (tokens.length > 0) {
|
||||
if (state.curtoken == -1) {
|
||||
state.curtoken = state.prevcur;
|
||||
} else {
|
||||
switch (e) {
|
||||
case -1: state.curtoken--; break;
|
||||
case 1: state.curtoken++; break;
|
||||
}
|
||||
}
|
||||
state.curtoken = Math.max(state.curtoken, 0);
|
||||
state.curtoken = Math.min(state.curtoken, tokens.length - 1);
|
||||
var fakee = {};
|
||||
fakee.y = state.curtoken * tokenentryheight - state.listy + Bangle.appRect.y;
|
||||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on('touch', onTouch);
|
||||
Bangle.on('drag' , onDrag );
|
||||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 964 B |
|
@ -0,0 +1,373 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style type="text/css">
|
||||
body{font-family:sans-serif}
|
||||
body div{display:none}
|
||||
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
||||
#tokens th,#tokens td{padding:5px}
|
||||
#tokens tr:nth-child(odd){background-color:#ccc}
|
||||
#tokens tr:nth-child(even){background-color:#eee}
|
||||
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
||||
#advbtn,#scan,#tokenqr table{text-align:center}
|
||||
#edittoken tbody#adv{display:none}
|
||||
#edittoken.showadv tbody#adv{display:table-row-group}
|
||||
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
||||
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
||||
button{height:3em}
|
||||
table button{width:100%}
|
||||
form.totp tr.hotp,form.hotp tr.totp{display:none}
|
||||
</style>
|
||||
|
||||
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
||||
<script src="qr_packed.js"></script>
|
||||
|
||||
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
||||
<script src="../../core/lib/qrcode.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/* Start of all TOTP URLs */
|
||||
const otpAuthUrl = 'otpauth://';
|
||||
|
||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||
|
||||
/* Array of TOTP tokens */
|
||||
var tokens=[];
|
||||
|
||||
/* Remove any non-base-32 characters from the given string and collapses
|
||||
* whitespace to a single space. Optionally removes all whitespace from
|
||||
* the string.
|
||||
*/
|
||||
function base32clean(val, nows) {
|
||||
var ret = val.replaceAll(/\s+/g, ' ');
|
||||
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
||||
if (nows) {
|
||||
ret = ret.replaceAll(/\s+/g, '');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Save changes to a token to the global tokens[] array.
|
||||
* id is the index into the global tokens[].
|
||||
* forget is a flag indicating if the token should be forgotten.
|
||||
*/
|
||||
function saveEdit(id, forget) {
|
||||
if (forget) {
|
||||
if (confirm('Forget token?')) {
|
||||
tokens.splice(id, 1);
|
||||
updateTokens();
|
||||
}
|
||||
} else {
|
||||
let fe = document.forms['edittoken'].elements;
|
||||
let d = parseInt(fe['digits'].value);
|
||||
let p = parseInt(fe['period'].value);
|
||||
let c = parseInt(fe['count'].value);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: p = (c > 0) ? -c : 0; break;
|
||||
default : p = (p > 0) ? p : 30; break;
|
||||
}
|
||||
tokens[id] = {
|
||||
'algorithm':fe['algorithm'].value,
|
||||
'digits':((d > 0) ? d : 6),
|
||||
'period':p,
|
||||
'issuer':fe['issuer'].value,
|
||||
'account':fe['account'].value,
|
||||
'secret':base32clean(fe['secret'].value, false),
|
||||
'label':fe['label'].value
|
||||
};
|
||||
updateTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate and display a QR-code representing the current token.
|
||||
*/
|
||||
function showQrCode() {
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
var url = new String(otpAuthUrl);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: url += 'hotp/'; break;
|
||||
default : url += 'totp/'; break;
|
||||
}
|
||||
if (fe['account'].value.length > 0) {
|
||||
url += encodeURIComponent(fe['account'].value);
|
||||
} else {
|
||||
url += encodeURIComponent(fe['label'].value);
|
||||
}
|
||||
url += '?';
|
||||
if (fe['issuer'].value.length > 0) {
|
||||
url += 'issuer=' + encodeURIComponent(fe['issuer'].value);
|
||||
url += '&';
|
||||
}
|
||||
url += 'secret=' + base32clean(fe['secret'].value, true);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]:
|
||||
if (parseInt(fe['count'].value) != 0) {
|
||||
url += '&counter=' + fe['count'].value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (parseInt(fe['period'].value) != 30) {
|
||||
url += '&period=' + fe['period'].value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parseInt(fe['digits'].value) != 6) {
|
||||
url += '&digits=' + fe['digits'].value;
|
||||
}
|
||||
if (fe['algorithm'].value != 'SHA1') {
|
||||
url += '&algorithm=' + fe['algorithm'].value;
|
||||
}
|
||||
tokenqr.clear();
|
||||
tokenqr.makeCode(url);
|
||||
document.body.className = 'showqr';
|
||||
}
|
||||
|
||||
function onTypeChanged() {
|
||||
var f = document.forms['edittoken'];
|
||||
var fe = f.elements;
|
||||
if (fe['type'].value == tokentypes[0]) { f.classList.add('totp'); f.classList.remove('hotp'); }
|
||||
if (fe['type'].value == tokentypes[1]) { f.classList.add('hotp'); f.classList.remove('totp'); }
|
||||
}
|
||||
|
||||
/* Generate a form for editing the specified token.
|
||||
* id is the index into the global tokens[].
|
||||
*/
|
||||
function editToken(id) {
|
||||
var p;
|
||||
const selectMarkup = function(name, ary, cur, onchg) {
|
||||
var ret = '<select name="' + name + '"' + ((typeof onchg == 'string') ? ' onchange="' + onchg + '"' : '') + '>';
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
ret += '<option' + ((ary[i] == cur) ? ' selected=selected' : '') + '>' + ary[i] + '</option>';
|
||||
}
|
||||
return ret + '</select>';
|
||||
};
|
||||
scanning=false;
|
||||
var markup = '<form id="edittoken"><input type="hidden" name="tokenid" value="' + id + '"><table>';
|
||||
markup += '<tr><td>Name:</td><td><input name="label" type="text" value="' + tokens[id].label + '"></td></tr>';
|
||||
markup += '<tr><td>Secret:</td><td><input name="secret" type="text" value="' + tokens[id].secret + '"></td></tr>';
|
||||
markup += '<tbody id="adv">';
|
||||
markup += '<tr><td>Account:</td><td><input name="account" type="text" value="' + tokens[id].account + '"></td></tr>';
|
||||
markup += '<tr><td>Issuer:</td><td><input name="issuer" type="text" value="' + tokens[id].issuer + '"></td></tr>';
|
||||
p = parseInt(tokens[id].period);
|
||||
markup += '<tr><td>Type:</td><td>';
|
||||
markup += selectMarkup('type', tokentypes, (tokens[id].period > 0) ? tokentypes[0] : tokentypes[1], 'onTypeChanged()');
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr class="totp"><td>Period:</td><td><input name="period" type="text" value="' + ((p > 0) ? p : 30) + '"></td></tr>';
|
||||
markup += '<tr class="hotp"><td>Count:</td><td><input name="count" type="text" value="' + ((p >= 0) ? 0 : -p) + '"></td></tr>';
|
||||
markup += '<tr><td>Digits:</td><td>';
|
||||
markup += selectMarkup('digits', ['6','7','8','9','10'], tokens[id].digits);
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr><td>Hash:</td><td>';
|
||||
markup += selectMarkup('algorithm', ['SHA1','SHA256','SHA512'], tokens[id].algorithm);
|
||||
markup += '</td></tr>';
|
||||
markup += '</tbody><tr><td id="advbtn" colspan="2">';
|
||||
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
|
||||
markup += '</td></tr></table></form>';
|
||||
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
|
||||
if (tokens[id].isnew) {
|
||||
markup += '<button type="button" onclick="startScan()">Scan QR Code</button>';
|
||||
} else {
|
||||
markup += '<button type="button" onclick="showQrCode()">Show QR Code</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
|
||||
}
|
||||
document.getElementById('edit').innerHTML = markup;
|
||||
document.body.className = 'editing';
|
||||
onTypeChanged();
|
||||
}
|
||||
|
||||
/* Create a new blank token and open the editor for it.
|
||||
*/
|
||||
function addToken() {
|
||||
tokens[tokens.length] = {'algorithm':'SHA1','digits':6,'period':30,'issuer':'','account':'','secret':'','label':'','isnew':true};
|
||||
editToken(tokens.length - 1);
|
||||
}
|
||||
|
||||
/* Move the specified token up or down in the global tokens[].
|
||||
* id is the index in the global tokens[] of the token to move.
|
||||
* dir is the direction to move: -1=up, 1=down.
|
||||
*/
|
||||
function moveToken(id, dir) {
|
||||
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
||||
updateTokens();
|
||||
}
|
||||
|
||||
/* Update the display listing all the tokens.
|
||||
*/
|
||||
function updateTokens() {
|
||||
const tokenButton = function(fn, id, label, dir) {
|
||||
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
|
||||
};
|
||||
var markup = '<table><tr><th>Token</th><th colspan="2">Order</th></tr>';
|
||||
/* any tokens marked new are cancelled new additions and must be removed */
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].isnew) {
|
||||
tokens.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
markup += '<tr><td>';
|
||||
markup += tokenButton('editToken', i, tokens[i].label);
|
||||
markup += '</td><td>';
|
||||
if (i < (tokens.length - 1)) {
|
||||
markup += tokenButton('moveToken', i, '▼', 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'] + ')';
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||
t['period'] = '30';
|
||||
fe['type'].value = tokentypes[1];
|
||||
} else {
|
||||
t['counter'] = '0';
|
||||
fe['type'].value = tokentypes[0];
|
||||
}
|
||||
fe['algorithm'].value = t['algorithm'];
|
||||
fe['digits' ].value = t['digits' ];
|
||||
fe['count' ].value = t['counter' ];
|
||||
fe['period' ].value = t['period' ];
|
||||
fe['secret' ].value = t['secret' ];
|
||||
fe['issuer' ].value = t['issuer' ];
|
||||
fe['account' ].value = t['account' ];
|
||||
fe['label' ].value = t['label' ];
|
||||
onTypeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function startScan() {
|
||||
document.body.className = 'scanning';
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({video:{facingMode:'environment'}})
|
||||
.then(function(stream){
|
||||
scanning=true;
|
||||
video.setAttribute('playsinline',true);
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
scanTick();
|
||||
doScan();
|
||||
});
|
||||
}
|
||||
function scanTick() {
|
||||
canvasElement.height = video.videoHeight;
|
||||
canvasElement.width = video.videoWidth;
|
||||
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
||||
if (scanning) {
|
||||
requestAnimationFrame(scanTick);
|
||||
} else {
|
||||
video.srcObject.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
function doScan() {
|
||||
try {
|
||||
qrcode.decode();
|
||||
} catch (e) {
|
||||
setTimeout(doScan,300);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load settings JSON file from the watch.
|
||||
*/
|
||||
function loadTokens() {
|
||||
Util.showModal('Loading...');
|
||||
Puck.eval(`require('Storage').read(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Util.hideModal();
|
||||
try {
|
||||
tokens = JSON.parse(data);
|
||||
updateTokens();
|
||||
} catch {
|
||||
tokens = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function saveTokens() {
|
||||
Util.showModal('Saving...');
|
||||
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(tokens)})\n`,()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
function onInit() {
|
||||
loadTokens();
|
||||
updateTokens();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="select">
|
||||
<h1>Authentiwatch</h1>
|
||||
<div id="tokens">
|
||||
<p>No watch comms.</p>
|
||||
</div>
|
||||
<div id="scan">
|
||||
<table>
|
||||
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
||||
<tr><td><button type="button" onclick="editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="edit">
|
||||
</div>
|
||||
<div id="tokenqr">
|
||||
<table><tr><td id="qrcode"></td></tr><tr><td>
|
||||
<button type="button" onclick="document.body.className='editing'">Back</button>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const video=document.createElement('video');
|
||||
const canvasElement=document.getElementById('qr-canvas');
|
||||
const canvas=canvasElement.getContext('2d');
|
||||
let scanning=false;
|
||||
const tokenqr=new QRCode(document.getElementById('qrcode'), '');
|
||||
</script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,107 @@
|
|||
/* Packed with Google Closure
|
||||
*
|
||||
* Ported to JavaScript by Lazar Laszlo 2011
|
||||
* lazarsoft@gmail.com, www.lazarsoft.info
|
||||
*
|
||||
* Copyright 2007 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks",
|
||||
function(){for(var d=0,c=0;c<this.ecBlocks.length;c++)d+=this.ecBlocks[c].length;return d});this.getECBlocks=function(){return this.ecBlocks}}function k(h,b,e,d,c,a){this.versionNumber=h;this.alignmentPatternCenters=b;this.ecBlocks=[e,d,c,a];h=0;b=e.ECCodewordsPerBlock;e=e.getECBlocks();for(d=0;d<e.length;d++)c=e[d],h+=c.Count*(c.DataCodewords+b);this.totalCodewords=h;this.__defineGetter__("VersionNumber",function(){return this.versionNumber});this.__defineGetter__("AlignmentPatternCenters",function(){return this.alignmentPatternCenters});
|
||||
this.__defineGetter__("TotalCodewords",function(){return this.totalCodewords});this.__defineGetter__("DimensionForVersion",function(){return 17+4*this.versionNumber});this.buildFunctionPattern=function(){var d=this.DimensionForVersion,c=new I(d);c.setRegion(0,0,9,9);c.setRegion(d-8,0,8,9);c.setRegion(0,d-8,9,8);for(var e=this.alignmentPatternCenters.length,b=0;b<e;b++)for(var a=this.alignmentPatternCenters[b]-2,h=0;h<e;h++)0==b&&(0==h||h==e-1)||b==e-1&&0==h||c.setRegion(this.alignmentPatternCenters[h]-
|
||||
2,a,5,5);c.setRegion(6,9,1,d-17);c.setRegion(9,6,d-17,1);6<this.versionNumber&&(c.setRegion(d-11,0,3,6),c.setRegion(0,d-11,6,3));return c};this.getECBlocksForLevel=function(c){return this.ecBlocks[c.ordinal()]}}function z(h,b,e,d,c,a,l,m,f){this.a11=h;this.a12=d;this.a13=l;this.a21=b;this.a22=c;this.a23=m;this.a31=e;this.a32=a;this.a33=f;this.transformPoints1=function(c){for(var d=c.length,e=this.a11,b=this.a12,h=this.a13,a=this.a21,l=this.a22,p=this.a23,m=this.a31,f=this.a32,g=this.a33,y=0;y<d;y+=
|
||||
2){var q=c[y],k=c[y+1],n=h*q+p*k+g;c[y]=(e*q+a*k+m)/n;c[y+1]=(b*q+l*k+f)/n}};this.transformPoints2=function(c,d){for(var e=c.length,b=0;b<e;b++){var h=c[b],a=d[b],l=this.a13*h+this.a23*a+this.a33;c[b]=(this.a11*h+this.a21*a+this.a31)/l;d[b]=(this.a12*h+this.a22*a+this.a32)/l}};this.buildAdjoint=function(){return new z(this.a22*this.a33-this.a23*this.a32,this.a23*this.a31-this.a21*this.a33,this.a21*this.a32-this.a22*this.a31,this.a13*this.a32-this.a12*this.a33,this.a11*this.a33-this.a13*this.a31,this.a12*
|
||||
this.a31-this.a11*this.a32,this.a12*this.a23-this.a13*this.a22,this.a13*this.a21-this.a11*this.a23,this.a11*this.a22-this.a12*this.a21)};this.times=function(c){return new z(this.a11*c.a11+this.a21*c.a12+this.a31*c.a13,this.a11*c.a21+this.a21*c.a22+this.a31*c.a23,this.a11*c.a31+this.a21*c.a32+this.a31*c.a33,this.a12*c.a11+this.a22*c.a12+this.a32*c.a13,this.a12*c.a21+this.a22*c.a22+this.a32*c.a23,this.a12*c.a31+this.a22*c.a32+this.a32*c.a33,this.a13*c.a11+this.a23*c.a12+this.a33*c.a13,this.a13*c.a21+
|
||||
this.a23*c.a22+this.a33*c.a23,this.a13*c.a31+this.a23*c.a32+this.a33*c.a33)}}function P(h,b){this.bits=h;this.points=b}function Q(h){this.image=h;this.resultPointCallback=null;this.sizeOfBlackWhiteBlackRun=function(b,e,d,c){var h=Math.abs(c-e)>Math.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=e<c?1:-1,x=b<d?1:-1,v=0,t=b,a=e;t!=d;t+=x){var J=h?a:t,n=h?t:a;1==v?this.image[J+n*g.width]&&v++:this.image[J+n*g.width]||v++;if(3==v)return c=t-b,e=a-e,Math.sqrt(c*
|
||||
c+e*e);q+=f;if(0<q){if(a==c)break;a+=k;q-=m}}b=d-b;e=c-e;return Math.sqrt(b*b+e*e)};this.sizeOfBlackWhiteBlackRunBothWays=function(b,e,d,c){var a=this.sizeOfBlackWhiteBlackRun(b,e,d,c),h=1;d=b-(d-b);0>d?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X),
|
||||
Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b,
|
||||
d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5,
|
||||
3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0<a.AlignmentPatternCenters.length)for(a=1-3/m,f=Math.floor(e.X+a*(d.X-e.X+b.X-e.X)),a=Math.floor(e.Y+a*(d.Y-e.Y+b.Y-e.Y));;){f=this.findAlignmentInRegion(c,
|
||||
f,a,4);break}c=this.createTransform(e,d,b,f,h);h=this.sampleGrid(this.image,c,h);return new P(h,null==f?[b,e,d]:[b,e,d,f])};this.detect=function(){var b=(new S).findFinderPattern(this.image);return this.processFinderPatternInfo(b)}}function r(h){this.errorCorrectionLevel=C.forBits(h>>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<<
|
||||
3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h&
|
||||
31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e<this.bits.length;e++)this.bits[e]=0;this.__defineGetter__("Width",function(){return this.width});this.__defineGetter__("Height",function(){return this.height});this.__defineGetter__("Dimension",function(){if(this.width!=this.height)throw"Can't call getDimension() on a non-square matrix";return this.width});this.get_Renamed=function(d,c){return 0!=(u(this.bits[c*this.rowSize+(d>>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+
|
||||
(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;c<d;c++)this.bits[c]=0};this.setRegion=function(d,c,e,b){if(0>c||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c<b;c++)for(var h=c*this.rowSize,a=d;a<e;a++)this.bits[h+(a>>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords=
|
||||
a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e=
|
||||
0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d<c;d++)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;throw"Error readFormatInformation";
|
||||
};this.readVersion=function(){if(null!=this.parsedVersion)return this.parsedVersion;var e=this.bitMatrix.Dimension,d=e-17>>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=
|
||||
this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0<k;k-=2){6==k&&k--;for(var x=0;x<b;x++)for(var v=a?b-1-x:x,t=0;2>t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k-
|
||||
t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1<e&&0==b[0]){for(var d=1;d<e&&0==b[d];)d++;if(d==e)this.coefficients=a.Zero.coefficients;else{this.coefficients=Array(e-d);for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=0;for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=b[d+e]}}else this.coefficients=b;this.__defineGetter__("Zero",
|
||||
function(){return 0==this.coefficients[0]});this.__defineGetter__("Degree",function(){return this.coefficients.length-1});this.__defineGetter__("Coefficients",function(){return this.coefficients});this.getCoefficient=function(c){return this.coefficients[this.coefficients.length-1-c]};this.evaluateAt=function(c){if(0==c)return this.getCoefficient(0);var d=this.coefficients.length;if(1==c){for(var b=c=0;b<d;b++)c=n.addOrSubtract(c,this.coefficients[b]);return c}for(var e=this.coefficients[0],b=1;b<
|
||||
d;b++)e=n.addOrSubtract(this.field.multiply(c,e),this.coefficients[b]);return e};this.addOrSubtract=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(this.Zero)return c;if(c.Zero)return this;var d=this.coefficients;c=c.coefficients;if(d.length>c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;h<e;h++)b[h]=c[h];for(h=e;h<c.length;h++)b[h]=n.addOrSubtract(d[h-e],c[h]);return new w(a,b)};this.multiply1=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";
|
||||
if(this.Zero||c.Zero)return this.field.Zero;var d=this.coefficients,b=d.length;c=c.coefficients;for(var e=c.length,a=Array(b+e-1),h=0;h<b;h++)for(var f=d[h],g=0;g<e;g++)a[h+g]=n.addOrSubtract(a[h+g],this.field.multiply(f,c[g]));return new w(this.field,a)};this.multiply2=function(c){if(0==c)return this.field.Zero;if(1==c)return this;for(var d=this.coefficients.length,b=Array(d),e=0;e<d;e++)b[e]=this.field.multiply(this.coefficients[e],c);return new w(this.field,b)};this.multiplyByMonomial=function(c,
|
||||
d){if(0>c)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a<e.length;a++)e[a]=0;for(a=0;a<b;a++)e[a]=this.field.multiply(this.coefficients[a],d);return new w(this.field,e)};this.divide=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(c.Zero)throw"Divide by 0";for(var d=this.field.Zero,b=this,e=c.getCoefficient(c.Degree),e=this.field.inverse(e);b.Degree>=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree,
|
||||
h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero});
|
||||
this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e<b.length;e++)b[e]=0;b[0]=c;return new w(this,b)};this.exp=function(d){return this.expTable[d]};this.log=function(d){if(0==d)throw"System.ArgumentException";return this.logTable[d]};this.inverse=function(d){if(0==d)throw"System.ArithmeticException";return this.expTable[255-this.logTable[d]]};this.multiply=function(d,c){return 0==
|
||||
d||0==c?0:1==d?c:1==c?d:this.expTable[(this.logTable[d]+this.logTable[c])%255]}}function u(a,b){return 0<=a?a>>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++};
|
||||
this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped=
|
||||
!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<<D)/7);e=Math.floor(b/2);return Math.abs(b-(a[0]<<D))<e&&Math.abs(b-
|
||||
(a[1]<<D))<e&&Math.abs(3*b-(a[2]<<D))<3*e&&Math.abs(b-(a[3]<<D))<e&&Math.abs(b-(a[4]<<D))<e};this.centerFromEnd=function(a,b){return b-a[4]-a[3]-a[2]/2};this.crossCheckVertical=function(a,b,e,d){for(var c=this.image,h=g.height,l=this.CrossCheckStateCount,m=a;0<=m&&c[b+m*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[b+m*g.width];)l[2]++,m++;if(m==h)return NaN;
|
||||
for(;m<h&&!c[b+m*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[b+m*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<=
|
||||
e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[m+b*g.width];)l[2]++,m++;if(m==h)return NaN;for(;m<h&&!c[m+b*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[m+b*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e),
|
||||
Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;h<c;h++){var l=this.possibleCenters[h];if(l.aboutEquals(a,b,e)){l.incrementCount();d=!0;break}}d||(e=new U(e,b,a),this.possibleCenters.push(e),null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(e));return!0}return!1};this.selectBestPatterns=function(){var a=this.possibleCenters.length;if(3>a)throw"Couldn't find enough finder patterns (found "+a+")";if(3<a){for(var b=0,e=0,d=0;d<a;d++)var c=
|
||||
this.possibleCenters[d].EstimatedModuleSize,b=b+c,e=e+c*c;var p=b/a;this.possibleCenters.sort(function(c,d){var b=Math.abs(d.EstimatedModuleSize-p),e=Math.abs(c.EstimatedModuleSize-p);return b<e?-1:b==e?0:1});a=Math.max(.2*p,Math.sqrt(e/a-p*p));for(d=this.possibleCenters.length-1;0<=d;d--)Math.abs(this.possibleCenters[d].EstimatedModuleSize-p)>a&&this.possibleCenters.splice(d,1)}3<this.possibleCenters.length&&this.possibleCenters.sort(function(c,d){return c.count>d.count?-1:c.count<d.count?1:0});
|
||||
return[this.possibleCenters[0],this.possibleCenters[1],this.possibleCenters[2]]};this.findRowSkip=function(){var a=this.possibleCenters.length;if(1>=a)return 0;for(var b=null,e=0;e<a;e++){var d=this.possibleCenters[e];if(d.Count>=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c<d;c++)a=this.possibleCenters[c],a.Count>=K&&(b++,e+=a.EstimatedModuleSize);
|
||||
if(3>b)return!1;for(var b=e/d,p=0,c=0;c<d;c++)a=this.possibleCenters[c],p+=Math.abs(a.EstimatedModuleSize-b);return p<=.05*e};this.findFinderPattern=function(a){var b;this.image=a;var e=g.height,d=g.width,c=Math.floor(3*e/(4*W));c<L&&(c=L);for(var h=!1,l=Array(5),m=c-1;m<e&&!h;m+=c){l[0]=0;l[1]=0;l[2]=0;l[3]=0;for(var f=b=l[4]=0;f<d;f++)if(a[f+m*g.width])1==(b&1)&&b++,l[b]++;else if(0==(b&1))if(4==b)if(this.foundPatternCross(l)){if(b=this.handlePossibleCenter(l,m,f))c=2,this.hasSkipped?h=this.haveMultiplyConfirmedCenters():
|
||||
(b=this.findRowSkip(),b>l[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f<d&&!a[f+m*g.width]);f--}b=0;l[0]=0;l[1]=0;l[2]=0;l[3]=0;l[4]=0}else l[0]=l[2],l[1]=l[3],l[2]=l[4],l[3]=1,l[4]=0,b=3;else l[++b]++;else l[b]++;this.foundPatternCross(l)&&(b=this.handlePossibleCenter(l,m,d))&&(c=l[0],this.hasSkipped&&(h=this.haveMultiplyConfirmedCenters()))}a=this.selectBestPatterns();g.orderBestPatterns(a);return new V(a)}}function M(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",
|
||||
function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return Math.floor(this.x)});this.__defineGetter__("Y",function(){return Math.floor(this.y)});this.incrementCount=function(){this.count++};this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters=
|
||||
[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN;
|
||||
for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;l<h&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l++;if(l==h||f[1]>b)return NaN;for(;l<h&&!e[d+l*g.width]&&f[2]<=b;)f[2]++,l++;return f[2]>b||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length,
|
||||
e=0;e<a;e++)if(this.possibleCenters[e].aboutEquals(c,d,b))return new M(b,d,c);b=new M(b,d,c);this.possibleCenters.push(b);null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(b)}return null};this.find=function(){for(var c,b=this.startX,h=this.height,f=b+d,l=e+(h>>1),p=[0,0,0],k=0;k<h;k++){var n=l+(0==(k&1)?k+1>>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A<f&&!a[A+g.width*n];)A++;for(c=0;A<f;){if(a[A+n*g.width])if(1==c)p[c]++;else if(2==c){if(this.foundPatternCross(p)&&
|
||||
(c=this.handlePossibleCenter(p,n,A),null!=c))return c;p[0]=p[2];p[1]=1;p[2]=0;c=1}else p[++c]++;else 1==c&&c++,p[c]++;A++}if(this.foundPatternCross(p)&&(c=this.handlePossibleCenter(p,n,f),null!=c))return c}if(0!=this.possibleCenters.length)return this.possibleCenters[0];throw"Couldn't find enough alignment patterns";}}function X(a,b,e){this.blockPointer=0;this.bitPointer=7;this.dataLength=0;this.blocks=a;this.numErrorCorrectionCode=e;9>=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<=
|
||||
b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b<this.bitPointer+1){var a=0;for(d=0;d<b;d++)a+=1<<d;a<<=this.bitPointer-b+1;d=(this.blocks[this.blockPointer]&a)>>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b<this.bitPointer+1+8){for(d=c=0;d<this.bitPointer+1;d++)c+=1<<d;d=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;d+=this.blocks[this.blockPointer]>>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer=
|
||||
8+this.bitPointer);return d}if(b<this.bitPointer+1+16){for(d=a=c=0;d<this.bitPointer+1;d++)c+=1<<d;c=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;var e=this.blocks[this.blockPointer]<<b-(this.bitPointer+1+8);this.blockPointer++;for(d=0;d<b-(this.bitPointer+1+8);d++)a+=1<<d;a<<=8-(b-(this.bitPointer+1+8));d=(this.blocks[this.blockPointer]&a)>>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0};
|
||||
this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1<b){var a=this.getNextBits(11);var e=a%45,c=c+d[Math.floor(a/45)],c=c+d[e];b-=2}else 1==b&&(a=this.getNextBits(6),c+=d[a],--b);while(0<
|
||||
b);return c};this.getFigureString=function(b){var c=0,d="";do 3<=b?(c=this.getNextBits(10),100>c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0<b);return d};this.get8bitByteArray=function(b){var c=[];do{var d=this.getNextBits(8);c.push(d);b--}while(0<b);return c};this.getKanjiString=function(b){var c="";do{var d=this.getNextBits(13);d=(d/192<<8)+d%192;c+=String.fromCharCode(40956>=d+33088?d+33088:d+49472);b--}while(0<
|
||||
b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0<b.length)break;else throw"Empty data block";if(1!=c&&2!=c&&4!=c&&8!=c&&7!=c)throw"Invalid mode: "+c+" in (block:"+this.blockPointer+" bit:"+this.bitPointer+")";if(7==c)this.parseECIValue();else{var a=this.getDataLength(c);
|
||||
if(1>a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 2:c=this.getRomanAndFigureString(a);a=Array(c.length);for(e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 4:c=this.get8bitByteArray(a);b.push(c);break;case 8:c=this.getKanjiString(a),b.push(c)}}}while(1);return b})}var F={checkAndNudgePoints:function(a,b){for(var e,d,c=g.width,h=g.height,f=!0,m=0;m<b.length&&f;m+=2){d=
|
||||
Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h<b;h++){for(var f=
|
||||
c.length,m=h+.5,k=0;k<f;k+=2)c[k]=(k>>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k<f;k+=2)a[Math.floor(c[k])+g.width*Math.floor(c[k+1])]&&d.set_Renamed(k>>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,
|
||||
84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18,
|
||||
new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22,
|
||||
new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))),
|
||||
new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5,
|
||||
87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)),
|
||||
new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26,
|
||||
new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30,
|
||||
new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30,
|
||||
new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10,
|
||||
48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46),
|
||||
new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14,
|
||||
122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138,
|
||||
166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40<a)throw"ArgumentException";return k.VERSIONS[a-1]};k.getProvisionalVersionForDimension=function(a){if(1!=a%4)throw"Error getProvisionalVersionForDimension";
|
||||
try{return k.getVersionForNumber(a-17>>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d<k.VERSION_DECODE_INFO.length;d++){var c=k.VERSION_DECODE_INFO[d];if(c==a)return this.getVersionForNumber(d+7);c=r.numBitsDiffering(a,c);c<b&&(e=d+7,b=c)}return 3>=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k,
|
||||
q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427,
|
||||
9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!=
|
||||
b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d<N.length;d++){var c=N[d],h=c[0];if(h==a)return new r(c[1]);h=this.numBitsDiffering(a,h);h<b&&(e=c[1],b=h)}return 3>=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException";
|
||||
var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;b<c.length;b++)e+=c[b].Count;e=Array(e);for(var h=0,f=0;f<c.length;f++){var g=c[f];for(b=0;b<g.Count;b++){var k=g.DataCodewords,q=d.ECCodewordsPerBlock+k;e[h++]=new G(k,Array(q))}}b=e[0].codewords.length;for(c=e.length-1;0<=c&&e[c].codewords.length!=b;)c--;c++;d=b-d.ECCodewordsPerBlock;for(b=g=0;b<d;b++)for(f=0;f<h;f++)e[f].codewords[b]=a[g++];for(f=c;f<h;f++)e[f].codewords[d]=a[g++];k=e[0].codewords.length;for(b=d;b<k;b++)for(f=0;f<
|
||||
h;f++)e[f].codewords[f<c?b:b+1]=a[g++];return e};var H={forReference:function(a){if(0>a||7<a)throw"System.ArgumentException";return H.DATA_MASKS[a]}};H.DATA_MASKS=[new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a&1)}},
|
||||
new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==b%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b)%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(u(a,
|
||||
1)+b/3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==(e&1)+e%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==((e&1)+e%3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,
|
||||
e)};this.isMasked=function(a,b){return 0==((a+b&1)+a*b%3&1)}}];n.QR_CODE_FIELD=new n(285);n.DATA_MATRIX_FIELD=new n(301);n.addOrSubtract=function(a,b){return a^b};var E={};E.rsDecoder=new function(a){this.field=a;this.decode=function(a,e){for(var b=new w(this.field,a),c=Array(e),f=0;f<c.length;f++)c[f]=0;for(var h=!0,f=0;f<e;f++){var g=b.evaluateAt(this.field.exp(f));c[c.length-1-f]=g;0!=g&&(h=!1)}if(!h)for(f=new w(this.field,c),b=this.runEuclideanAlgorithm(this.field.buildMonomial(e,1),f,e),f=b[1],
|
||||
b=this.findErrorLocations(b[0]),c=this.findErrorMagnitudes(f,b,!1),f=0;f<b.length;f++){h=a.length-1-this.field.log(b[f]);if(0>h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree<e.Degree){var b=a;a=e;e=b}for(var b=this.field.One,f=this.field.Zero,h=this.field.Zero,g=this.field.One;e.Degree>=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree);
|
||||
for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1==
|
||||
b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&c<b;f++)0==a.evaluateAt(f)&&(d[c]=this.field.inverse(f),c++);if(c!=b)throw"Error locator degree does not match number of roots";return d};this.findErrorMagnitudes=function(a,e,d){for(var b=e.length,f=Array(b),h=0;h<b;h++){for(var g=this.field.inverse(e[h]),k=1,q=0;q<b;q++)h!=q&&(k=this.field.multiply(k,n.addOrSubtract(1,this.field.multiply(e[q],g))));f[h]=this.field.multiply(a.evaluateAt(g),this.field.inverse(k));d&&(f[h]=this.field.multiply(f[h],
|
||||
g))}return f}}(n.QR_CODE_FIELD);E.correctErrors=function(a,b){for(var e=a.length,d=Array(e),c=0;c<e;c++)d[c]=a[c]&255;e=a.length-b;try{E.rsDecoder.decode(d,e)}catch(p){throw p;}for(c=0;c<b;c++)a[c]=d[c]};E.decode=function(a){var b=new T(a);a=b.readVersion();for(var e=b.readFormatInformation().ErrorCorrectionLevel,b=b.readCodewords(),b=G.getDataBlocks(b,a,e),d=0,c=0;c<b.length;c++)d+=b[c].NumDataCodewords;for(var d=Array(d),f=0,h=0;h<b.length;h++){var c=b[h],g=c.Codewords,k=c.NumDataCodewords;E.correctErrors(g,
|
||||
k);for(c=0;c<k;c++)d[f++]=g[c]}return new X(d,a.VersionNumber,e.Bits)};var g={imagedata:null,width:0,height:0,qrCodeSymbol:null,debug:!1,maxImgSize:1048576,sizeOfDataLengthInfo:[[10,9,8,8],[12,11,16,10],[14,13,16,12]],callback:null,vidSuccess:function(a){g.localstream=a;g.webkit?g.video.src=window.webkitURL.createObjectURL(a):g.moz?(g.video.mozSrcObject=a,g.video.play()):g.video.src=a;g.gUM=!0;g.canvas_qr2=document.createElement("canvas");g.canvas_qr2.id="qr-canvas";g.qrcontext2=g.canvas_qr2.getContext("2d");
|
||||
g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;setTimeout(g.captureToCanvas,500)},vidError:function(a){g.gUM=!1},captureToCanvas:function(){if(g.gUM)try{if(0==g.video.videoWidth)setTimeout(g.captureToCanvas,500);else{g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;g.qrcontext2.drawImage(g.video,0,0);try{g.decode()}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}}}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}},setWebcam:function(a){var b=
|
||||
navigator;g.video=document.getElementById(a);var e=!0;if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices)try{navigator.mediaDevices.enumerateDevices().then(function(a){a.forEach(function(a){console.log("deb1");"videoinput"===a.kind&&-1<a.label.toLowerCase().search("back")&&(e=[{sourceId:a.deviceId}]);console.log(a.kind+": "+a.label+" id = "+a.deviceId)})})}catch(d){console.log(d)}else console.log("no navigator.mediaDevices.enumerateDevices");b.getUserMedia?b.getUserMedia({video:e,
|
||||
audio:!1},g.vidSuccess,g.vidError):b.webkitGetUserMedia?(g.webkit=!0,b.webkitGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError)):b.mozGetUserMedia&&(g.moz=!0,b.mozGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError))},decode:function(a){if(0==arguments.length){if(g.canvas_qr2){var b=g.canvas_qr2;var e=g.qrcontext2}else b=document.getElementById("qr-canvas"),e=b.getContext("2d");g.width=b.width;g.height=b.height;g.imagedata=e.getImageData(0,0,g.width,g.height);g.result=g.process(e);null!=
|
||||
g.callback&&g.callback(g.result);return g.result}var d=new Image;d.crossOrigin="Anonymous";d.onload=function(){var a=document.getElementById("out-canvas");null!=a&&(a=a.getContext("2d"),a.clearRect(0,0,320,240),a.drawImage(d,0,0,320,240));var a=document.createElement("canvas"),b=a.getContext("2d"),e=d.height,f=d.width;d.width*d.height>g.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata=
|
||||
b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)},
|
||||
decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;d<g.height;d++)for(var c=0;c<g.width;c++){var f=4*c+d*g.width*4;g.imagedata.data[f]=0;g.imagedata.data[f+1]=0;g.imagedata.data[f+2]=e[c+d*g.width]?255:0}a.putImageData(g.imagedata,0,0)}e=
|
||||
(new Q(e)).detect();if(g.debug){for(d=0;d<e.bits.Height;d++)for(c=0;c<e.bits.Width;c++)f=8*c+2*d*g.width*4,g.imagedata.data[f]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+1]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+2]=e.bits.get_Renamed(c,d)?255:0;a.putImageData(g.imagedata,0,0)}f=E.decode(e.bits).DataByte;a="";for(d=0;d<f.length;d++)for(c=0;c<f[d].length;c++)a+=String.fromCharCode(f[d][c]);f=(new Date).getTime();console.log(f-b);return g.decode_utf8(a)},getPixel:function(a,b){if(g.width<
|
||||
a)throw"point error";if(g.height<b)throw"point error";var e=4*a+b*g.width*4;return(33*g.imagedata.data[e]+34*g.imagedata.data[e+1]+33*g.imagedata.data[e+2])/100},binarize:function(a){for(var b=Array(g.width*g.height),e=0;e<g.height;e++)for(var d=0;d<g.width;d++){var c=g.getPixel(d,e);b[d+e*g.width]=c<=a?!0:!1}return b},getMiddleBrightnessPerArea:function(a){for(var b=Math.floor(g.width/4),e=Math.floor(g.height/4),d=Array(4),c=0;4>c;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f=
|
||||
0;4>f;f++){d[f][c][0]=255;for(var h=0;h<e;h++)for(var m=0;m<b;m++){var k=a[b*f+m+(e*c+h)*g.width];k<d[f][c][0]&&(d[f][c][0]=k);k>d[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h<e;h++)for(var m=0;m<e;m++)for(var k=
|
||||
0;k<c;k++)for(var n=0;n<d;n++)f[d*m+n+(c*h+k)*g.width]=a[d*m+n+(c*h+k)*g.width]<b[m][h]?!0:!1;return f},grayscale:function(){for(var a=new ArrayBuffer(g.width*g.height),a=new Uint8Array(a),b=0;b<g.height;b++)for(var e=0;e<g.width;e++){var d=g.getPixel(e,b);a[e+b*g.width]=d}return a}},L=3,W=57,D=8,K=2;g.orderBestPatterns=function(a){function b(a,b){var c=a.X-b.X,d=a.Y-b.Y;return Math.sqrt(c*c+d*d)}var e=b(a[0],a[1]),d=b(a[1],a[2]),c=b(a[0],a[2]);d>=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1],
|
||||
e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}();
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -5,3 +5,5 @@
|
|||
0.05: Clock does not start if app Languages is not installed
|
||||
0.06: Improve accuracy
|
||||
0.07: Update to use Bangle.setUI instead of setWatch
|
||||
0.08: Use theme colors, Layout library
|
||||
0.09: Fix time/date disappearing after fullscreen notification
|
||||
|
|
|
@ -3,167 +3,106 @@
|
|||
* A simple digital clock showing seconds as a bar
|
||||
**/
|
||||
// Check settings for what type our clock should be
|
||||
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
|
||||
let locale = require('locale')
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
let locale = require("locale");
|
||||
{ // add some more info to locale
|
||||
let date = new Date()
|
||||
date.setFullYear(1111)
|
||||
date.setMonth(1, 3) // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true)
|
||||
locale.dayFirst = /3.*2/.test(localized)
|
||||
let date = new Date();
|
||||
date.setFullYear(1111);
|
||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true);
|
||||
locale.dayFirst = /3.*2/.test(localized);
|
||||
|
||||
locale.hasMeridian = false
|
||||
if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date) !== '')
|
||||
locale.hasMeridian = false;
|
||||
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||
}
|
||||
|
||||
}
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
Bangle.loadWidgets();
|
||||
function renderBar(l) {
|
||||
if (!this.fraction) {
|
||||
// zero-size fillRect stills draws one line of pixels, we don't want that
|
||||
return;
|
||||
}
|
||||
const width = this.fraction*l.w;
|
||||
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
||||
}
|
||||
|
||||
// hardcoded "settings"
|
||||
const settings = {
|
||||
time: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
ampm: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: 2,
|
||||
const Layout = require("Layout");
|
||||
const layout = new Layout({
|
||||
type: "v", c: [
|
||||
{
|
||||
type: "h", c: [
|
||||
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
|
||||
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
|
||||
],
|
||||
},
|
||||
},
|
||||
date: {
|
||||
color: -1,
|
||||
font: 'Vector',
|
||||
size: 20,
|
||||
middle: screen.height - 20, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
bar: {
|
||||
color: -1,
|
||||
top: 155, // just below time
|
||||
thickness: 6, // matches 24h time "pixel" size
|
||||
},
|
||||
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
||||
{height: 40},
|
||||
{id: "date", type: "txt", font: "10%", valign: 1},
|
||||
],
|
||||
}, {lazy: true});
|
||||
// adjustments based on screen size and whether we display am/pm
|
||||
let thickness; // bar thickness, same as time font "pixel block" size
|
||||
if (is12Hour) {
|
||||
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
|
||||
thickness = Math.floor((g.getWidth()-24)/(5*6));
|
||||
} else {
|
||||
layout.ampm.label = "";
|
||||
thickness = Math.floor(g.getWidth()/(5*6));
|
||||
}
|
||||
layout.bar.height = thickness+1;
|
||||
layout.time.font = "6x8:"+thickness;
|
||||
layout.update();
|
||||
|
||||
const SECONDS_PER_MINUTE = 60
|
||||
|
||||
const timeText = function (date) {
|
||||
function timeText(date) {
|
||||
if (!is12Hour) {
|
||||
return locale.time(date, true)
|
||||
return locale.time(date, true);
|
||||
}
|
||||
const date12 = new Date(date.getTime())
|
||||
const hours = date12.getHours()
|
||||
if (hours === 0) {
|
||||
date12.setHours(12)
|
||||
} else if (hours > 12) {
|
||||
date12.setHours(hours - 12)
|
||||
const date12 = new Date(date.getTime());
|
||||
const hours = date12.getHours();
|
||||
if (hours===0) {
|
||||
date12.setHours(12);
|
||||
} else if (hours>12) {
|
||||
date12.setHours(hours-12);
|
||||
}
|
||||
return locale.time(date12, true)
|
||||
return locale.time(date12, true);
|
||||
}
|
||||
const ampmText = function (date) {
|
||||
return is12Hour ? locale.meridian(date) : ''
|
||||
function ampmText(date) {
|
||||
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
|
||||
}
|
||||
|
||||
const dateText = function (date) {
|
||||
function dateText(date) {
|
||||
const dayName = locale.dow(date, true),
|
||||
month = locale.month(date, true),
|
||||
day = date.getDate()
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
|
||||
return `${dayName} ${dayMonth}`
|
||||
day = date.getDate();
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
||||
return `${dayName} ${dayMonth}`;
|
||||
}
|
||||
|
||||
const drawDateTime = function (date) {
|
||||
const t = settings.time
|
||||
g.setColor(t.color)
|
||||
g.setFont(t.font, t.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(timeText(date), t.center, t.middle, true)
|
||||
if (is12Hour && locale.hasMeridian) {
|
||||
const a = settings.time.ampm
|
||||
g.setColor(a.color)
|
||||
g.setFont(a.font, a.size)
|
||||
g.setFontAlign(1, -1) // right top
|
||||
// at right edge of screen, aligned with time bottom
|
||||
const left = screen.width - a.size * 2,
|
||||
top = t.middle + t.size - a.size
|
||||
g.drawString(ampmText(date), left, top, true)
|
||||
draw = function draw(force) {
|
||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
||||
const date = new Date();
|
||||
layout.time.label = timeText(date);
|
||||
layout.ampm.label = ampmText(date);
|
||||
layout.date.label = dateText(date);
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||
if (force) {
|
||||
Bangle.drawWidgets();
|
||||
layout.forgetLazyState();
|
||||
}
|
||||
layout.render();
|
||||
// schedule update at start of next second
|
||||
const millis = date.getMilliseconds();
|
||||
setTimeout(draw, 1000-millis);
|
||||
};
|
||||
|
||||
const d = settings.date
|
||||
g.setColor(d.color)
|
||||
g.setFont(d.font, d.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(dateText(date), d.center, d.middle, true)
|
||||
}
|
||||
|
||||
const drawBar = function (date) {
|
||||
const b = settings.bar
|
||||
const seconds = date.getSeconds()
|
||||
if (seconds === 0) {
|
||||
// zero-size rect stills draws one line of pixels, we don't want that
|
||||
return
|
||||
}
|
||||
const fraction = seconds / SECONDS_PER_MINUTE,
|
||||
width = fraction * screen.width
|
||||
g.setColor(b.color)
|
||||
g.fillRect(0, b.top, width, b.top + b.thickness)
|
||||
}
|
||||
|
||||
const clearScreen = function () {
|
||||
g.setColor(0)
|
||||
const timeTop = settings.time.middle - (settings.time.size * 4)
|
||||
g.fillRect(0, timeTop, screen.width, screen.height)
|
||||
}
|
||||
|
||||
let lastSeconds, tTick
|
||||
const tick = function () {
|
||||
g.reset()
|
||||
const date = new Date()
|
||||
const seconds = date.getSeconds()
|
||||
if (lastSeconds > seconds) {
|
||||
// new minute
|
||||
clearScreen()
|
||||
drawDateTime(date)
|
||||
}
|
||||
// the bar only gets larger, so drawing on top of the previous one is fine
|
||||
drawBar(date)
|
||||
lastSeconds = seconds
|
||||
// schedule next update
|
||||
const millis = date.getMilliseconds()
|
||||
tTick = setTimeout(tick, 1000-millis)
|
||||
}
|
||||
|
||||
const start = function () {
|
||||
lastSeconds = 99 // force redraw
|
||||
tick()
|
||||
}
|
||||
const stop = function () {
|
||||
if (tTick) {
|
||||
clearTimeout(tTick)
|
||||
tTick = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
g.clear()
|
||||
Bangle.loadWidgets()
|
||||
Bangle.drawWidgets()
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
Bangle.on("lcdPower", function(on) {
|
||||
if (on) {
|
||||
start()
|
||||
} else {
|
||||
stop()
|
||||
draw(true);
|
||||
}
|
||||
})
|
||||
start()
|
||||
});
|
||||
g.reset().clear();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
|
@ -1,3 +1,6 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
||||
0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal.
|
||||
0.04: Update to use Bangle.setUI instead of setWatch
|
||||
0.05: Update *on* the minute rather than every 15 secs
|
||||
Now show widgets
|
||||
Make compatible with themes, and Bangle.js 2
|
||||
|
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,7 +1,7 @@
|
|||
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
||||
// https://github.com/eska-muc/BangleApps
|
||||
const fields = [4, 4, 11, 4];
|
||||
const offset = 20;
|
||||
const offset = 24;
|
||||
const width = g.getWidth() - 2 * offset;
|
||||
const height = g.getHeight() - 2 * offset;
|
||||
const rowHeight = height / 4;
|
||||
|
@ -10,11 +10,23 @@ var show_date = false;
|
|||
var show_time = false;
|
||||
var yy = 0;
|
||||
|
||||
rowlights = [];
|
||||
time_digit = [];
|
||||
var rowlights = [];
|
||||
var time_digit = [];
|
||||
|
||||
function drawBerlinClock() {
|
||||
g.clear();
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
var now = new Date();
|
||||
|
||||
// show date below the clock
|
||||
|
@ -24,8 +36,7 @@ function drawBerlinClock() {
|
|||
var day = now.getDate();
|
||||
var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
|
||||
var strWidth = g.stringWidth(dateString);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1,-1);
|
||||
g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4);
|
||||
}
|
||||
|
||||
|
@ -50,8 +61,7 @@ function drawBerlinClock() {
|
|||
x2 = (col + 1) * boxWidth + offset;
|
||||
y2 = (row + 1) * rowHeight + offset;
|
||||
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawRect(x1, y1, x2, y2);
|
||||
g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2);
|
||||
if (col < rowlights[row]) {
|
||||
if (row === 2) {
|
||||
if (((col + 1) % 3) === 0) {
|
||||
|
@ -65,46 +75,42 @@ function drawBerlinClock() {
|
|||
g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
|
||||
}
|
||||
if (row == 3 && show_time) {
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg).setFontAlign(0,0);
|
||||
g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function toggleDate() {
|
||||
show_date = ! show_date;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
function toggleTime() {
|
||||
show_time = ! show_time;
|
||||
drawBerlinClock();
|
||||
draw();
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
g.clear();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
Bangle.drawWidgets();
|
||||
// call your app function here
|
||||
drawBerlinClock();
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// refesh every 15 sec
|
||||
setInterval(drawBerlinClock, 15E3);
|
||||
// Show launcher when button pressed, handle up/down
|
||||
Bangle.setUI("clockupdown", dir=> {
|
||||
if (dir<0) toggleTime();
|
||||
if (dir>0) toggleDate();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawBerlinClock();
|
||||
if (BTN3) {
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"});
|
||||
// Toggle date display, when BTN3 is pressed
|
||||
setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"});
|
||||
}
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
draw();
|
||||
|
|
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
|
@ -0,0 +1,4 @@
|
|||
0.01: start of development
|
||||
0.02: first running version for BangleJs2
|
||||
0.03: corrected icon, added screen shot, extended description
|
||||
0.04: corrected format of background image (raw binary)
|
|
@ -0,0 +1,47 @@
|
|||
# TheBinWatch
|
||||
|
||||
Binary watch to train Your brain
|
||||
Inspired by the LCD wrist watch from TecRAL from 1989
|
||||
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
- swipe to left or right to change displayed text (date, time, ...)
|
||||
- currently only available for BangeJs2
|
||||
- Widgets will not be shown
|
||||
- If bluetooth connection is not established an icon will show up
|
||||
|
||||
## How it works
|
||||
Binary means that every digit can represent 2 states: 0 or 1, displayed by a black bar.
|
||||
|
||||
The principle is the same like in out well known and daily used decimal system with values from 0 to 9:
|
||||
|
||||
We start from the most right position with the least significant bit (binary digit) which can have the value 0 or 1
|
||||
The 2nd bit from the right can have the value 0 or 2 (sum of all bits to the right set to 1 plus 1).
|
||||
This principle is valid for all the remaining bits.
|
||||
|
||||
Mathematically spoken: the value of a digit is the base number of the system (10 for decimal or 2 for binary)
|
||||
to the power of the position (from the right, starting with 0).
|
||||
That means in numbers: 2^5 = 32, 2^4 = 16, 2^3 = 8, 2^2 = 4, 2^1 = 2, 2^0 = 1
|
||||
|
||||
The upper row represents the hours with 4 bit (2^4 = 16 possible values in total, 12 are used: 1 to 12),
|
||||
the 2nd row represents the minutes with 6 bit (2^6 = 64 possible values in total, 60 are used: 0 to 59).
|
||||
Same holds for the thrid row: 0-59 seconds
|
||||
|
||||
To read the values of a row we summ up the vaules of set bits (black bars).
|
||||
E.g. the picture above, 3rd row (seconds):
|
||||
101001
|
||||
is 1 * 32 + 0 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 1 * 1
|
||||
is (only the '1' bit): 32 + 8 + 1 = 41
|
||||
|
||||
for the minutes we do the same: 32 + 1 = 33
|
||||
and the hours: 8 + 2 = 10
|
||||
|
||||
So the time is 10:33:41 (that's all)
|
||||
|
||||
## TRAIN YOUR BRAIN
|
||||
|
||||
Remark: more infos about the original watch including manual can be found here:
|
||||
https://timeartpiece.com/watches/tech-ral-binary
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcCgEBkmSpICKCwQRRhMn/4AK+VACIU4A4PAz+27dt20ECI1IgEDCIOT+wRB2EkCIX+BwMCpE/8f+gmSvwRB2Mkz///v/5IRBpwRHwIRC5PzCIMSCIXwMQNP7dshMkyf/p+G/MgiV+CIPxCJFM8gRByf+CIIvBRIP7sCMCv/h8//C4P+g6ABCIdiCIVP/M///kFIPAj6iLCIYAOCPH4ibUC2zABdgW/8ARFUgILB2/8fwf/kB3BPobUD3/kz4pCTwMDCIrCBCIWTCINv/IREfAVJDoYpCv/JkmAv4RCYQYRM+ARCn4vCHYX+bQOQh4RBfAYRJyUBCI3/F4IFB/4RGdP4RHwDmC7/gmzaC//tbQWBR4UbfAWQgzIDfwVsR4QRCfAIRM/0DCIWSgDaDz4RBsDXDCIIdByVAfAb+CCIf/4AREjYRFgZ9D/D4DpEDfAT+Cj4REhoRJ7ARE/8PfAVJgbmDp/YWZHgv6zIkkSBYWB44sB/4CB/AREkESp4EBx4RBx0/CIPACAf5kECCIQAHPQIAB5MAgVJEYs4AwIjECIMACI0ACIv+pARCn5rDvwFDGoQRDhILDABHyoARBgKeCARQQBCKIA=="))
|
|
@ -0,0 +1,379 @@
|
|||
/***************************************************
|
||||
* BINARY WATCH
|
||||
* for Bangle 1 / 2
|
||||
* inspired by RAL tec binary wrist watch
|
||||
*
|
||||
* TODO:
|
||||
* - vibrate on full hour
|
||||
* -
|
||||
****************************************************/
|
||||
|
||||
/* reuqirements */
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
require("Font5x7Numeric7Seg").add(Graphics);
|
||||
|
||||
/* constants and definitions */
|
||||
|
||||
/* Bangle 2: 176 x 176 */
|
||||
|
||||
/* month images */
|
||||
|
||||
var month = [
|
||||
/* JAN */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AAMf/0D8AFBkM/9EvwMAgcM/3B30YgE4uEOh354EB4eAuFz90Ah0cgeDx9wgFw8Ecjk7wEDw8A8AIBgEcnEHg4IBgFh4EYnEDHYMF/8AwBID/BODgN/4EgAoI0BgODwExGgkDzg0FAII0D88A8PAnAIBAIMOgPBBAPAiBpCgPAQIOAmFwg0P/B5BwcAiE/JYYAHA"))},
|
||||
/* FEB */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("v/8n/+g/+if/hP/wM/8c/4Of8Ez/FwgE4gEHgFOAYUA8Ed4ADBgEcge4AYMAgeAu4DCgFwhwQBEIMOgPcAYMAgPAjN/4G/8EX/kf/EP/kB/+F/8C/+Ar/xGQkBGTE7wADBMIMHMotMgEGv+A7/hEYOf/EH/hvBh6FBIIKYFA"))},
|
||||
/* MAR */ {width : 52, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+gf/hP/gV/6FP/HP8kz/cGv8OgHDwFwuEE8McnEHg8A905BgcO8ecBiM4BgMwuEGoeEi/8gX/wE4gH/4Ef/AMFx0QDIcA8BADnEOgIzCufABgk+Bglx+AMEh+OBgdwvnghk4gcGgfsgFDgEQoEeSgvg"))},
|
||||
/* APR */ {width : 52, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/4gf/hP/oV/4FP/HP9kz/EGv8OgPDwEguEE8EcnEHg8A9wMCuFwhwMTgAMBmFwg1f+EX/kC/+D/8A//AJIIMFxwZCgFwgAmCgEHnBNDgFz4AMEnwMEuPwBgkPxwMDuF88EMBgMGgfsgFDRgNAjyUF8A="))},
|
||||
/* MAY */ {width : 52, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+AoMJ/8Cv/QAwPP8kz/cEgEugHDwFwsEG8McnEHg8A905BgVwh3jzgMRnAMBmAMBoeEi/8BgNgnEA//Ah/4BgcB/+OiAZCBgPgIARTB90BGYUAhwMahk4gYMBpkAocAiEP+CSDIAOAAwYMB"))},
|
||||
/* JUN */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AAcD8ADBkMAhEvwIJBhkA4O+jEAnFwh0O/PAgPDwFwufugEOjkDwePuEAuHgjkcneAgeHgHgBAMAjk4g8HBAMAsPAjE4gY7BggCBwBPLkACBGgMBweAmI0EgecGgoBBGgfngHh4E4BAIBBh0B4IIB4EQmEEBAPA/0An5qBg0P/ED/xNBiAKBh6PCAAw="))},
|
||||
/* JUL */ {width : 53, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("AA8hgEImAFBgcMgHB4AGBnFwh0OAoMB4eAuFwAwMOjkDweAAwNw8EcjggCw8A8HgAwMcnEHg40CsPAjE4AwUEAQIgCABMgGgcBGgMBGgo/BGggKBGgYBB8PAnA0BBQMOgJpC4EQmEENIX+gE/wFn/EP/ED/0Cv/gBQMP8EP/5QGA"))},
|
||||
/* AUG */ {width : 52, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("n/8AocE/+gAgMP/1n+0QgGA//HgHhwEYl/wuEOjkDw8Ag4MB4E4uEABilhBgcv/EcgOCgEB/+AwBBB/AMBAgMCj/ngFgAwNw/wmCgImBBgIzDhwzFBikGhkBgUAs0AkEf4EH+A3Bgf+gBLBAwIMD"))},
|
||||
/* SEP */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("h/4j//g/+gf/wn/4M/8ABB5/wmf4mEAjkAg8Ap0AgeAgHgjvAgFwBwMD3EAhwOCu8AgIOCh3ggE4BwMB7gjCBwMYv/Ar/wi/8j/8IYMB/+BIYIODDwIyCLIMHGQYGB8JBDB4IyCAoMDw5BDB4JBDgEEMoZ6Cn/A8A6B8FP/kYgEf/EH/4eCA"))},
|
||||
/* OCT */ {width : 50, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("h/gg/+j//w/8gf/h//+H+gF/wP//OAkHADAXgjlwAoU4g8cAgMYh0B44pCgeAuIYBgfADAnwnEDDAUcghCDgRMIsACBkAYFGKZKDngYFgJjBwAYCPgX4DAMHPgQYBgB8C8EGgAA="))},
|
||||
/* NOV */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("vkAgf4AoMX4GA/+ABIN8mEP8EggP350MgMGgF+vvDwFw8Ef4+4uEOjgiBu8OgIOBv8A8PAnFwEQMcnEHBwP8gOHgFh4EdHYNAgEQgJLFggFEhPAjFwg0cg4jDGQPnGQk8GQkPI4IyB8PDKwYOB+BWBMoMHnkOgHAn+A98BwEIh/4jnAHgX+gaGBAAcggAA=="))},
|
||||
/* DEC */ {width : 49, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/gj//gP/5/4iYFC2f4hn/CAOcgMHgEBwEOgPDwEB4AJB8PAgHggeAuHggFwBoM4uEAnANBjgDBjgNBgwDBh0AiEAgowBAAQ6BwEAggFBv/BwAwBsIwWhwwDnEHAYIiBjhhDgEN/0Dn/Aj/hO4M/+Ef/JABv/8g/+A=="))},
|
||||
/* MAI */ {width : 44, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("v/4j/+gEJ/8Cv/QgnP8kz/cA50A4eAuEc8McnEHgPOnIKD8ecBR04BQMwhlDwkX/kAoE4gH/4EABQlOiAVD8A2EgIrDBS0MnEDgHMGQMAiEEPwo="))},
|
||||
/* OKT */ {width : 51, height : 24, bpp : 1, buffer : require("heatshrink").decompress(atob("g/wAQMP//B/8DgPh//8j/AuF8n//jECh0fDAUA8PH4AGB8EcnIhBsEcgeHvkAj0DwFw98AgYjBh0dDAN4h0A4eAEgQDBl/4gFAE4MD/5OE3/ggIyBhk4gcAuAyCBIIyDIIIyDAgOAGQMBGQNwh8B4E4BwMB8BlCBIM8gF/AgMYg+Aj/wmA3B+EB/hBChiYGA"))},
|
||||
/* DEZ */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("n/wh//w//xP/gV/8F//Of4Fn/EH/04gUODAUHgHh4AFBnHgjk4BYUcgeHAoMB8eAuHgAwN4uEOjgFBh4jB4eAgED4ADBl/4gFwB4MD/4DBgQCB3/gC4PghgyBgPAGQl4gYyDjwgBGQQrBh0BGQVwDQM4F4MMLIJlEg3/gOfPAPgn/gk/+j/+h/8IoPh//gA="))}
|
||||
];
|
||||
|
||||
var imgSquid = {width : 88, height : 26, bpp : 1, buffer : require("heatshrink").decompress(atob("gE/AYUYgEH////0B//gBQM8BQgDB/AKHh/A/gKBvwKBAgMOj8AnwKHBAIMBgH/BQgmCAoPnBQl4AoOAgPnwAKDuEAgYKB4YKIgfD4AKDMAMB4EDwIKIg+B8AKIgAKIh8A+AKHh0AuAKHj0AvBMG4EcgE4K458Bnh4HnEAjiOHBwMeBQpKBEgMOXQ/wBwIKDaAZQBg4KDcwT0BAAOHfgoKHgE/wDaBAAL8DA="))};
|
||||
|
||||
var imgNoBT = {width : 20, height : 20, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("///8mSpM/AoP/yUT/8yuYGB5AMB/1MyYUBkmT/P85MP+USBwOT8mQ/8JBwXyoVnyGSv8//Mhk14pMn//8BYNMwmSp/+pFJkgyBDoMkkgODpOSuQOE5M/KgIOCsmfz/JknPhMyof5n+Ss/wzMhn4OBk1+smQLoWTn/mHAM/+VJz4KBwhZBEYJ/CkM8yZVBAAQxBCgP/A="))};
|
||||
|
||||
const V2_X_STEP = 26;
|
||||
const V2_Y_STEP = 34;
|
||||
|
||||
const V2_TIME_Y_OFFSET = 8;
|
||||
const V2_HX = 36;
|
||||
const V2_HY = 0 + V2_TIME_Y_OFFSET;
|
||||
const V2_MX = 10;
|
||||
const V2_MY = 51 + V2_TIME_Y_OFFSET;
|
||||
const V2_SX = 10;
|
||||
const V2_SY = 95 + V2_TIME_Y_OFFSET;
|
||||
const V2_BT_X = 137; /* 145, 35 */
|
||||
const V2_BT_Y = 20;
|
||||
const V2_DX = 100;
|
||||
const V2_DY = 141;
|
||||
|
||||
const V2_BAT_POS_X = 21;
|
||||
const V2_BAT_POS_Y = 40;
|
||||
const V2_BAT_SIZE_X = 13;
|
||||
const V2_BAT_SIZE_Y = 2;
|
||||
|
||||
const V2_SCREEN_SIZE_X = 176;
|
||||
const V2_SCREEN_SIZE_Y = 176;
|
||||
const V2_BACKGROUND_IMAGE = "binwatch.bg176.img";
|
||||
const V2_BG_COLOR = 0;
|
||||
const V2_FG_COLOR = 1;
|
||||
|
||||
/* Bangle 1: 240 x 240 */
|
||||
|
||||
const V1_X_STEP = 35;
|
||||
const V1_Y_STEP = 46;
|
||||
|
||||
const V1_TIME_Y_OFFSET = 41;
|
||||
const V1_HX = 48;
|
||||
const V1_HY = 0 + V1_TIME_Y_OFFSET;
|
||||
const V1_MX = 14;
|
||||
const V1_MY = 55 + V1_TIME_Y_OFFSET;
|
||||
const V1_SX = 14;
|
||||
const V1_SY = 110 + V1_TIME_Y_OFFSET;
|
||||
const V1_BT_X = 41;
|
||||
const V1_BT_Y = 14;
|
||||
//var BT_X = 20, BT_Y = 14;
|
||||
const V1_DX = 160;
|
||||
const V1_DY = 205;
|
||||
|
||||
const V1_BAT_POS_X = 175;
|
||||
const V1_BAT_POS_Y = 21;
|
||||
const V1_BAT_SIZE_X = 3;
|
||||
const V1_BAT_SIZE_Y = 5;
|
||||
const V1_SCREEN_SIZE_X = 240;
|
||||
const V1_SCREEN_SIZE_Y = 240;
|
||||
const V1_BACKGROUND_IMAGE = "binwatch.bg240.img";
|
||||
const V1_BG_COLOR = 1;
|
||||
const V1_FG_COLOR = 0;
|
||||
|
||||
/* runtime settings */
|
||||
|
||||
var x_step = 0;
|
||||
var y_step = 0;
|
||||
|
||||
var time_y_offset = 0;
|
||||
var hx = 0, hy = 0;
|
||||
var mx = 0, my = 0;
|
||||
var sx = 0, sy = 0;
|
||||
var bt_x = 0, bt_y = 0;
|
||||
var dx = 0, dy = 0;
|
||||
|
||||
var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y;
|
||||
var backgroundImage = "";
|
||||
var screen_size_x = 0;
|
||||
var screen_size_y = 0;
|
||||
var bg_color = 0;
|
||||
var fg_color = 1;
|
||||
|
||||
/* global variables */
|
||||
|
||||
var showDateTime = 2; /* show noting, time or date */
|
||||
var cg;
|
||||
var cgimg;
|
||||
|
||||
/* local functions */
|
||||
|
||||
/**
|
||||
* function drawSquare(...)
|
||||
*
|
||||
* go through all bits and draw a square if a bit
|
||||
* is set. So we get the binary representation
|
||||
* of the value
|
||||
* used to draw block for hours, mintutes, seconds, date
|
||||
*
|
||||
* @param gfx: graphic object to use
|
||||
* @param x: x-coordinate of 1st the square
|
||||
* @param y: y-coordinate of 1st the square
|
||||
* @param data: data conatining the bit information
|
||||
* @param numOfBits: number of bits to draw
|
||||
*/
|
||||
function drawSquare(gfx, x, y, data, numOfBits) {
|
||||
|
||||
for(i = numOfBits; i > 0 ; i--) {
|
||||
if( (data & 1) != 0) {
|
||||
gfx.fillRect(x + (i - 1) * x_step, y,
|
||||
x + i * x_step , y + y_step);
|
||||
}
|
||||
data >>= 1; /* shift one bit right */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function drawBinary(...)
|
||||
* draw the time in binary format
|
||||
* default display for geeks and real men
|
||||
|
||||
* @param h: hours
|
||||
* @param m: minutes
|
||||
* @param s: seconds
|
||||
*/
|
||||
function drawBinary(gfx, hour, minute, second) {
|
||||
gfx.clear(0);
|
||||
|
||||
if(hour > 12) {
|
||||
hour -= 12; /* we use for bit for hours so we only display 12 hours*/
|
||||
}
|
||||
drawSquare(gfx, hx, hy, hour, 4); /* set hour */
|
||||
drawSquare(gfx, mx, my, minute, 6); /* set minute */
|
||||
drawSquare(gfx, sx, sy, second, 6); /* set second */
|
||||
}
|
||||
|
||||
/**
|
||||
* function drawTime(...)
|
||||
* show time under the graphic
|
||||
* for wimps and commies
|
||||
*
|
||||
* @param h: hours
|
||||
* @param m: minutes
|
||||
* @param s: seconds
|
||||
*/
|
||||
|
||||
function drawTime(gfx, h, m, s) {
|
||||
var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2)+ ":" + ("0"+s).substr(-2);
|
||||
|
||||
gfx.setFontAlign(0,-1); // align right bottom
|
||||
gfx.setFont("7x11Numeric7Seg", 2);
|
||||
gfx.drawString(time, gfx.getWidth() / 2, dy + 1, false /*clear background*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* function drawDate(...)
|
||||
* show date under the graphic
|
||||
* (optionally)
|
||||
*
|
||||
* @param gfx: graphic object to use
|
||||
* @param d: date object
|
||||
*/
|
||||
var vMonth = 0;
|
||||
function drawDate(gfx, d) {
|
||||
var dateString = ""
|
||||
+ ("0" + d.getDate()).substr(-2)
|
||||
// + " "
|
||||
// + ("0" + d.getMonth()).substr(-2)
|
||||
// + " "
|
||||
// + ("0" + d.getFullYear()).substr(-2)
|
||||
;
|
||||
|
||||
gfx.setFontAlign(-1,-1); // align right bottom
|
||||
gfx.setFont("7x11Numeric7Seg",2); /* draw the current time font */
|
||||
gfx.drawString(dateString, dx, dy + 1, false /* don't clear background*/);
|
||||
gfx.drawImage(month[d.getMonth()], 40, dy);
|
||||
}
|
||||
|
||||
function toggleDateTime() {
|
||||
showDateTime++;
|
||||
if(showDateTime > 2){
|
||||
showDateTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function updateVTime() {
|
||||
vMonth++;
|
||||
if(vMonth >= 12 + 3) {
|
||||
vMonth = 0;
|
||||
}
|
||||
second++;
|
||||
if(second > 59) {
|
||||
second = 0;
|
||||
minute++;
|
||||
if(minute > 59) {
|
||||
minute = 0;
|
||||
hour++;
|
||||
if(hour > 12) {
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function drawBattery(...)
|
||||
* fill the battery symbol with blocks
|
||||
* according to the battery level
|
||||
*
|
||||
* @param gfx: graphic object
|
||||
* @param level: current battery level
|
||||
*/
|
||||
function drawBattery(gfx, level) {
|
||||
var pos_y = bat_pos_y - 1;
|
||||
var stepLevel = Math.round((level + 10) / 20);
|
||||
|
||||
for(i = 0; i < stepLevel; i++) {
|
||||
pos_y -= bat_size_y + 2;
|
||||
gfx.fillRect(bat_pos_x, pos_y,
|
||||
bat_pos_x + bat_size_x, pos_y + bat_size_y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function drawBattery(...)
|
||||
* fill the battery symbol with blocks
|
||||
* according to the battery level
|
||||
*
|
||||
* @param gfx: graphic object
|
||||
* @param level: current battery level
|
||||
*/
|
||||
function drawBT(gfx, status) {
|
||||
if(!status) {
|
||||
gfx.drawImage(imgNoBT, bt_x, bt_y);
|
||||
}
|
||||
}
|
||||
function setRuntimeValues(resolution) {
|
||||
if(240 == resolution) {
|
||||
x_step = V1_X_STEP;
|
||||
y_step = V1_Y_STEP;
|
||||
|
||||
time_y_offset = V1_TIME_Y_OFFSET;
|
||||
hx = V1_HX;
|
||||
hy = V1_HY;
|
||||
mx = V1_MX;
|
||||
my = V1_MY;
|
||||
sx = V1_SX;
|
||||
sy = V1_SY;
|
||||
bt_x = V1_BT_X;
|
||||
bt_y = V1_BT_Y;
|
||||
dx = V1_DX;
|
||||
dy = V1_DY;
|
||||
|
||||
screen_size_x = V1_SCREEN_SIZE_X;
|
||||
screen_size_y = V1_SCREEN_SIZE_Y;
|
||||
backgroundImage = V1_BACKGROUND_IMAGE;
|
||||
|
||||
bat_pos_x = V1_BAT_POS_X;
|
||||
bat_pos_y = V1_BAT_POS_Y;
|
||||
bat_size_x = V1_BAT_SIZE_X;
|
||||
bat_size_y = V1_BAT_SIZE_Y;
|
||||
|
||||
setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"});
|
||||
|
||||
} else {
|
||||
x_step = V2_X_STEP;
|
||||
y_step = V2_Y_STEP;
|
||||
|
||||
time_y_offset = V2_TIME_Y_OFFSET;
|
||||
|
||||
hx = V2_HX;
|
||||
hy = V2_HY;
|
||||
mx = V2_MX;
|
||||
my = V2_MY;
|
||||
sx = V2_SX;
|
||||
sy = V2_SY;
|
||||
|
||||
bt_x = V2_BT_X;
|
||||
bt_y = V2_BT_Y;
|
||||
|
||||
dx = V2_DX;
|
||||
dy = V2_DY;
|
||||
|
||||
screen_size_x = V2_SCREEN_SIZE_X;
|
||||
screen_size_y = V2_SCREEN_SIZE_Y;
|
||||
backgroundImage = V2_BACKGROUND_IMAGE;
|
||||
|
||||
bat_pos_x = V2_BAT_POS_X;
|
||||
bat_pos_y = V2_BAT_POS_Y;
|
||||
bat_size_x = V2_BAT_SIZE_X;
|
||||
bat_size_y = V2_BAT_SIZE_Y;
|
||||
|
||||
Bangle.on('swipe', function(direction) { toggleDateTime(direction);});
|
||||
}
|
||||
cg = Graphics.createArrayBuffer(
|
||||
screen_size_x,screen_size_y, 1, {msb:true});
|
||||
|
||||
cgimg = {width:screen_size_x, height:screen_size_y, bpp:1,
|
||||
transparent:0, buffer:cg.buffer};
|
||||
|
||||
}
|
||||
var hour = 0, minute = 1, second = 50;
|
||||
var batVLevel = 20;
|
||||
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
|
||||
g.reset();
|
||||
|
||||
drawBinary(cg, h, m, s);
|
||||
|
||||
switch(showDateTime) {
|
||||
case 1:
|
||||
drawTime(cg, h, m, s);
|
||||
break;
|
||||
case 2:
|
||||
drawDate(cg, d);
|
||||
break;
|
||||
default:
|
||||
cg.drawImage(imgSquid, cg.getWidth() / 2 - 44, dy);
|
||||
}
|
||||
drawBattery(cg, /*batVLevel*/ E.getBattery());
|
||||
|
||||
batVLevel += 2;
|
||||
if(batVLevel > 100) {
|
||||
batVLevel = 0;
|
||||
}
|
||||
updateVTime();
|
||||
g.clear();
|
||||
g.drawImages([{image:cgimg},
|
||||
{image:require("Storage").read(backgroundImage)}
|
||||
]);
|
||||
drawBT(g, NRF.getSecurityStatus().connected);
|
||||
// Bangle.drawWidgets();
|
||||
const millis = d.getMilliseconds();
|
||||
setTimeout(draw, 1000-millis);
|
||||
// Bangle.loadWidgets();
|
||||
}
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
setRuntimeValues(g.getWidth());
|
||||
g.reset().clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 708 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,4 @@
|
|||
# Bold Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -29,3 +29,14 @@
|
|||
0.28: Fix double clock load after settings are changed
|
||||
0.29: Update boot0 to avoid code block (faster execution)
|
||||
Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js
|
||||
0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now
|
||||
0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics
|
||||
0.32: Fix single quote error in g.wrapString polyfill
|
||||
improve g.stringMetrics polyfill
|
||||
Fix issue where re-running bootupdate could disable existing polyfills
|
||||
0.33: Add E.showScroller polyfill
|
||||
0.34: Use Storage.hash if available
|
||||
Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS)
|
||||
0.35: Add Bangle.appRect polyfill
|
||||
Don't set beep vibration up on Bangle.js 2 (built in)
|
||||
0.36: Add comments to .boot0 to make debugging a bit easier
|
||||
|
|
|
@ -15,25 +15,5 @@ if (!clockApp) {
|
|||
clockApp = require("Storage").read(clockApp.src);
|
||||
}
|
||||
if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`;
|
||||
// check to see if our clock is wrong - if it is use GPS time
|
||||
if ((new Date()).getFullYear()<2000) {
|
||||
E.showMessage("Searching for\nGPS time");
|
||||
Bangle.on("GPS",function cb(g) {
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.removeListener("GPS",cb);
|
||||
if (!g.time || (g.time.getFullYear()<2000) ||
|
||||
(g.time.getFullYear()>2200)) {
|
||||
// GPS receiver's time not set - just boot clock anyway
|
||||
eval(clockApp);
|
||||
delete clockApp;
|
||||
return;
|
||||
}
|
||||
// We have a GPS time. Set time and reboot (to load alarms properly)
|
||||
setTime(g.time.getTime()/1000);
|
||||
load();
|
||||
});
|
||||
Bangle.setGPSPower(1);
|
||||
} else {
|
||||
eval(clockApp);
|
||||
delete clockApp;
|
||||
}
|
||||
eval(clockApp);
|
||||
delete clockApp;
|
||||
|
|
|
@ -3,17 +3,24 @@ recalculates, but this avoids us doing a whole bunch of reconfiguration most
|
|||
of the time. */
|
||||
E.showMessage("Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var isB2 = process.env.HWVERSION; // Is Bangle.js 2
|
||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
var boot = "";
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))!=${CRC}) { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`;
|
||||
} else {
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`;
|
||||
}
|
||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||
boot += `E.setFlags({pretokenise:1});\n`;
|
||||
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||
if (s.ble!==false) {
|
||||
if (s.HID) { // Human interface device
|
||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
||||
else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));`
|
||||
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`;
|
||||
boot += `NRF.setServices({}, {uart:true, hid:Bangle.HID});\n`;
|
||||
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
|
||||
}
|
||||
}
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
|
@ -48,7 +55,7 @@ boot += `E.setTimeZone(${s.timezone});`;
|
|||
if (!Bangle.F_BEEPSET) {
|
||||
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
||||
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n`
|
||||
else if (s.beep=="vib") boot += `Bangle.beep = function (time, freq) {
|
||||
else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) {
|
||||
return new Promise(function(resolve) {
|
||||
if ((0|freq)<=0) freq=4000;
|
||||
if ((0|time)<=0) time=200;
|
||||
|
@ -81,9 +88,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
|||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.theme) {
|
||||
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
|
||||
}
|
||||
delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it
|
||||
boot += `Bangle.setUI=function(mode, cb) {
|
||||
if (Bangle.btnWatches) {
|
||||
|
@ -94,7 +103,7 @@ if (Bangle.swipeHandler) {
|
|||
Bangle.removeListener("swipe", Bangle.swipeHandler);
|
||||
delete Bangle.swipeHandler;
|
||||
}
|
||||
if (Bangle.touchandler) {
|
||||
if (Bangle.touchHandler) {
|
||||
Bangle.removeListener("touch", Bangle.touchHandler);
|
||||
delete Bangle.touchHandler;
|
||||
}
|
||||
|
@ -131,13 +140,66 @@ else if (mode=="updown") {
|
|||
throw new Error("Unknown UI mode");
|
||||
};\n`;
|
||||
}
|
||||
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);b<c&&(c=b);g.setColor(g.theme.fg);for(var d=0;d<l;d++){var m=d+c;if(0>m||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(),
|
||||
k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`;
|
||||
}
|
||||
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.imageMetrics=function(src) {
|
||||
if (src[0]) return {width:src[0],height:src[1]};
|
||||
else if ('object'==typeof src) return {
|
||||
width:("width" in src) ? src.width : src.getWidth(),
|
||||
height:("height" in src) ? src.height : src.getHeight()};
|
||||
var im = E.toString(src);
|
||||
return {width:im.charCodeAt(0), height:im.charCodeAt(1)};
|
||||
};\n`;
|
||||
}
|
||||
delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.stringMetrics=function(txt) {
|
||||
txt = txt.toString().split("\\n");
|
||||
return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length};
|
||||
};\n`;
|
||||
}
|
||||
delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.wrapString=function(str, maxWidth) {
|
||||
var lines = [];
|
||||
for (var unwrappedLine of str.split("\\n")) {
|
||||
var words = unwrappedLine.split(" ");
|
||||
var line = words.shift();
|
||||
for (var word of words) {
|
||||
if (g.stringWidth(line + " " + word) > maxWidth) {
|
||||
lines.push(line);
|
||||
line = word;
|
||||
} else {
|
||||
line += " " + word;
|
||||
}
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
};\n`;
|
||||
}
|
||||
delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
|
||||
boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight());
|
||||
(lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`;
|
||||
}
|
||||
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||
// which would cause an error!
|
||||
boot += require('Storage').read(bootFile)+";\n";
|
||||
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
||||
});
|
||||
// update ble
|
||||
boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`;
|
||||
// write file
|
||||
require('Storage').write('.boot0',boot);
|
||||
delete boot;
|
||||
E.showMessage("Reloading...");
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,45 @@
|
|||
# Bluetooth Heart Rate Monitor
|
||||
|
||||
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
|
||||
|
||||
This means it's compatible with many Bangle.js apps including:
|
||||
|
||||
* [Heart Rate Widget](https://banglejs.com/apps/#widhrt)
|
||||
* [Heart Rate Recorder](https://banglejs.com/apps/#heart)
|
||||
|
||||
It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm)
|
||||
as that requires live sensor data (rather than just BPM readings).
|
||||
|
||||
## Usage
|
||||
|
||||
Just install the app, then install an app that uses the heart rate monitor.
|
||||
|
||||
Once installed it'll automatically try and connect to the first bluetooth
|
||||
heart rate monitor it finds.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
This works with any heart rate monitor providing the standard Bluetooth
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`).
|
||||
|
||||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
|
||||
## Internals
|
||||
|
||||
This replaces `Bangle.setHRMPower` with its own implementation.
|
||||
|
||||
## TODO
|
||||
|
||||
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
|
||||
* A widget to show connection state?
|
||||
* Specify a specific device by address?
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA=="))
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,79 @@
|
|||
(function() {
|
||||
var log = function() {};//print
|
||||
var gatt;
|
||||
var status;
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||
}
|
||||
Bangle.setHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
log("setHRMPower ->", isOn, app);
|
||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
||||
isOn = Bangle._PWR.HRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
log("setHRMPower on", app);
|
||||
if (!Bangle.isHRMOn()) {
|
||||
log("HRM not already on");
|
||||
status = "searching";
|
||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||
log("Found device "+device.id);
|
||||
status = "connecting";
|
||||
device.on('gattserverdisconnected', function(reason) {
|
||||
gatt = undefined;
|
||||
});
|
||||
return device.gatt.connect();
|
||||
}).then(function(g) {
|
||||
log("Connected");
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService(0x180D);
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic(0x2A37);
|
||||
}).then(function(characteristic) {
|
||||
log("Got characteristic");
|
||||
characteristic.on('characteristicvaluechanged', function(event) {
|
||||
var dv = event.target.value;
|
||||
var flags = dv.getUint8(0);
|
||||
// 0 = 8 or 16 bit
|
||||
// 1,2 = sensor contact
|
||||
// 3 = energy expended shown
|
||||
// 4 = RR interval
|
||||
var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit
|
||||
/* var idx = 2 + (flags&1); // index of next field
|
||||
if (flags&8) idx += 2; // energy expended
|
||||
if (flags&16) {
|
||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||
}*/
|
||||
Bangle.emit('HRM',{
|
||||
bpm:bpm,
|
||||
confidence:100
|
||||
});
|
||||
});
|
||||
return characteristic.startNotifications();
|
||||
}).then(function() {
|
||||
log("Ready");
|
||||
status = "ok";
|
||||
}).catch(function(err) {
|
||||
log("Error",err);
|
||||
gatt = undefined;
|
||||
status = "error";
|
||||
});
|
||||
}
|
||||
} else { // not on
|
||||
log("setHRMPower off", app);
|
||||
if (gatt) {
|
||||
log("HRM connected - disconnecting");
|
||||
status = undefined;
|
||||
try {gatt.disconnect();}catch(e) {
|
||||
log("HRM disconnect error", e);
|
||||
}
|
||||
gatt = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: fix precision rounding issue + no reset when equals pressed
|
||||
0.03: Support for different screen sizes and touchscreen
|
||||
0.04: Display current operation on LHS
|
||||
|
|
|
@ -199,8 +199,7 @@ function doMath(x, y, operator) {
|
|||
function displayOutput(num) {
|
||||
var len;
|
||||
var minusMarge = 0;
|
||||
g.setColor(0);
|
||||
g.fillRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||
g.setColor(-1);
|
||||
if (num === Infinity || num === -Infinity || isNaN(num)) {
|
||||
// handle division by 0
|
||||
|
@ -244,6 +243,10 @@ function displayOutput(num) {
|
|||
}
|
||||
g.setFontAlign(1,0);
|
||||
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);
|
||||
if (operator) {
|
||||
g.setFont('Vector', 22).setFontAlign(1,0);
|
||||
g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2);
|
||||
}
|
||||
}
|
||||
var wasPressedEquals = false;
|
||||
var hasPressedNumber = false;
|
||||
|
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Bangle.js 2 compatibility
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
g.setBgColor(0, 0, 0);
|
||||
g.clear().flip();
|
||||
var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA=="));
|
||||
var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
||||
var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa"));
|
||||
var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA=="));
|
||||
|
||||
var W=240,H=240;
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var b2v = (W != 240)?-1:1;
|
||||
var b2rot = (W != 240)?Math.PI:0;
|
||||
var b2scale = W/240.0;
|
||||
var bubbles = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
||||
|
@ -12,12 +16,16 @@ function anim() {
|
|||
/* we don't use any kind of buffering here. Just draw one image
|
||||
at a time (image contains a background) too, and there is minimal
|
||||
flicker. */
|
||||
var mx = 120, my = 120;
|
||||
var mx = W/2.0, my = H/2.0;
|
||||
bubbles.forEach(f=>{
|
||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
||||
f.y-=f.v * b2v;
|
||||
if (f.y<-24)
|
||||
f.y=H+8;
|
||||
else if (f.y > (H+8))
|
||||
f.y=0;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot});
|
||||
});
|
||||
g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2});
|
||||
g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot});
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.8 KiB |