merge3
23
README.md
|
|
@ -377,40 +377,37 @@ that handles configuring the app.
|
||||||
When the app settings are opened, this function is called with one
|
When the app settings are opened, this function is called with one
|
||||||
argument, `back`: a callback to return to the settings menu.
|
argument, `back`: a callback to return to the settings menu.
|
||||||
|
|
||||||
Usually it will save any information in `app.json` where `app` is the name
|
Usually it will save any information in `myappid.json` where `myappid` is the name
|
||||||
of your app - so you should change the example accordingly.
|
of your app - so you should change the example accordingly.
|
||||||
|
|
||||||
Example `settings.js`
|
Example `settings.js`
|
||||||
```js
|
```js
|
||||||
// make sure to enclose the function in parentheses
|
// make sure to enclose the function in parentheses
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
function get(key, def) { return require('Settings').get('myappid', key, def); }
|
||||||
function save(key, value) {
|
function set(key, value) { require('Settings').set('myappid', key, value); }
|
||||||
settings[key] = value;
|
|
||||||
require('Storage').write('app.json',settings);
|
|
||||||
}
|
|
||||||
const appMenu = {
|
const appMenu = {
|
||||||
'': {'title': 'App Settings'},
|
'': {'title': 'App Settings'},
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
'Monkeys': {
|
'Monkeys': {
|
||||||
value: settings.monkeys||12,
|
value: get('monkeys', 12),
|
||||||
onchange: (m) => {save('monkeys', m)}
|
onchange: (m) => set('monkeys', m)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
E.showMenu(appMenu)
|
E.showMenu(appMenu)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||||
It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||||
```json
|
```json
|
||||||
{ "id": "app",
|
{ "id": "myappid",
|
||||||
...
|
...
|
||||||
"storage": [
|
"storage": [
|
||||||
...
|
...
|
||||||
{"name":"app.settings.js","url":"settings.js"},
|
{"name":"myappid.settings.js","url":"settings.js"}
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name":"app.json"}
|
{"name":"myappid.json"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
|
||||||
124
apps.json
|
|
@ -82,7 +82,7 @@
|
||||||
{
|
{
|
||||||
"id": "health",
|
"id": "health",
|
||||||
"name": "Health Tracking",
|
"name": "Health Tracking",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
|
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,health",
|
"tags": "tool,system,health",
|
||||||
|
|
@ -543,7 +543,7 @@
|
||||||
{
|
{
|
||||||
"id": "cubescramble",
|
"id": "cubescramble",
|
||||||
"name": "Cube Scramble",
|
"name": "Cube Scramble",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "A random scramble generator for the 3x3 Rubik's cube",
|
"description": "A random scramble generator for the 3x3 Rubik's cube",
|
||||||
"icon": "cube-scramble.png",
|
"icon": "cube-scramble.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
|
|
@ -599,7 +599,7 @@
|
||||||
{
|
{
|
||||||
"id": "compass",
|
"id": "compass",
|
||||||
"name": "Compass",
|
"name": "Compass",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Simple compass that points North",
|
"description": "Simple compass that points North",
|
||||||
"icon": "compass.png",
|
"icon": "compass.png",
|
||||||
"screenshots": [{"url":"screenshot_compass.png"}],
|
"screenshots": [{"url":"screenshot_compass.png"}],
|
||||||
|
|
@ -749,12 +749,12 @@
|
||||||
{
|
{
|
||||||
"id": "weather",
|
"id": "weather",
|
||||||
"name": "Weather",
|
"name": "Weather",
|
||||||
"version": "0.10",
|
"version": "0.11",
|
||||||
"description": "Show Gadgetbridge weather report",
|
"description": "Show Gadgetbridge weather report",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"tags": "widget,outdoors",
|
"tags": "widget,outdoors",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "readme.md",
|
"readme": "readme.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"weather.app.js","url":"app.js"},
|
{"name":"weather.app.js","url":"app.js"},
|
||||||
|
|
@ -768,11 +768,11 @@
|
||||||
{
|
{
|
||||||
"id": "chargeanim",
|
"id": "chargeanim",
|
||||||
"name": "Charge Animation",
|
"name": "Charge Animation",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
|
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"tags": "battery",
|
"tags": "battery",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"chargeanim.app.js","url":"app.js"},
|
{"name":"chargeanim.app.js","url":"app.js"},
|
||||||
|
|
@ -892,7 +892,7 @@
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"tags": "widget",
|
"tags": "widget",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widchime.wid.js","url":"widget.js"},
|
{"name":"widchime.wid.js","url":"widget.js"},
|
||||||
{"name":"widchime.settings.js","url":"settings.js"}
|
{"name":"widchime.settings.js","url":"settings.js"}
|
||||||
|
|
@ -908,7 +908,7 @@
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"tags": "widget",
|
"tags": "widget",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widram.wid.js","url":"widget.js"}
|
{"name":"widram.wid.js","url":"widget.js"}
|
||||||
]
|
]
|
||||||
|
|
@ -2639,14 +2639,16 @@
|
||||||
{
|
{
|
||||||
"id": "magnav",
|
"id": "magnav",
|
||||||
"name": "Navigation Compass",
|
"name": "Navigation Compass",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.",
|
"description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.",
|
||||||
|
"screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}],
|
||||||
"icon": "magnav.png",
|
"icon": "magnav.png",
|
||||||
"tags": "tool,outdoors",
|
"tags": "tool,outdoors",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"magnav.app.js","url":"magnav.min.js"},
|
{"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]},
|
||||||
|
{"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]},
|
||||||
{"name":"magnav.img","url":"magnav-icon.js","evaluate":true}
|
{"name":"magnav.img","url":"magnav-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"magnav.json"}]
|
"data": [{"name":"magnav.json"}]
|
||||||
|
|
@ -2730,8 +2732,9 @@
|
||||||
{
|
{
|
||||||
"id": "multiclock",
|
"id": "multiclock",
|
||||||
"name": "Multi Clock",
|
"name": "Multi Clock",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 or swipe left-right. For best display set theme Background 2 to cyan or some other bright colour in settings.",
|
"description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.",
|
||||||
|
"screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}],
|
||||||
"icon": "multiclock.png",
|
"icon": "multiclock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
|
|
@ -3127,15 +3130,17 @@
|
||||||
{
|
{
|
||||||
"id": "dtlaunch",
|
"id": "dtlaunch",
|
||||||
"name": "Desktop Launcher",
|
"name": "Desktop Launcher",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.",
|
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||||
|
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "launch",
|
"type": "launch",
|
||||||
"tags": "tool,system,launcher",
|
"tags": "tool,system,launcher",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"dtlaunch.app.js","url":"app.js"},
|
{"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]},
|
||||||
|
{"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]},
|
||||||
{"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
|
{"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -3644,15 +3649,15 @@
|
||||||
"id": "gbmusic",
|
"id": "gbmusic",
|
||||||
"name": "Gadgetbridge Music Controls",
|
"name": "Gadgetbridge Music Controls",
|
||||||
"shortName": "Music Controls",
|
"shortName": "Music Controls",
|
||||||
"version": "0.05",
|
"version": "0.06",
|
||||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
|
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tools,bluetooth,gadgetbridge,music",
|
"tags": "tools,bluetooth,gadgetbridge,music",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": false,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"gbmusic.app.js","url":"app.js"},
|
{"name":"gbmusic.app.js","url":"app.js"},
|
||||||
{"name":"gbmusic.settings.js","url":"settings.js"},
|
{"name":"gbmusic.settings.js","url":"settings.js"},
|
||||||
|
|
@ -3718,12 +3723,12 @@
|
||||||
"id": "qmsched",
|
"id": "qmsched",
|
||||||
"name": "Quiet Mode Schedule and Widget",
|
"name": "Quiet Mode Schedule and Widget",
|
||||||
"shortName": "Quiet Mode",
|
"shortName": "Quiet Mode",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}],
|
"screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}],
|
||||||
"tags": "tool,widget",
|
"tags": "tool,widget",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"qmsched","url":"lib.js"},
|
{"name":"qmsched","url":"lib.js"},
|
||||||
|
|
@ -4040,7 +4045,7 @@
|
||||||
{
|
{
|
||||||
"id": "antonclk",
|
"id": "antonclk",
|
||||||
"name": "Anton Clock",
|
"name": "Anton Clock",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "A simple clock using the bold Anton font.",
|
"description": "A simple clock using the bold Anton font.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
@ -4201,7 +4206,7 @@
|
||||||
{
|
{
|
||||||
"id": "swiperclocklaunch",
|
"id": "swiperclocklaunch",
|
||||||
"name": "Swiper Clock Launch",
|
"name": "Swiper Clock Launch",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Navigate between clock and launcher with Swipe action",
|
"description": "Navigate between clock and launcher with Swipe action",
|
||||||
"icon": "swiperclocklaunch.png",
|
"icon": "swiperclocklaunch.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
@ -4264,18 +4269,18 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "a_battery_widget",
|
"id": "wid_a_battery_widget",
|
||||||
"name": "A Battery Widget (with percentage)",
|
"name": "A Battery Widget (with percentage)",
|
||||||
"shortName":"A Battery Widget",
|
"shortName":"A Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"1.0",
|
"version":"1.01",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"description": "Simple and slim battery widget with charge status and percentage",
|
"description": "Simple and slim battery widget with charge status and percentage",
|
||||||
"tags": "widget,battery",
|
"tags": "widget,battery",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"a_battery_widget.wid.js","url":"widget.js"}
|
{"name":"wid_a_battery_widget.wid.js","url":"widget.js"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -4283,11 +4288,12 @@
|
||||||
"name": "LCARS Clock",
|
"name": "LCARS Clock",
|
||||||
"shortName":"LCARS",
|
"shortName":"LCARS",
|
||||||
"icon": "lcars.png",
|
"icon": "lcars.png",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"lcars.app.js","url":"lcars.app.js"},
|
{"name":"lcars.app.js","url":"lcars.app.js"},
|
||||||
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true}
|
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true}
|
||||||
|
|
@ -4297,34 +4303,50 @@
|
||||||
"name": "Binary Watch",
|
"name": "Binary Watch",
|
||||||
"shortName":"BinWatch",
|
"shortName":"BinWatch",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.02",
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"version":"0.03",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
"description": "Famous binary watch",
|
"description": "Famous binary watch",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"binwatch.app.js","url":"app.js"},
|
{"name":"binwatch.app.js","url":"app.js"},
|
||||||
{"name":"binwatch.img","url":"app-icon.js","evaluate":true}
|
{"name":"binwatch.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "schoolCalendar",
|
{
|
||||||
"name": "School Calendar",
|
"id": "hidmsicswipe",
|
||||||
"shortName":"SCalendar",
|
"name": "Bluetooth Music Swipe Controls",
|
||||||
"icon": "CalenderLogo.png",
|
"shortName": "Swipe Control",
|
||||||
"version": "0.01",
|
"version": "0.01",
|
||||||
"description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)",
|
"description": "Based on the original Bluetooth Music Controls. Swipe up/down for volume, left/right for previous and next, tap for play/pause and btn1 to lock and unlock the controls. Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
|
||||||
"tags": "tool",
|
"icon": "hidmsicswipe.png",
|
||||||
"readme": "README.md",
|
"tags": "bluetooth",
|
||||||
"custom":"custom.html",
|
"supports": ["BANGLEJS2"],
|
||||||
"supports": ["BANGLEJS"],
|
"storage": [
|
||||||
"screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}],
|
{"name":"hidmsicswipe.app.js","url":"hidmsicswipe.js"},
|
||||||
"storage": [
|
{"name":"hidmsicswipe.img","url":"hidmsicswipe-icon.js","evaluate":true}
|
||||||
{"name":"schoolCalendar.app.js"},
|
]
|
||||||
{"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}
|
},
|
||||||
],
|
{
|
||||||
"data": [
|
"id": "authentiwatch",
|
||||||
{"name":"app.json"}
|
"name": "2FA Authenticator",
|
||||||
]
|
"shortName": "AuthWatch",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Google Authenticator compatible tool.",
|
||||||
|
"tags": "tool",
|
||||||
|
"interface": "interface.html",
|
||||||
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"authentiwatch.app.js","url":"app.js"},
|
||||||
|
{"name":"authentiwatch.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"authentiwatch.json"}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@
|
||||||
"no-prototype-builtins": "off",
|
"no-prototype-builtins": "off",
|
||||||
"no-redeclare": "off",
|
"no-redeclare": "off",
|
||||||
"no-unreachable": "warn",
|
"no-unreachable": "warn",
|
||||||
|
"no-cond-assign": "warn",
|
||||||
|
"no-useless-catch": "warn",
|
||||||
// TODO: "no-undef": "warn",
|
// TODO: "no-undef": "warn",
|
||||||
"no-undef": "off",
|
"no-undef": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
2021/11/18 | 1.0: Release for Bangle 2
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Load widgets after setUI so widclk knows when to hide
|
0.02: Load widgets after setUI so widclk knows when to hide
|
||||||
|
0.03: Clock now shows day of week under date.
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ function draw() {
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
var timeStr = require("locale").time(date,1);
|
var timeStr = require("locale").time(date,1);
|
||||||
var dateStr = require("locale").date(date).toUpperCase();
|
var dateStr = require("locale").date(date).toUpperCase();
|
||||||
|
var dowStr = require("locale").dow(date).toUpperCase();
|
||||||
// draw time
|
// draw time
|
||||||
g.setFontAlign(0,0).setFont("Anton");
|
g.setFontAlign(0,0).setFont("Anton");
|
||||||
g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background
|
g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background
|
||||||
|
|
@ -32,6 +33,10 @@ function draw() {
|
||||||
g.setFontAlign(0,0).setFont("6x8",2);
|
g.setFontAlign(0,0).setFont("6x8",2);
|
||||||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||||
g.drawString(dateStr,x,y);
|
g.drawString(dateStr,x,y);
|
||||||
|
//draw day of week
|
||||||
|
y += 16;
|
||||||
|
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||||
|
g.drawString(dowStr,x,y);
|
||||||
// queue draw in one minute
|
// queue draw in one minute
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: start of development
|
0.01: start of development
|
||||||
0.02: first running version for BangleJs2
|
0.02: first running version for BangleJs2
|
||||||
|
0.03: corrected icon, added screen shot, extended description
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,47 @@
|
||||||
# TheBinWatch
|
# TheBinWatch
|
||||||
|
|
||||||
Binary watch to train Your brain
|
Binary watch to train Your brain
|
||||||
Inspired by the 80's LCD wrist watch from RALtec
|
Inspired by the LCD wrist watch from TecRAL from 1989
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- swipe to left or right to change displayed text (date, time, ...)
|
- swipe to left or right to change displayed text (date, time, ...)
|
||||||
- currently only available for BangeJs2
|
- currently only available for BangeJs2
|
||||||
- Widgets will not be shown
|
- Widgets will not be shown
|
||||||
- If bluetooth connection is not established an icon will show up
|
- If bluetooth connection is not established an icon will show up
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
Binary means that every digit can represent 2 states: 0 or 1, displayed by a black bar.
|
||||||
|
|
||||||
|
The principle is the same like in out well known and daily used decimal system with values from 0 to 9:
|
||||||
|
|
||||||
|
We start from the most right position with the least significant bit (binary digit) which can have the value 0 or 1
|
||||||
|
The 2nd bit from the right can have the value 0 or 2 (sum of all bits to the right set to 1 plus 1).
|
||||||
|
This principle is valid for all the remaining bits.
|
||||||
|
|
||||||
|
Mathematically spoken: the value of a digit is the base number of the system (10 for decimal or 2 for binary)
|
||||||
|
to the power of the position (from the right, starting with 0).
|
||||||
|
That means in numbers: 2^5 = 32, 2^4 = 16, 2^3 = 8, 2^2 = 4, 2^1 = 2, 2^0 = 1
|
||||||
|
|
||||||
|
The upper row represents the hours with 4 bit (2^4 = 16 possible values in total, 12 are used: 1 to 12),
|
||||||
|
the 2nd row represents the minutes with 6 bit (2^6 = 64 possible values in total, 60 are used: 0 to 59).
|
||||||
|
Same holds for the thrid row: 0-59 seconds
|
||||||
|
|
||||||
|
To read the values of a row we summ up the vaules of set bits (black bars).
|
||||||
|
E.g. the picture above, 3rd row (seconds):
|
||||||
|
101001
|
||||||
|
is 1 * 32 + 0 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 1 * 1
|
||||||
|
is (only the '1' bit): 32 + 8 + 1 = 41
|
||||||
|
|
||||||
|
for the minutes we do the same: 32 + 1 = 33
|
||||||
|
and the hours: 8 + 2 = 10
|
||||||
|
|
||||||
|
So the time is 10:33:41 (that's all)
|
||||||
|
|
||||||
|
## TRAIN YOUR BRAIN
|
||||||
|
|
||||||
|
Remark: more infos about the original watch including manual can be found here:
|
||||||
|
https://timeartpiece.com/watches/tech-ral-binary
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("2GwwcCBQ0JkmSpICuoBMNIP4ABH14CCpBAMgRB/IOlJIJkSIOcgIP5BNH2ICDIJ3/AFvkIJsEIOuAIJKSCk4jQ7dt2wCJt4dP/hBc4EAgIOCIJl8EgPyv5BPyBAIgJBCn4GBg4cG/wKBhJBC/ZBLsATByRBM/5BCyRBM/5BMvMkIJ3gDoOSp5BM+RBLhJBCXIIABj4bF+AJBDoOfA4JBLCQMEMoRBPoBBHBZCnFwEAgfJIIftIJPfDYM8IJ3+ILkCCIOTIJnYDYKnCn5BPpBAGF4WSuEHCgRBF/gRBjxBE/pBJcYMBIJ//U4IRBILDjDBYJBJ25BBh4vCk5BXiRxC+BBJ8ARBDQIdBp4JBQZISBhJBQ/IRCkBBFF4WfIJkHII32II++EgN5F4UkHg/8JohBYCAMEIIdJIJWwCYIRDEwJBGkkn/1k85BDkhAEF4f/IJP4CIM8II3+II/wgEDeoZBH/MJQIPJyV/8hBZFgYCBn5BJwEAgSDEyZBF5DDB+f5yUfIIYZBAAQaCKQJBJ4EAgJBH/5BGtgkBiRBK/1CGAPyvhBB/gRCyBBUh5BFCgJBHvgkB+RBEXIJBEJwKDB8mE55BHOIZuBIJH+CIMJIIraB//7IItgCYIRFIIvyiVP8//kkk5//CIZBCF4YVBIJd5IJH/IIvggEHII1PIIfwAwX5kMkzJKBIKnwCIIsFAQOfII4SBghBGFIRBDAwPJGwJBFoAvELIRBIwEAgfJIJPtIIffEgM8IJ0mpAMBIIP+CIVIIKUCQY+TII3YgEB8hBHn5BFmVOIJIvDXgZBG/hSBiRBK/pBD4BBBCIxBIGQKoBIILLBCIQvEIJrdDAQoOBIIe3IIMPIJEnIIlMyfz/JBDAgJBFNYRBI8BBBFg7dEQYYSBhJBIkgmC/0SsmH+V/knPIIsgCgWfIJkHIJn2IIO+IIN5IJsTkknQYRBC/4UGIJYtBghBJpJBE2ATBCJIsD/nJkLMB5MmfYRBHBIRBH/AtBnhBM/xBBwEAgRBPhMn/1JmY2D8hBSgIUGAQk/IIVvIIMeIJWTE4RBC+VJXIZBGSIJBJ4BBBFhJBD//btiWBiRBOHAMnyVkA4aOBIJQnBAARBCh5BLDQXbvgWBOAIUKfwf/LggIFIJt+AQMJIJbgC/dgCYIRLyVPHQoAGIJP+IAcDDhgAkTwhBEAG5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5Bs+EAgFJAoP27dt2wCJB4P8z4DBCJdt34PB5ApBh5BTwEAgfJAoP+IJffIIvtIJYpC5PAIK8CpM/IKHkyZBQ/1JQYMBIKX8CwMSIIX/IJYWCkhBC/pBKt41DnBBXBwILCIJW3IIeSv5BMBoI1CkArBNYRBP8AVBBYMkA4P7IJn5EANPAoJBNGQQrBg5BTg5BE/5BJ35BH+xBJEAQmCFgRBRKwMEDQWfIJ3JEARBO/gmCwAtBIKH4CYM8IIvtIJAWCIIv+IJHfBgPkEwWcIKoLCkmTIJv+EAc/IJQpCIIeQFoMfIJ/AgEBIIeSv///pBHt4gGIIP/IJZoEYwJBSh5BG/5BHBQQgHII+3II2SQYMDIJ3+CQMJDQlPIJgRDAQIHB/ZBJ/ImEoAvBDwRBL+ARBvJBH+xBGCwRBH/5BG35BHpxBTFggCBIJf8IIufIJfJEwlIF4MPIJuAa4IaFIIX+IIvfBIPkIJHtIIocCNA3AIKMCDQ0/II4VCII2TII9vIJKDBgJBM/gQBiQaGBwRBICIpBD/pBHGQgCCnBBRDQ4OC/ZBD25BJyV/IIwHBIJEgGIKtBIJXgB4IsGAQJBJ/JBHp4LBII4mIGIMHIJYOCIJX/IIe/IJv2IIYaCExB0BIJ0EDRGfIJHJII9JIJH8ExGAGYJBK/ANBFhBBD9pBCAoP+CI5BD/xBC74GB8gmIzhBOgIaJyZBEt5BMn5BEGIQmJyBBBj5BJ4BBBQZOSv///pBEDogCFEYRBFExOTYwMDIJcPIJn/IIIECIJv7tu3IJmSQYJBJ/wMBhIaKp5BGCJICBII35ExVAGoIkBII3wBYN5IJv27ZvCIJv/tu/AYPJExVOIJosKAQJBF/hBLz5BCIoRBLpA1Bh5BHwEAgRBO/3fAYPkIJ3tCwQmM4BBZn5tCIJ2TB4PvIJ6DBgJBHAHRB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5BmwEAgO27dtARIZCkASBCJfbt4SB+YCB/omM4AjBIJMDDRe3IIUngEAjZBLv5uCAYJBM25BBh5BH+AuBmxBN/MkCQMDIJ2Sp4DBQZgiBIJH+BYN2DRW/IIfggEHIJaWCIIf2ExW+GoJXBIJMGIJvJkjZBgBBN/gpBIJuwIJX/aIMADRQPB/wXBzgSB9pBJ74TB8hBD/wmKMYMDCAJBJgJBPyBBBhpBJYgRBCn5BLt5BBj5BJ/AuBjYaJC4oSBgJBMFIRBB/5BJtggBIJs7DRDcBC4lwUgJBI25BFFIRBJvgzBCoRBH/4NBgZBLCgIXBoATBmxBK/IpCkgGB/YmIsBBN8EAg4aIBwRBDpwhBuxBH35BI/4mIDwMHIJsAIJX8IIdICQMGIJXJIIefIJPfIJ38B4MNDQ4NB8hBDpISBhxBHCQP+CIZBD9omG7AeBn5BOjpBPnATBII1vII+TIJP4gEBG4RBJ/+ACAIaGBgQsDAQMgIIMbIJApEAQN///9Ew3AIJ/wgEDDQu3IJEnIIM7IIo3BIJP/EwxBBh5BPgE2II/5IIskCQMDIJARFyVPII+2DgJBO/wRBuwaE34LB5JBG8EAg5BFCQP8IJP2EwmwF4JXCIJ0GIJ+ACYJBE75BJpJBHWYRBO/7XBgAaEJgQsGkmQCQPtII3kIJP+EwhdBgY2EIJkBDQdvIJWTEwMNIIYdCIJE/IItvDQMfIJ/4OAMbIIoUEAQgSBgJBGCI4sDIIdsDQJBQ/4TBnYaCbgRBJuCqBIIW3IJ37EwV8FoI1FIJsDIIosHAQNACYM2IIn5IJEkIItgIKfgCgIaCA4P8IJNOCQN2IIO/CYPJIJf/EwQYBg5BU9pBOpASBgxBBDYRBKz5BD74YBn5BR/gVBhoaBA4PkIJNJCQMAIIf+CJJBDNAPYIK8d2wHCIJc4gEB7dvIJuTIIf4C4I0FIJn/wAWBIIYsJAQMgKoMbIIQmEAQ9///923AIKvwgED25BOk5BBnYxBIJ//2wWBh40GEwpBIgE3AoP5IJckCQMDGIQRLyVPB4O+IKwAz/hWFIP5B88hBFz4SK8EAn4HE4EAgJBjHwYCCyYSKjkAg///xEB/0AAAJKFABXwCYMPIKUSIJsDwEAFII7CQYnxSol/ILP5IIUgIIWSEZAABgFwgEfwBBBwIKC45KECIIdG4H8HwQREIJ0CIJ1+gCGBn/8QATIBF4LRB//4ILfJIISYBIIVPIJV/4BBp/w7CpBBR/BBwhKJCIJYDBINHyIIVAIIoXJIOcBIKAmBIIYyBIIgREIKw4BHYJABIIknChHjAwuPc4ovDCIxBS/hBGgBBMADfwFYJECIJuQIIcEBASDPABUfILHkHAWAIKD1DVQP8gK2DBAMHAoRBHYqJBIgAICz5BLwBBE/0AIL7+CkhAEIIeTIK4FBIMcSILT7BDI5BQ/JBCkBBFgRBByQ4CIKmAZARBW5JBCIApBb/kAGRBBbgBBCp5BM/+BBQXHIIXgJQRBW/w1CpBBKpJBKEwfAgA7CIIMAGoRBjhJBJgYbDEwLCBAAIFBQYTdHAAfwCYJQJ//yIIVAIJbgKOIiDFCZhBagJBRVAoUTAA4yBGoJAHAAJBCk4saACf8IIWQIP5BLggOCIN3kGQWAIJufIPkABwQCyIBUAiRBzkBB/IJsCIOZALIP4ADIOVIIJsJIONAHQwA=="))
|
require("heatshrink").decompress(atob("mEwwcCgEBkmSpICKCwQRRhMn/4AK+VACIU4A4PAz+27dt20ECI1IgEDCIOT+wRB2EkCIX+BwMCpE/8f+gmSvwRB2Mkz///v/5IRBpwRHwIRC5PzCIMSCIXwMQNP7dshMkyf/p+G/MgiV+CIPxCJFM8gRByf+CIIvBRIP7sCMCv/h8//C4P+g6ABCIdiCIVP/M///kFIPAj6iLCIYAOCPH4ibUC2zABdgW/8ARFUgILB2/8fwf/kB3BPobUD3/kz4pCTwMDCIrCBCIWTCINv/IREfAVJDoYpCv/JkmAv4RCYQYRM+ARCn4vCHYX+bQOQh4RBfAYRJyUBCI3/F4IFB/4RGdP4RHwDmC7/gmzaC//tbQWBR4UbfAWQgzIDfwVsR4QRCfAIRM/0DCIWSgDaDz4RBsDXDCIIdByVAfAb+CCIf/4AREjYRFgZ9D/D4DpEDfAT+Cj4REhoRJ7ARE/8PfAVJgbmDp/YWZHgv6zIkkSBYWB44sB/4CB/AREkESp4EBx4RBx0/CIPACAf5kECCIQAHPQIAB5MAgVJEYs4AwIjECIMACI0ACIv+pARCn5rDvwFDGoQRDhILDABHyoARBgKeCARQQBCKIA=="))
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Bangle.js 2 compatibility
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
|
g.setBgColor(0, 0, 0);
|
||||||
g.clear().flip();
|
g.clear().flip();
|
||||||
var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA=="));
|
var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa"));
|
||||||
var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA=="));
|
||||||
|
|
||||||
var W=240,H=240;
|
var W=g.getWidth(),H=g.getHeight();
|
||||||
|
var b2v = (W != 240)?-1:1;
|
||||||
|
var b2rot = (W != 240)?Math.PI:0;
|
||||||
|
var b2scale = W/240.0;
|
||||||
var bubbles = [];
|
var bubbles = [];
|
||||||
for (var i=0;i<10;i++) {
|
for (var i=0;i<10;i++) {
|
||||||
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
||||||
|
|
@ -12,12 +16,16 @@ function anim() {
|
||||||
/* we don't use any kind of buffering here. Just draw one image
|
/* we don't use any kind of buffering here. Just draw one image
|
||||||
at a time (image contains a background) too, and there is minimal
|
at a time (image contains a background) too, and there is minimal
|
||||||
flicker. */
|
flicker. */
|
||||||
var mx = 120, my = 120;
|
var mx = W/2.0, my = H/2.0;
|
||||||
bubbles.forEach(f=>{
|
bubbles.forEach(f=>{
|
||||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
f.y-=f.v * b2v;
|
||||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
if (f.y<-24)
|
||||||
|
f.y=H+8;
|
||||||
|
else if (f.y > (H+8))
|
||||||
|
f.y=0;
|
||||||
|
g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot});
|
||||||
});
|
});
|
||||||
g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2});
|
g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot});
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Show text if uncalibrated
|
0.02: Show text if uncalibrated
|
||||||
0.03: Eliminate flickering
|
0.03: Eliminate flickering
|
||||||
0.04: Fix for Bangle.js 2 and themes
|
0.04: Fix for Bangle.js 2 and themes
|
||||||
|
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ Bangle.on('mag', function(m) {
|
||||||
}
|
}
|
||||||
g.setFontAlign(0,0).setFont("6x8",3);
|
g.setFontAlign(0,0).setFont("6x8",3);
|
||||||
var y = 36;
|
var y = 36;
|
||||||
g.clearRect(M-40,y,M+40,y+24);
|
g.clearRect(M-40,24,M+40,48);
|
||||||
g.drawString(Math.round(m.heading),M,y,true);
|
g.drawString(Math.round(m.heading),M,y,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: Initial Release
|
0.01: Initial Release
|
||||||
0.02: Replace icon with one found on https://icons8.com
|
0.02: Replace icon with one found on https://icons8.com
|
||||||
|
0.03: Re-render icon fixing display in settings
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("AH4A6iIAQCwkBC6MQC4kYxAACwMT/4ACmMe9wAC6IXLj4XD+IXE8IX/C9/zR4oXOmYABC6UTCwQXZjrXKf5IAHC713AAURindAAVBiatDmIXFi4XDuMdC4fRYooX/5nBC6Xc5gABC6UcCwQXF+aPMC471DC6MTCwQXHa4V2szXBC4bXBC5YAQC7se9wAC6MYxAACwJTCAAIXL8IXFQYoX/C/4XtjrXNu1ma4z/JAA4XEgAXRCwgA/AGo"))
|
require("heatshrink").decompress(atob("mEwwhC/AHcRACAWEgIXRiAXEjGIAAWBif/AAUxj3uAAXRC5cfC4fxC4nhC/4Xv+aPFC50zAAIXSiYWCC7Mda5T/JAA4Xeu4ACiMU7oACoMTVocxC4sXC4dxjoXD6LFFC//M4IXS7nMAAIXSjgWCC4vzR5gXHeoYXRiYWCC47XCu1ma4IXDa4IXLACAXdj3uAAXRjGIAAWBKYQABC5fhC4qDFC/4X/C9sda5t2szXGf5IAHC4kAC6IWEAH4A1"))
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Multiple pages
|
0.02: Multiple pages
|
||||||
0.03: cycle thru pages
|
0.03: cycle thru pages
|
||||||
0.04: reset to clock after 2 mins of inactivity
|
0.04: reset to clock after 2 mins of inactivity
|
||||||
|
0.05: add Bangle 2 version
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||

|

|
||||||
|
|
||||||
In the picture above, the Settings app is selected.
|
In the picture above, the Settings app is selected.
|
||||||
## Controls
|
## Controls- Bangle
|
||||||
|
|
||||||
**BTN1** - move backward through app icons on a page
|
**BTN1** - move backward through app icons on a page
|
||||||
|
|
||||||
|
|
@ -14,3 +14,11 @@ In the picture above, the Settings app is selected.
|
||||||
**Swipe Left** - move to next page of app icons
|
**Swipe Left** - move to next page of app icons
|
||||||
|
|
||||||
**Swipe Right** - move to previous page of app icons
|
**Swipe Right** - move to previous page of app icons
|
||||||
|
|
||||||
|
## Controls- Bangle 2
|
||||||
|
|
||||||
|
**Touch** - icon to select, scond touch launches app
|
||||||
|
|
||||||
|
**Swipe Left** - move to next page of app icons
|
||||||
|
|
||||||
|
**Swipe Right** - move to previous page of app icons
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/* Desktop launcher
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var s = require("Storage");
|
||||||
|
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||||
|
apps.sort((a,b)=>{
|
||||||
|
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||||
|
if (n) return n; // do sortorder first
|
||||||
|
if (a.name<b.name) return -1;
|
||||||
|
if (a.name>b.name) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
apps.forEach(app=>{
|
||||||
|
if (app.icon)
|
||||||
|
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||||
|
});
|
||||||
|
|
||||||
|
var Napps = apps.length;
|
||||||
|
var Npages = Math.ceil(Napps/4);
|
||||||
|
var maxPage = Npages-1;
|
||||||
|
var selected = -1;
|
||||||
|
var oldselected = -1;
|
||||||
|
var page = 0;
|
||||||
|
const XOFF = 24;
|
||||||
|
const YOFF = 30;
|
||||||
|
|
||||||
|
function draw_icon(p,n,selected) {
|
||||||
|
var x = (n%2)*72+XOFF;
|
||||||
|
var y = n>1?72+YOFF:YOFF;
|
||||||
|
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52);
|
||||||
|
g.clearRect(x+12,y+4,x+59,y+51);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||||
|
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
||||||
|
var txt = apps[p*4+n].name.split(" ");
|
||||||
|
for (var i = 0; i < txt.length; i++) {
|
||||||
|
txt[i] = txt[i].trim();
|
||||||
|
g.drawString(txt[i],x+36,y+54+i*8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPage(p){
|
||||||
|
g.reset();
|
||||||
|
g.clearRect(0,24,175,175);
|
||||||
|
var O = 88+YOFF/2-12*(Npages/2);
|
||||||
|
for (var j=0;j<Npages;j++){
|
||||||
|
var y = O+j*12;
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||||
|
else g.drawCircle(XOFF/2,y,4);
|
||||||
|
}
|
||||||
|
for (var i=0;i<4;i++) {
|
||||||
|
if (!apps[p*4+i]) return i;
|
||||||
|
draw_icon(p,i,selected==i);
|
||||||
|
}
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on("swipe",(dir)=>{
|
||||||
|
selected = 0;
|
||||||
|
oldselected=-1;
|
||||||
|
if (dir<0){
|
||||||
|
++page; if (page>maxPage) page=0;
|
||||||
|
drawPage(page);
|
||||||
|
} else {
|
||||||
|
--page; if (page<0) page=maxPage;
|
||||||
|
drawPage(page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function isTouched(p,n){
|
||||||
|
if (n<0 || n>3) return false;
|
||||||
|
var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF;
|
||||||
|
var x2 = x1+71; var y2 = y1+81;
|
||||||
|
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on("touch",(_,p)=>{
|
||||||
|
var i;
|
||||||
|
for (i=0;i<4;i++){
|
||||||
|
if((page*4+i)<Napps){
|
||||||
|
if (isTouched(p,i)) {
|
||||||
|
draw_icon(page,i,true);
|
||||||
|
if (selected>=0) {
|
||||||
|
if (selected!=i){
|
||||||
|
draw_icon(page,selected,false);
|
||||||
|
} else {
|
||||||
|
load(apps[page*4+i].src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected=i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||||
|
draw_icon(page,selected,false);
|
||||||
|
selected=-1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
drawPage(0);
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity
|
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity
|
||||||
0.04: Setting to disable touch controls, minor bugfix
|
0.04: Setting to disable touch controls, minor bugfix
|
||||||
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker
|
||||||
|
0.06: Bangle.js 2 support
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
If you have an Android phone with Gadgetbridge, this app allows you to view
|
If you have an Android phone with Gadgetbridge, this app allows you to view
|
||||||
and control music playback.
|
and control music playback.
|
||||||
|
|
||||||
 
|
| Bangle.js 1 | Bangle.js 2 |
|
||||||
|
|:-------------------------------------------|:-------------------------------------------|
|
||||||
|
|  |  |
|
||||||
|
|
||||||
Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/).
|
Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/).
|
||||||
|
|
||||||
|
|
@ -23,25 +25,27 @@ Automatically load the app when you play music and close when the music stops.
|
||||||
(If the app opened automatically, it closes after music has been paused for 5 minutes.)
|
(If the app opened automatically, it closes after music has been paused for 5 minutes.)
|
||||||
|
|
||||||
**Simple button**:
|
**Simple button**:
|
||||||
Disable double/triple pressing Button 2: always simply toggle play/pause.
|
Disable double/triple pressing Middle Button: always simply toggle play/pause.
|
||||||
(For music players which handle multiple button presses themselves.)
|
(For music players which handle multiple button presses themselves.)
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
### Buttons
|
### Buttons
|
||||||
* Button 1: Volume up
|
* Button 1 (*Bangle.js 1*): Volume up
|
||||||
* Button 2:
|
* Middle Button:
|
||||||
- Single press: toggle play/pause
|
- Single press: Toggle play/pause
|
||||||
- Double press: next song
|
- Double press: Next song
|
||||||
- Triple press: previous song
|
- Triple press: Previous song
|
||||||
- Long-press: open application launcher
|
- Long-press: open application launcher
|
||||||
* Button 3: Volume down
|
* Button 3 (*Bangle.js 1*): Volume down
|
||||||
|
|
||||||
### Touch
|
### Touch
|
||||||
* Left: pause/previous song
|
* Left: Pause/previous song
|
||||||
* Right: next song/resume
|
* Right: Next song/resume
|
||||||
* Center: toggle play/pause
|
* Center: Toggle play/pause
|
||||||
* Swipe: next/previous song
|
* Swipe left/right: Next/previous song
|
||||||
|
* Swipe up/down (*Bangle.js 2*): Volume up/down
|
||||||
|
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,77 +4,9 @@
|
||||||
**/
|
**/
|
||||||
let auto = false; // auto close if opened automatically
|
let auto = false; // auto close if opened automatically
|
||||||
let stat = "";
|
let stat = "";
|
||||||
let info = {
|
|
||||||
artist: "",
|
|
||||||
album: "",
|
|
||||||
track: "",
|
|
||||||
n: 0,
|
|
||||||
c: 0,
|
|
||||||
};
|
|
||||||
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
|
||||||
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
|
||||||
|
const BANGLE2 = process.env.HWVERSION===2;
|
||||||
///////////////////////
|
|
||||||
// Self-repeating timeouts
|
|
||||||
///////////////////////
|
|
||||||
|
|
||||||
// Clock
|
|
||||||
let tock = -1;
|
|
||||||
function tick() {
|
|
||||||
if (!Bangle.isLCDOn()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const now = new Date;
|
|
||||||
if (now.getHours()*60+now.getMinutes()!==tock) {
|
|
||||||
drawDateTime();
|
|
||||||
tock = now.getHours()*60+now.getMinutes();
|
|
||||||
}
|
|
||||||
setTimeout(tick, 1000); // we only show minute precision anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade out while paused and auto closing
|
|
||||||
let fade = null;
|
|
||||||
function fadeOut() {
|
|
||||||
if (!Bangle.isLCDOn() || !fade) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
drawMusic(false); // don't clear: draw over existing text to prevent flicker
|
|
||||||
setTimeout(fadeOut, 500);
|
|
||||||
}
|
|
||||||
function brightness() {
|
|
||||||
if (!fade) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return Math.max(0, 1-((Date.now()-fade)/POUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll long track names
|
|
||||||
// use an interval to get smooth movement
|
|
||||||
let offset = null, // scroll Offset: null = no scrolling
|
|
||||||
iScroll;
|
|
||||||
function scroll() {
|
|
||||||
offset += 10;
|
|
||||||
drawScroller();
|
|
||||||
}
|
|
||||||
function scrollStart() {
|
|
||||||
if (offset!==null) {
|
|
||||||
return; // already started
|
|
||||||
}
|
|
||||||
offset = 0;
|
|
||||||
if (Bangle.isLCDOn()) {
|
|
||||||
if (!iScroll) {
|
|
||||||
iScroll = setInterval(scroll, 200);
|
|
||||||
}
|
|
||||||
drawScroller();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function scrollStop() {
|
|
||||||
if (iScroll) {
|
|
||||||
clearInterval(iScroll);
|
|
||||||
iScroll = null;
|
|
||||||
}
|
|
||||||
offset = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
|
|
@ -85,21 +17,22 @@ function fitText(text) {
|
||||||
return Infinity;
|
return Infinity;
|
||||||
}
|
}
|
||||||
// make a guess, then shrink/grow until it fits
|
// make a guess, then shrink/grow until it fits
|
||||||
const test = (s) => g.setFont("Vector", s).stringWidth(text);
|
const w = Bangle.appRect.w,
|
||||||
let best = Math.floor(24000/test(100));
|
test = (s) => g.setFont("Vector", s).stringWidth(text);
|
||||||
if (test(best)===240) { // good guess!
|
let best = Math.floor(100*w/test(100));
|
||||||
|
if (test(best)===w) { // good guess!
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
if (test(best)<240) {
|
if (test(best)<w) {
|
||||||
do {
|
do {
|
||||||
best++;
|
best++;
|
||||||
} while(test(best)<=240);
|
} while(test(best)<=w);
|
||||||
return best-1;
|
return best-1;
|
||||||
}
|
}
|
||||||
// width > 240
|
// width > w
|
||||||
do {
|
do {
|
||||||
best--;
|
best--;
|
||||||
} while(test(best)>240);
|
} while(test(best)>w);
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,14 +48,6 @@ function textCode(text) {
|
||||||
}
|
}
|
||||||
return code%360;
|
return code%360;
|
||||||
}
|
}
|
||||||
// dark magic
|
|
||||||
function hsv2rgb(h, s, v) {
|
|
||||||
const f = (n) => {
|
|
||||||
const k = (n+h/60)%6;
|
|
||||||
return v-v*s*Math.max(Math.min(k, 4-k, 1), 0);
|
|
||||||
};
|
|
||||||
return {r: f(5), g: f(3), b: f(1)};
|
|
||||||
}
|
|
||||||
function f2hex(f) {
|
function f2hex(f) {
|
||||||
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
return ("00"+(Math.round(f*255)).toString(16)).substr(-2);
|
||||||
}
|
}
|
||||||
|
|
@ -131,38 +56,218 @@ function f2hex(f) {
|
||||||
* @return {string} Semi-random color to use for given info
|
* @return {string} Semi-random color to use for given info
|
||||||
*/
|
*/
|
||||||
function infoColor(name) {
|
function infoColor(name) {
|
||||||
let h, s, v;
|
// make color depend deterministically on info
|
||||||
if (name==="num") {
|
let code = textCode(layout[name].label);
|
||||||
// always white
|
switch(name) {
|
||||||
h = 0;
|
case "title": // also use album and artist
|
||||||
s = 0;
|
code += textCode(layout.album.label);
|
||||||
} else {
|
// fallthrough
|
||||||
// make color depend deterministically on info
|
case "album": // also use artist
|
||||||
let code = textCode(info[name]);
|
code += textCode(layout.artist.label);
|
||||||
switch(name) {
|
|
||||||
case "track": // also use album
|
|
||||||
code += textCode(info.album);
|
|
||||||
// fallthrough
|
|
||||||
case "album": // also use artist
|
|
||||||
code += textCode(info.artist);
|
|
||||||
}
|
|
||||||
h = code%360;
|
|
||||||
s = 0.7;
|
|
||||||
}
|
}
|
||||||
v = brightness();
|
let rgb;
|
||||||
const rgb = hsv2rgb(h, s, v);
|
if (g.getBPP()===3) {
|
||||||
return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b);
|
// only pick 3-bit colors, always at full brightness
|
||||||
|
rgb = [code&1, (code&2)/2, (code&4)/4];
|
||||||
|
if (g.setColor(rgb[0], rgb[1], rgb[2]).getColor()===g.theme.bg) {
|
||||||
|
// avoid picking the bg color
|
||||||
|
rgb = rgb.map(c => 1-c);
|
||||||
|
}
|
||||||
|
return "#"+f2hex(rgb[0])+f2hex(rgb[1])+f2hex(rgb[2]);
|
||||||
|
} else {
|
||||||
|
// pick any hue, adjust for brightness
|
||||||
|
const h = code%360, s = 0.7, b = brightness();
|
||||||
|
return E.HSBtoRGB(h/360, s, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render scrolling title
|
||||||
|
* @param l
|
||||||
|
*/
|
||||||
|
function rScroller(l) {
|
||||||
|
g.setFont("Vector", Math.round(g.getHeight()*l.fsz.slice(0, -1)/100));
|
||||||
|
const w = g.stringWidth(l.label)+40,
|
||||||
|
y = l.y+l.h/2;
|
||||||
|
l.offset = l.offset%w;
|
||||||
|
g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||||
|
.setColor(l.col)
|
||||||
|
.setFontAlign(-1, 0) // left center
|
||||||
|
.clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1)
|
||||||
|
.drawString(l.label, l.x-l.offset+40, y)
|
||||||
|
.drawString(l.label, l.x-l.offset+40+w, y);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Remember track color until info changes
|
* Render title
|
||||||
* Because we need this every time we move the scroller
|
* @param l
|
||||||
* @return {string}
|
|
||||||
*/
|
*/
|
||||||
function trackColor() {
|
function rTitle(l) {
|
||||||
if (!("track_color" in info) || fade) {
|
if (l.offset!==null) {
|
||||||
info.track_color = infoColor("track");
|
rScroller(l); // already scrolling
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return info.track_color;
|
let size = fitText(l.label);
|
||||||
|
if (size<l.h/2) {
|
||||||
|
// the title is too long: start the scroller
|
||||||
|
scrollStart();
|
||||||
|
} else {
|
||||||
|
rInfo(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Render info field
|
||||||
|
* @param l
|
||||||
|
*/
|
||||||
|
function rInfo(l) {
|
||||||
|
let size = fitText(l.label);
|
||||||
|
if (size>l.h) {
|
||||||
|
size = l.h;
|
||||||
|
}
|
||||||
|
g.setFont("Vector", size)
|
||||||
|
.setFontAlign(0, -1) // center top
|
||||||
|
.drawString(l.label, l.x+l.w/2, l.y);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Render icon
|
||||||
|
* @param l
|
||||||
|
*/
|
||||||
|
function rIcon(l) {
|
||||||
|
const x2 = l.x+l.w-1,
|
||||||
|
y2 = l.y+l.h-1;
|
||||||
|
switch(l.icon) {
|
||||||
|
case "pause":
|
||||||
|
const w13 = l.w/3;
|
||||||
|
g.drawRect(l.x, l.y, l.x+w13, y2);
|
||||||
|
g.drawRect(l.x+l.w-w13, l.y, x2, y2);
|
||||||
|
break;
|
||||||
|
case "play":
|
||||||
|
g.drawPoly([
|
||||||
|
l.x, l.y,
|
||||||
|
x2, l.y+l.h/2,
|
||||||
|
l.x, y2,
|
||||||
|
], true);
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
const w15 = l.w*1/5;
|
||||||
|
g.drawPoly([
|
||||||
|
x2, l.y,
|
||||||
|
l.x+w15, l.y+l.h/2,
|
||||||
|
x2, y2,
|
||||||
|
], true);
|
||||||
|
g.drawRect(l.x, l.y, l.x+w15, y2);
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
|
const w45 = l.w*4/5;
|
||||||
|
g.drawPoly([
|
||||||
|
l.x, l.y,
|
||||||
|
l.x+w45, l.y+l.h/2,
|
||||||
|
l.x, y2,
|
||||||
|
], true);
|
||||||
|
g.drawRect(l.x+w45, l.y, x2, y2);
|
||||||
|
break;
|
||||||
|
default: // red X
|
||||||
|
console.log(`Unknown icon: ${l.icon}`);
|
||||||
|
g.setColor("#f00")
|
||||||
|
.drawRect(l.x, l.y, x2, y2)
|
||||||
|
.drawLine(l.x, l.y, x2, y2)
|
||||||
|
.drawLine(l.x, y2, x2, l.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let layout;
|
||||||
|
function makeUI() {
|
||||||
|
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
delete (global.gbmusic_active);
|
||||||
|
const Layout = require("Layout");
|
||||||
|
layout = new Layout({
|
||||||
|
type: "v", c: [
|
||||||
|
{
|
||||||
|
type: "h", fillx: 1, c: [
|
||||||
|
{id: "time", type: "txt", label: "88:88", valign: -1, halign: -1, font: "8%", bgCol: g.theme.bg},
|
||||||
|
{fillx: 1},
|
||||||
|
{id: "num", type: "txt", label: "88:88", valign: -1, halign: 1, font: "12%", bgCol: g.theme.bg},
|
||||||
|
BANGLE2 ? {} : {id: "up", type: "txt", label: " +", font: "6x8:2"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg},
|
||||||
|
{id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg},
|
||||||
|
{id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg},
|
||||||
|
{height: 10},
|
||||||
|
{
|
||||||
|
type: "h", c: [
|
||||||
|
{width: 3},
|
||||||
|
{id: "prev", type: "custom", height: 15, width: 15, icon: "previous", render: rIcon, bgCol: g.theme.bg},
|
||||||
|
{id: "date", type: "txt", halign: 0, valign: 1, label: "", font: "8%", fillx: 1, bgCol: g.theme.bg},
|
||||||
|
{id: "next", type: "custom", height: 15, width: 15, icon: "next", render: rIcon, bgCol: g.theme.bg},
|
||||||
|
BANGLE2 ? {width: 3} : {id: "down", type: "txt", label: " -", font: "6x8:2"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{height: 10},
|
||||||
|
],
|
||||||
|
}, {lazy: true});
|
||||||
|
layout.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Self-repeating timeouts
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
let tock = -1;
|
||||||
|
function tick() {
|
||||||
|
if (!BANGLE2 && !Bangle.isLCDOn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
if (now.getHours()*60+now.getMinutes()!==tock) {
|
||||||
|
drawDateTime();
|
||||||
|
tock = now.getHours()*60+now.getMinutes();
|
||||||
|
}
|
||||||
|
setTimeout(tick, 1000); // we only show minute precision anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out while paused and auto closing
|
||||||
|
let fade = null;
|
||||||
|
function fadeOut() {
|
||||||
|
if (BANGLE2 || !Bangle.isLCDOn() || !fade) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layout.render();
|
||||||
|
setTimeout(fadeOut, 500);
|
||||||
|
}
|
||||||
|
function brightness() {
|
||||||
|
if (!fade) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return Math.max(0, 1-((Date.now()-fade)/POUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll long track names
|
||||||
|
// use an interval to get smooth movement
|
||||||
|
let iScroll;
|
||||||
|
function scroll() {
|
||||||
|
layout.title.offset += 10;
|
||||||
|
rScroller(layout.title);
|
||||||
|
}
|
||||||
|
function scrollStart() {
|
||||||
|
if (layout.title.offset!==null) {
|
||||||
|
return; // already started
|
||||||
|
}
|
||||||
|
layout.title.offset = 0;
|
||||||
|
if (BANGLE2 || Bangle.isLCDOn()) {
|
||||||
|
if (!iScroll) {
|
||||||
|
iScroll = setInterval(scroll, 200);
|
||||||
|
}
|
||||||
|
rScroller(layout.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function scrollStop() {
|
||||||
|
if (iScroll) {
|
||||||
|
clearInterval(iScroll);
|
||||||
|
iScroll = null;
|
||||||
|
}
|
||||||
|
layout.title.offset = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
@ -172,10 +277,9 @@ function trackColor() {
|
||||||
* Draw date and time
|
* Draw date and time
|
||||||
*/
|
*/
|
||||||
function drawDateTime() {
|
function drawDateTime() {
|
||||||
const now = new Date;
|
const now = new Date();
|
||||||
const l = require("locale");
|
const l = require("locale");
|
||||||
const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||||
let time;
|
|
||||||
if (is12) {
|
if (is12) {
|
||||||
const d12 = new Date(now.getTime());
|
const d12 = new Date(now.getTime());
|
||||||
const hour = d12.getHours();
|
const hour = d12.getHours();
|
||||||
|
|
@ -184,29 +288,35 @@ function drawDateTime() {
|
||||||
} else if (hour>12) {
|
} else if (hour>12) {
|
||||||
d12.setHours(hour-12);
|
d12.setHours(hour-12);
|
||||||
}
|
}
|
||||||
time = l.time(d12, true)+l.meridian(now);
|
layout.time.label = l.time(d12, true)+l.meridian(now);
|
||||||
} else {
|
} else {
|
||||||
time = l.time(now, true);
|
layout.time.label = l.time(now, true);
|
||||||
}
|
}
|
||||||
g.reset();
|
layout.date.label = require("locale").date(now, true);
|
||||||
g.setFont("Vector", 24)
|
layout.render();
|
||||||
.setFontAlign(-1, -1) // top left
|
|
||||||
.clearRect(10, 30, 119, 54)
|
|
||||||
.drawString(time, 10, 30);
|
|
||||||
|
|
||||||
const date = require("locale").date(now, true);
|
|
||||||
g.setFont("Vector", 16)
|
|
||||||
.setFontAlign(0, 1) // bottom center
|
|
||||||
.setClipRect(35, 198, 199, 214)
|
|
||||||
.clearRect(31, 198, 199, 214)
|
|
||||||
.drawString(date, 119, 240-26);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawControls() {
|
||||||
|
let l = layout;
|
||||||
|
const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive
|
||||||
|
if (!BANGLE2) {
|
||||||
|
l.up.col = cc("volumeup" in tCommand);
|
||||||
|
l.down.col = cc("volumedown" in tCommand);
|
||||||
|
}
|
||||||
|
l.prev.icon = (stat==="play") ? "pause" : "prev";
|
||||||
|
l.prev.col = cc("prev" in tCommand || "pause" in tCommand);
|
||||||
|
l.next.icon = (stat==="play") ? "next" : "play";
|
||||||
|
l.next.col = cc("next" in tCommand || "play" in tCommand);
|
||||||
|
layout.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// GB event handlers
|
||||||
|
///////////////////////
|
||||||
/**
|
/**
|
||||||
* Draw track number and total count
|
* Mangle track number and total count for display
|
||||||
* @param {boolean} clr - Clear area before redrawing?
|
|
||||||
*/
|
*/
|
||||||
function drawNum(clr) {
|
function formatNum(info) {
|
||||||
let num = "";
|
let num = "";
|
||||||
if ("n" in info && info.n>0) {
|
if ("n" in info && info.n>0) {
|
||||||
num = "#"+info.n;
|
num = "#"+info.n;
|
||||||
|
|
@ -214,198 +324,26 @@ function drawNum(clr) {
|
||||||
num += "/"+info.c;
|
num += "/"+info.c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.reset();
|
return num;
|
||||||
g.setFont("Vector", 30)
|
|
||||||
.setFontAlign(1, -1); // top right
|
|
||||||
if (clr) {
|
|
||||||
g.clearRect(225, 30, 120, 60);
|
|
||||||
}
|
|
||||||
g.drawString(num, 225, 30);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Clear rectangle used by track title
|
|
||||||
*/
|
|
||||||
function clearTrack() {
|
|
||||||
g.clearRect(0, 60, 239, 119);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Draw track title
|
|
||||||
* @param {boolean} clr - Clear area before redrawing?
|
|
||||||
*/
|
|
||||||
function drawTrack(clr) {
|
|
||||||
let size = fitText(info.track);
|
|
||||||
if (size<25) {
|
|
||||||
// the title is too long: start the scroller
|
|
||||||
scrollStart();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
scrollStop();
|
|
||||||
}
|
|
||||||
// stationary track
|
|
||||||
if (size>40) {
|
|
||||||
size = 40;
|
|
||||||
}
|
|
||||||
g.reset();
|
|
||||||
g.setFont("Vector", size)
|
|
||||||
.setFontAlign(0, 1) // center bottom
|
|
||||||
.setColor(trackColor());
|
|
||||||
if (clr) {
|
|
||||||
clearTrack();
|
|
||||||
}
|
|
||||||
g.drawString(info.track, 119, 109);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Draw scrolling track title
|
|
||||||
*/
|
|
||||||
function drawScroller() {
|
|
||||||
g.reset();
|
|
||||||
g.setFont("Vector", 40);
|
|
||||||
const w = g.stringWidth(info.track)+40;
|
|
||||||
offset = offset%w;
|
|
||||||
g.setFontAlign(-1, 1) // left bottom
|
|
||||||
.setColor(trackColor());
|
|
||||||
clearTrack();
|
|
||||||
g.drawString(info.track, -offset+40, 109)
|
|
||||||
.drawString(info.track, -offset+40+w, 109);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw track artist and album
|
|
||||||
* @param {boolean} clr - Clear area before redrawing?
|
|
||||||
*/
|
|
||||||
function drawArtistAlbum(clr) {
|
|
||||||
// we just use small enough fonts to make these always fit
|
|
||||||
// calculate stuff before clear+redraw
|
|
||||||
const aCol = infoColor("artist");
|
|
||||||
const bCol = infoColor("album");
|
|
||||||
let aSiz = fitText(info.artist);
|
|
||||||
if (aSiz>30) {
|
|
||||||
aSiz = 30;
|
|
||||||
}
|
|
||||||
let bSiz = fitText(info.album);
|
|
||||||
if (bSiz>20) {
|
|
||||||
bSiz = 20;
|
|
||||||
}
|
|
||||||
g.reset();
|
|
||||||
if (clr) {
|
|
||||||
g.clearRect(0, 120, 240, 189);
|
|
||||||
}
|
|
||||||
let top = 124;
|
|
||||||
if (info.artist) {
|
|
||||||
g.setFont("Vector", aSiz)
|
|
||||||
.setFontAlign(0, -1) // center top
|
|
||||||
.setColor(aCol)
|
|
||||||
.drawString(info.artist, 119, top);
|
|
||||||
top += aSiz+4; // fit album neatly under artist
|
|
||||||
}
|
|
||||||
if (info.album) {
|
|
||||||
g.setFont("Vector", bSiz)
|
|
||||||
.setFontAlign(0, -1) // center top
|
|
||||||
.setColor(bCol)
|
|
||||||
.drawString(info.album, 119, top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} icon Icon name
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} s Icon size
|
|
||||||
*/
|
|
||||||
function drawIcon(icon, x, y, s) {
|
|
||||||
({
|
|
||||||
pause: function(x, y, s) {
|
|
||||||
const w1 = s/3;
|
|
||||||
g.drawRect(x, y, x+w1, y+s);
|
|
||||||
g.drawRect(x+s-w1, y, x+s, y+s);
|
|
||||||
},
|
|
||||||
play: function(x, y, s) {
|
|
||||||
g.drawPoly([
|
|
||||||
x, y,
|
|
||||||
x+s, y+s/2,
|
|
||||||
x, y+s,
|
|
||||||
], true);
|
|
||||||
},
|
|
||||||
previous: function(x, y, s) {
|
|
||||||
const w2 = s*1/5;
|
|
||||||
g.drawPoly([
|
|
||||||
x+s, y,
|
|
||||||
x+w2, y+s/2,
|
|
||||||
x+s, y+s,
|
|
||||||
], true);
|
|
||||||
g.drawRect(x, y, x+w2, y+s);
|
|
||||||
},
|
|
||||||
next: function(x, y, s) {
|
|
||||||
const w2 = s*4/5;
|
|
||||||
g.drawPoly([
|
|
||||||
x, y,
|
|
||||||
x+w2, y+s/2,
|
|
||||||
x, y+s,
|
|
||||||
], true);
|
|
||||||
g.drawRect(x+w2, y, x+s, y+s);
|
|
||||||
},
|
|
||||||
})[icon](x, y, s);
|
|
||||||
}
|
|
||||||
function controlColor(ctrl) {
|
|
||||||
return (ctrl in tCommand) ? "#ff0000" : "#008800";
|
|
||||||
}
|
|
||||||
function drawControl(ctrl, x, y) {
|
|
||||||
g.setColor(controlColor(ctrl));
|
|
||||||
const s = 20;
|
|
||||||
if (stat!==controlState) {
|
|
||||||
g.clearRect(x, y, x+s, y+s);
|
|
||||||
}
|
|
||||||
drawIcon(ctrl, x, y, s);
|
|
||||||
}
|
|
||||||
let controlState;
|
|
||||||
function drawControls() {
|
|
||||||
g.reset();
|
|
||||||
if (stat==="play") {
|
|
||||||
// left touch
|
|
||||||
drawControl("pause", 10, 190);
|
|
||||||
// right touch
|
|
||||||
drawControl("next", 200, 190);
|
|
||||||
} else {
|
|
||||||
drawControl("previous", 10, 190);
|
|
||||||
drawControl("play", 200, 190);
|
|
||||||
}
|
|
||||||
g.setFont("6x8", 2);
|
|
||||||
// BTN1
|
|
||||||
g.setFontAlign(1, -1);
|
|
||||||
g.setColor(controlColor("volumeup"));
|
|
||||||
g.drawString("+", 240, 30);
|
|
||||||
// BTN2
|
|
||||||
g.setFontAlign(1, 1);
|
|
||||||
g.setColor(controlColor("volumedown"));
|
|
||||||
g.drawString("-", 240, 210);
|
|
||||||
controlState = stat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} [clr=true] Clear area before redrawing?
|
|
||||||
*/
|
|
||||||
function drawMusic(clr) {
|
|
||||||
clr = !(clr===false); // undefined means yes
|
|
||||||
drawNum(clr);
|
|
||||||
drawTrack(clr);
|
|
||||||
drawArtistAlbum(clr);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// GB event handlers
|
|
||||||
///////////////////////
|
|
||||||
/**
|
/**
|
||||||
* Update music info
|
* Update music info
|
||||||
* @param {Object} e - Gadgetbridge musicinfo event
|
* @param {Object} info - Gadgetbridge musicinfo event
|
||||||
*/
|
*/
|
||||||
function musicInfo(e) {
|
function musicInfo(info) {
|
||||||
info = e;
|
scrollStop();
|
||||||
delete (info.t);
|
layout.title.label = info.track || "";
|
||||||
offset = null;
|
layout.album.label = info.album || "";
|
||||||
if (Bangle.isLCDOn()) {
|
layout.artist.label = info.artist || "";
|
||||||
drawMusic();
|
// color depends on all labels
|
||||||
}
|
layout.title.col = infoColor("title");
|
||||||
|
layout.album.col = infoColor("album");
|
||||||
|
layout.artist.col = infoColor("artist");
|
||||||
|
layout.num.label = formatNum(info);
|
||||||
|
layout.render();
|
||||||
|
rTitle(layout.title); // force redraw of title, or scroller might break
|
||||||
|
// reset auto exit interval
|
||||||
if (tIxt) {
|
if (tIxt) {
|
||||||
clearTimeout(tIxt);
|
clearTimeout(tIxt);
|
||||||
tIxt = null;
|
tIxt = null;
|
||||||
|
|
@ -435,7 +373,6 @@ function musicState(e) {
|
||||||
tIxt = null;
|
tIxt = null;
|
||||||
}
|
}
|
||||||
fade = null;
|
fade = null;
|
||||||
delete info.track_color;
|
|
||||||
if (auto) { // auto opened -> auto close
|
if (auto) { // auto opened -> auto close
|
||||||
switch(stat) {
|
switch(stat) {
|
||||||
case "stop": // never actually happens with my phone :-(
|
case "stop": // never actually happens with my phone :-(
|
||||||
|
|
@ -444,7 +381,7 @@ function musicState(e) {
|
||||||
case "play":
|
case "play":
|
||||||
// if inactive for double song duration (or an hour if unknown), load the clock
|
// if inactive for double song duration (or an hour if unknown), load the clock
|
||||||
// i.e. phone finished playing without bothering to notify the watch
|
// i.e. phone finished playing without bothering to notify the watch
|
||||||
tIxt = setTimeout(load, (info.dur*2000) || IOUT);
|
tIxt = setTimeout(load, (e.dur*2000) || IOUT);
|
||||||
break;
|
break;
|
||||||
case "pause":
|
case "pause":
|
||||||
default:
|
default:
|
||||||
|
|
@ -456,8 +393,7 @@ function musicState(e) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Bangle.isLCDOn()) {
|
if (BANGLE2 || Bangle.isLCDOn()) {
|
||||||
drawMusic(false); // redraw in case we were fading out but resumed play
|
|
||||||
drawControls();
|
drawControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,30 +409,34 @@ function musicState(e) {
|
||||||
*/
|
*/
|
||||||
let tPress, nPress = 0;
|
let tPress, nPress = 0;
|
||||||
function startButtonWatches() {
|
function startButtonWatches() {
|
||||||
// BTN1/3: volume control
|
let btn = BTN1;
|
||||||
// Wait for falling edge to avoid messing with volume while long-pressing BTN3
|
if (!BANGLE2) {
|
||||||
// to reload the watch (and same for BTN2 for consistency)
|
// BTN1/3: volume control
|
||||||
setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"});
|
// Wait for falling edge to avoid messing with volume while long-pressing BTN3
|
||||||
setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"});
|
// to reload the watch (and same for BTN2 for consistency)
|
||||||
|
setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"});
|
||||||
|
setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"});
|
||||||
|
btn = BTN2;
|
||||||
|
}
|
||||||
|
|
||||||
// BTN2: long-press for launcher, otherwise depends on number of presses
|
// middle button: long-press for launcher, otherwise depends on number of presses
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
if (nPress===0) {
|
if (nPress===0) {
|
||||||
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
tPress = setTimeout(() => {Bangle.showLauncher();}, 3000);
|
||||||
}
|
}
|
||||||
}, BTN2, {repeat: true, edge: "rising"});
|
}, btn, {repeat: true, edge: "rising"});
|
||||||
const s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
const s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||||
if (s.simpleButton) {
|
if (s.simpleButton) {
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
clearTimeout(tPress);
|
clearTimeout(tPress);
|
||||||
togglePlay();
|
togglePlay();
|
||||||
}, BTN2, {repeat: true, edge: "falling"});
|
}, btn, {repeat: true, edge: "falling"});
|
||||||
} else {
|
} else {
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
nPress++;
|
nPress++;
|
||||||
clearTimeout(tPress);
|
clearTimeout(tPress);
|
||||||
tPress = setTimeout(handleButton2Press, 500);
|
tPress = setTimeout(handleButton2Press, 500);
|
||||||
}, BTN2, {repeat: true, edge: "falling"});
|
}, btn, {repeat: true, edge: "falling"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleButton2Press() {
|
function handleButton2Press() {
|
||||||
|
|
@ -524,7 +464,7 @@ let tCommand = {};
|
||||||
*/
|
*/
|
||||||
function sendCommand(command) {
|
function sendCommand(command) {
|
||||||
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
Bluetooth.println(JSON.stringify({t: "music", n: command}));
|
||||||
// for controlColor
|
// for control color
|
||||||
if (command in tCommand) {
|
if (command in tCommand) {
|
||||||
clearTimeout(tCommand[command]);
|
clearTimeout(tCommand[command]);
|
||||||
}
|
}
|
||||||
|
|
@ -539,18 +479,29 @@ function sendCommand(command) {
|
||||||
function togglePlay() {
|
function togglePlay() {
|
||||||
sendCommand(stat==="play" ? "pause" : "play");
|
sendCommand(stat==="play" ? "pause" : "play");
|
||||||
}
|
}
|
||||||
function startTouchWatches() {
|
function pausePrev() {
|
||||||
|
sendCommand(stat==="play" ? "pause" : "previous");
|
||||||
|
}
|
||||||
|
function nextPlay() {
|
||||||
|
sendCommand(stat==="play" ? "next" : "play");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup touch+swipe for Bangle.js 1
|
||||||
|
*/
|
||||||
|
function touch1() {
|
||||||
Bangle.on("touch", side => {
|
Bangle.on("touch", side => {
|
||||||
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
|
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
|
||||||
switch(side) {
|
switch(side) {
|
||||||
case 1:
|
case 1:
|
||||||
sendCommand(stat==="play" ? "pause" : "previous");
|
pausePrev();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
sendCommand(stat==="play" ? "next" : "play");
|
nextPlay();
|
||||||
break;
|
break;
|
||||||
case 3:
|
default:
|
||||||
togglePlay();
|
togglePlay();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.on("swipe", dir => {
|
Bangle.on("swipe", dir => {
|
||||||
|
|
@ -558,16 +509,56 @@ function startTouchWatches() {
|
||||||
sendCommand(dir===1 ? "previous" : "next");
|
sendCommand(dir===1 ? "previous" : "next");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Setup touch+swipe for Bangle.js 2
|
||||||
|
*/
|
||||||
|
function touch2() {
|
||||||
|
Bangle.on("touch", (side, xy) => {
|
||||||
|
const ar = Bangle.appRect;
|
||||||
|
if (xy.x<ar.x+ar.w/3) {
|
||||||
|
pausePrev();
|
||||||
|
} else if (xy.x>ar.x+ar.w*2/3) {
|
||||||
|
nextPlay();
|
||||||
|
} else {
|
||||||
|
togglePlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// swiping
|
||||||
|
let drag;
|
||||||
|
Bangle.on("drag", e => {
|
||||||
|
if (!drag) { // start dragging
|
||||||
|
drag = {x: e.x, y: e.y};
|
||||||
|
} else if (!e.b) { // released
|
||||||
|
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||||
|
drag = null;
|
||||||
|
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||||
|
// horizontal
|
||||||
|
sendCommand(dx>0 ? "previous" : "next");
|
||||||
|
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||||
|
// vertical
|
||||||
|
sendCommand(dy>0 ? "volumedown" : "volumeup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function startTouchWatches() {
|
||||||
|
if (BANGLE2) {
|
||||||
|
touch2();
|
||||||
|
} else {
|
||||||
|
touch1();
|
||||||
|
}
|
||||||
|
}
|
||||||
function startLCDWatch() {
|
function startLCDWatch() {
|
||||||
|
if (BANGLE2) {
|
||||||
|
return; // always keep drawing
|
||||||
|
}
|
||||||
Bangle.on("lcdPower", (on) => {
|
Bangle.on("lcdPower", (on) => {
|
||||||
if (on) {
|
if (on) {
|
||||||
// redraw and resume scrolling
|
// redraw and resume scrolling
|
||||||
tick();
|
tick();
|
||||||
drawMusic();
|
layout.render();
|
||||||
drawControls();
|
|
||||||
fadeOut();
|
fadeOut();
|
||||||
if (offset!==null) {
|
if (offset.offset!==null) {
|
||||||
drawScroller();
|
|
||||||
if (!iScroll) {
|
if (!iScroll) {
|
||||||
iScroll = setInterval(scroll, 200);
|
iScroll = setInterval(scroll, 200);
|
||||||
}
|
}
|
||||||
|
|
@ -585,15 +576,10 @@ function startLCDWatch() {
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// Startup
|
// Startup
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// check for saved music stat (by widget) to load
|
|
||||||
g.clear();
|
g.clear();
|
||||||
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
|
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
delete (global.gbmusic_active);
|
|
||||||
|
|
||||||
function startEmulator() {
|
function startEmulator() {
|
||||||
if (typeof Bluetooth==="undefined") { // emulator!
|
if (typeof Bluetooth==="undefined" || typeof Bluetooth.println==="undefined") { // emulator!
|
||||||
Bluetooth = {
|
Bluetooth = {
|
||||||
println: (line) => {console.log("Bluetooth:", line);},
|
println: (line) => {console.log("Bluetooth:", line);},
|
||||||
};
|
};
|
||||||
|
|
@ -609,6 +595,7 @@ function startWatches() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
makeUI();
|
||||||
// start listening for music updates
|
// start listening for music updates
|
||||||
const _GB = global.GB;
|
const _GB = global.GB;
|
||||||
global.GB = (event) => {
|
global.GB = (event) => {
|
||||||
|
|
@ -628,43 +615,39 @@ function start() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
drawMusic();
|
|
||||||
drawControls();
|
|
||||||
startWatches();
|
startWatches();
|
||||||
tick();
|
tick();
|
||||||
startEmulator();
|
startEmulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
// check for saved music status (by widget) to load
|
||||||
let saved = require("Storage").readJSON("gbmusic.load.json", true);
|
let saved = require("Storage").readJSON("gbmusic.load.json", true);
|
||||||
require("Storage").erase("gbmusic.load.json");
|
require("Storage").erase("gbmusic.load.json");
|
||||||
if (saved) {
|
if (saved) {
|
||||||
// autoloaded: load state was saved by widget
|
// autoloaded: load state was saved by widget
|
||||||
info = saved.info;
|
|
||||||
stat = saved.state;
|
|
||||||
delete saved;
|
|
||||||
auto = true;
|
auto = true;
|
||||||
start();
|
start();
|
||||||
} else {
|
musicInfo(saved.info);
|
||||||
delete saved;
|
musicState(saved.state);
|
||||||
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
return;
|
||||||
if (!("autoStart" in s)) {
|
|
||||||
// user opened the app, but has not picked a setting yet
|
|
||||||
// ask them about autoloading now
|
|
||||||
E.showPrompt(
|
|
||||||
"Automatically load\n"+
|
|
||||||
"when playing music?\n",
|
|
||||||
).then(choice => {
|
|
||||||
s.autoStart = choice;
|
|
||||||
require("Storage").writeJSON("gbmusic.json", s);
|
|
||||||
delete s;
|
|
||||||
setTimeout(start, 0);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
delete s;
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
|
||||||
|
if ("autoStart" in s) {
|
||||||
|
start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user opened the app, but has not picked a autoStart setting yet
|
||||||
|
// ask them about autoloading now
|
||||||
|
E.showPrompt(
|
||||||
|
"Automatically load\n"+
|
||||||
|
"when playing music?\n"
|
||||||
|
).then(choice => {
|
||||||
|
s.autoStart = choice;
|
||||||
|
require("Storage").writeJSON("gbmusic.json", s);
|
||||||
|
setTimeout(start, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -15,7 +15,7 @@ GB({"t":"notify","id":1592721714,"src":"ALARMCLOCKRECEIVER"})
|
||||||
GB({"t":"notify-","id":1592721714})
|
GB({"t":"notify-","id":1592721714})
|
||||||
|
|
||||||
// Weather update (doesn't show a notification, not handled by gbridge app: see weather app)
|
// Weather update (doesn't show a notification, not handled by gbridge app: see weather app)
|
||||||
GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"loc":"Test City"})
|
GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"wdir":120,"loc":"Test City"})
|
||||||
|
|
||||||
// Nextcloud updated a file
|
// Nextcloud updated a file
|
||||||
GB({"t":"notify","id":1594184421,"src":"Nextcloud","title":"Downloaded","body":"test.file downloaded"})
|
GB({"t":"notify","id":1594184421,"src":"Nextcloud","title":"Downloaded","body":"test.file downloaded"})
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@
|
||||||
0.05: Fix daily summary calculation
|
0.05: Fix daily summary calculation
|
||||||
0.06: Fix daily health summary for movement (a line got deleted!)
|
0.06: Fix daily health summary for movement (a line got deleted!)
|
||||||
0.07: Added coloured bar charts
|
0.07: Added coloured bar charts
|
||||||
|
0.08: Suppress bleed through of E.showMenu's when displaying bar charts
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,9 @@ Bangle.on('swipe', dir => {
|
||||||
|
|
||||||
// use setWatch() as Bangle.setUI("updown",..) interacts with swipes
|
// use setWatch() as Bangle.setUI("updown",..) interacts with swipes
|
||||||
function setButton(fn) {
|
function setButton(fn) {
|
||||||
|
// cancel callback, otherwise a slight up down movement will show the E.showMenu()
|
||||||
|
Bangle.setUI("updown", undefined);
|
||||||
|
|
||||||
if (process.env.HWVERSION == 1)
|
if (process.env.HWVERSION == 1)
|
||||||
btn = setWatch(fn, BTN2);
|
btn = setWatch(fn, BTN2);
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: Core functionnality based entirely on hidmsic
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au"))
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
var storage = require('Storage');
|
||||||
|
|
||||||
|
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||||
|
|
||||||
|
var sendHid, next, prev, toggle, up, down, profile;
|
||||||
|
var lasty = 0;
|
||||||
|
var lastx = 0;
|
||||||
|
|
||||||
|
if (settings.HID=="kbmedia") {
|
||||||
|
profile = 'Music';
|
||||||
|
sendHid = function (code, cb) {
|
||||||
|
try {
|
||||||
|
NRF.sendHIDReport([1,code], () => {
|
||||||
|
NRF.sendHIDReport([1,0], () => {
|
||||||
|
if (cb) cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
next = function (cb) { sendHid(0x01, cb); };
|
||||||
|
prev = function (cb) { sendHid(0x02, cb); };
|
||||||
|
toggle = function (cb) { sendHid(0x10, cb); };
|
||||||
|
up = function (cb) {sendHid(0x40, cb); };
|
||||||
|
down = function (cb) { sendHid(0x80, cb); };
|
||||||
|
} else {
|
||||||
|
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||||
|
if (enable) {
|
||||||
|
settings.HID = "kbmedia";
|
||||||
|
require("Storage").write('setting.json', settings);
|
||||||
|
setTimeout(load, 1000, "hidmsicswipe.app.js");
|
||||||
|
} else setTimeout(load, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawApp() {
|
||||||
|
g.clear();
|
||||||
|
if(Bangle.isLocked()==false) E.showMessage('Swipe', 'Music');
|
||||||
|
else E.showMessage('Locked', 'Music');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
setWatch(function(e) {
|
||||||
|
var len = e.time - e.lastTime;
|
||||||
|
E.showMessage('lock');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
Bangle.setLocked(true);
|
||||||
|
}, BTN1, { edge:"falling",repeat:true,debounce:50});
|
||||||
|
Bangle.on('drag', function(e) {
|
||||||
|
if(!e.b){
|
||||||
|
//console.log(lasty);
|
||||||
|
//console.log(lastx);
|
||||||
|
if(lasty > 40){
|
||||||
|
E.showMessage('down');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
down(() => {});
|
||||||
|
}
|
||||||
|
else if(lasty < -40){
|
||||||
|
E.showMessage('up');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
up(() => {});
|
||||||
|
} else if(lastx < -40){
|
||||||
|
E.showMessage('prev');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
prev(() => {});
|
||||||
|
} else if(lastx > 40){
|
||||||
|
E.showMessage('next');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
next(() => {});
|
||||||
|
} else if(lastx==0 && lasty==0){
|
||||||
|
E.showMessage('play/pause');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
toggle(() => {});
|
||||||
|
}
|
||||||
|
lastx = 0;
|
||||||
|
lasty = 0;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
lastx = lastx + e.dx;
|
||||||
|
lasty = lasty + e.dy;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.on("lock", function(on) {
|
||||||
|
if(!on){
|
||||||
|
E.showMessage('unlock');
|
||||||
|
setTimeout(drawApp, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
drawApp();
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 632 B |
|
|
@ -1 +1,2 @@
|
||||||
0.01: Launch app
|
0.01: Launch app
|
||||||
|
0.02: Swipe left/right to set an alarm.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
# LCARS clock
|
# LCARS clock
|
||||||
|
|
||||||
A simple LCARS inspired clock that shows:
|
A simple LCARS inspired clock.
|
||||||
* Current time
|
Note: To display the steps, its necessary to install
|
||||||
* Current date
|
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget).
|
||||||
* Battery level
|
|
||||||
* Steps
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* Shows the time
|
||||||
|
* Shows the date
|
||||||
|
* Shows the current battery level in %
|
||||||
|
* Shows the number of daily steps
|
||||||
|
* Swipe left/right to activate an alarm
|
||||||
|
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
Made by [David Peer](https://github.com/peerdavid)
|
||||||
|
|
@ -1,43 +1,39 @@
|
||||||
const locale = require('locale');
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assets: Images, fonts etc.
|
* Requirements and globals
|
||||||
*/
|
*/
|
||||||
|
const locale = require('locale');
|
||||||
|
var alarm = -1;
|
||||||
|
|
||||||
var img = {
|
var img = {
|
||||||
width : 176, height : 151, bpp : 3,
|
width : 176, height : 151, bpp : 3,
|
||||||
transparent : 0,
|
transparent : 0,
|
||||||
buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA=="))
|
buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA=="))
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics.prototype.setFontMinaSmall = function(scale) {
|
Graphics.prototype.setFontAntonioMedium = function(scale) {
|
||||||
// Actual height 18 (17 - 0)
|
// Actual height 18 (17 - 0)
|
||||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16));
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16));
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics.prototype.setFontMinaLarge = function(scale) {
|
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
||||||
// Actual height 35 (34 - 0)
|
// Actual height 34 (34 - 1)
|
||||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16));
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Queue drawing every minute
|
* Draw watch face
|
||||||
*/
|
*/
|
||||||
var drawTimeout;
|
var drawTimeout;
|
||||||
function queueDraw() {
|
function queueDraw() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
draw();
|
draw(true);
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
function draw(queue){
|
||||||
* Draw watch face
|
|
||||||
*/
|
|
||||||
function draw(){
|
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||||
|
|
||||||
|
|
@ -48,43 +44,142 @@ function draw(){
|
||||||
var currentDate = new Date();
|
var currentDate = new Date();
|
||||||
var timeStr = locale.time(currentDate,1);
|
var timeStr = locale.time(currentDate,1);
|
||||||
g.setFontAlign(0,0,0);
|
g.setFontAlign(0,0,0);
|
||||||
g.setFontMinaLarge();
|
g.setFontAntonioLarge();
|
||||||
g.drawString(timeStr, 115, 53);
|
g.drawString(timeStr, 100, 50);
|
||||||
|
|
||||||
// Write date
|
// Write date
|
||||||
g.setFontAlign(-1,-1,0);
|
g.setFontAlign(1,-1, 0);
|
||||||
g.setFontMinaSmall();
|
g.setFontAntonioMedium();
|
||||||
|
|
||||||
var dayName = locale.dow(currentDate, true).toUpperCase();
|
var dayName = locale.dow(currentDate, true).toUpperCase();
|
||||||
var day = currentDate.getDate();
|
var day = currentDate.getDate();
|
||||||
g.drawString("DATE:", 40, 107);
|
g.drawString(day, 170, 30);
|
||||||
g.drawString(dayName + " " + day, 100, 105);
|
g.drawString(dayName, 170, 50);
|
||||||
|
|
||||||
|
// Alarm
|
||||||
|
g.setFontAlign(-1,-1,0);
|
||||||
|
g.drawString("TMR:", 30, 107);
|
||||||
|
var alrmText = alarm >= 0 ? "T-"+alarm : "OFF";
|
||||||
|
g.drawString(alrmText, 65, 107);
|
||||||
|
|
||||||
// Draw battery
|
// Draw battery
|
||||||
var bat = E.getBattery();
|
var bat = E.getBattery();
|
||||||
g.drawString("BAT:", 40, 127);
|
var charging = Bangle.isCharging() ? "*" : "";
|
||||||
g.drawString(bat+"%", 100, 127);
|
g.drawString("BAT:", 30, 127);
|
||||||
|
g.drawString(charging + bat+ "%", 65, 127);
|
||||||
|
|
||||||
// Draw steps
|
// Draw steps
|
||||||
var steps = Bangle.getStepCount();
|
var steps = getSteps();
|
||||||
g.drawString("STEP:", 40, 147);
|
g.drawString("STEP:", 30, 147);
|
||||||
g.drawString(steps, 100, 147);
|
g.drawString(steps, 65, 147);
|
||||||
|
|
||||||
|
// GPS
|
||||||
|
var gpsText = Bangle.isGPSOn() ? "ON" : "OFF";
|
||||||
|
g.drawString("GPS:", 115, 107);
|
||||||
|
g.drawString(gpsText, 149, 107);
|
||||||
|
|
||||||
|
|
||||||
|
// HRM
|
||||||
|
var gpsText = Bangle.isHRMOn() ? "ON" : "OFF";
|
||||||
|
g.drawString("HRM:", 115, 127);
|
||||||
|
g.drawString(gpsText, 149, 127);
|
||||||
|
|
||||||
|
// CMP
|
||||||
|
var compassText = Bangle.isCompassOn() ? "ON" : "OFF";
|
||||||
|
g.drawString("CMP:", 115, 147);
|
||||||
|
g.drawString(compassText, 149, 147);
|
||||||
|
|
||||||
// Queue draw in one minute
|
// Queue draw in one minute
|
||||||
queueDraw();
|
if(queue){
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the screen once, at startup
|
/*
|
||||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
* Step counter via widget
|
||||||
|
*/
|
||||||
|
function getSteps() {
|
||||||
|
if (stepsWidget() !== undefined)
|
||||||
|
return stepsWidget().getSteps();
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
|
||||||
// draw immediately at first, queue update
|
function stepsWidget() {
|
||||||
draw();
|
if (WIDGETS.activepedom !== undefined) {
|
||||||
|
return WIDGETS.activepedom;
|
||||||
|
} else if (WIDGETS.wpedom !== undefined) {
|
||||||
|
return WIDGETS.wpedom;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle alarm
|
||||||
|
*/
|
||||||
|
var alarmTimeout;
|
||||||
|
function queueAlarm() {
|
||||||
|
if (alarmTimeout) clearTimeout(alarmTimeout);
|
||||||
|
alarmTimeout = setTimeout(function() {
|
||||||
|
alarmTimeout = undefined;
|
||||||
|
handleAlarm();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAlarm(){
|
||||||
|
|
||||||
|
// Check each minute
|
||||||
|
if(alarm > 0){
|
||||||
|
alarm--;
|
||||||
|
queueAlarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// After n minutes, inform the user
|
||||||
|
if(alarm == 0){
|
||||||
|
alarm = -1;
|
||||||
|
|
||||||
|
var t = 300;
|
||||||
|
Bangle.buzz(t, 1)
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||||
|
.then(() => Bangle.buzz(t, 1))
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||||
|
.then(() => Bangle.buzz(t, 1))
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||||
|
.then(() => Bangle.buzz(t, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
draw(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Stop updates when LCD is off, restart when on
|
/*
|
||||||
|
* Swipe to set an alarm
|
||||||
|
*/
|
||||||
|
Bangle.on('swipe',function(dir) {
|
||||||
|
// Increase alarm
|
||||||
|
if(dir == -1){
|
||||||
|
alarm = alarm < 0 ? 0 : alarm;
|
||||||
|
alarm += 5;
|
||||||
|
queueAlarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease alarm
|
||||||
|
if(dir == +1){
|
||||||
|
alarm -= 5;
|
||||||
|
alarm = alarm <= 0 ? -1 : alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
draw(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stop updates when LCD is off, restart when on
|
||||||
|
*/
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (on) {
|
if (on) {
|
||||||
draw(); // draw immediately, queue redraw
|
draw(true); // draw immediately, queue redraw
|
||||||
} else { // stop draw timer
|
} else { // stop draw timer
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
|
@ -94,6 +189,12 @@ Bangle.on('lcdPower',on=>{
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
// Load widgets
|
// Load widgets - needed by draw
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
|
// Clear the screen once, at startup and draw clock
|
||||||
|
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||||
|
draw(true);
|
||||||
|
|
||||||
|
// After drawing the watch face, we can draw the widgets
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -2,5 +2,6 @@
|
||||||
0.02: Course marker
|
0.02: Course marker
|
||||||
0.03: Tilt compensation and calibration
|
0.03: Tilt compensation and calibration
|
||||||
0.04: Fix Font size
|
0.04: Fix Font size
|
||||||
|
0.05: Inital portable version
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,20 @@ This is a tilt and roll compensated compass with a linear display. The compass w
|
||||||
|
|
||||||
## Calibration
|
## Calibration
|
||||||
|
|
||||||
Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 30 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN3*. Calibration data is recorded in a storage file named `magnav.json`.
|
Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 20 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN2*. Calibration data is recorded in a storage file named `magnav.json`.
|
||||||
|
|
||||||
|
Note: Charging your Bangle due to the magnetic connector clamp seems to require recalibration afterwards for accurate readings.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
*BTN1* - switches to your selected clock app.
|
*BTN1* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it..
|
||||||
|
(Swipe UP on Bangle 2)
|
||||||
|
|
||||||
*BTN2* - switches to the app launcher.
|
*BTN2* - invokes calibration ( can be cancelled if pressed accidentally).
|
||||||
|
(*BTN1* on Bangle 2)
|
||||||
|
|
||||||
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
*BTN3* - cancels the marker (blue circle not displayed)
|
||||||
|
(swipe DOWN on Bangle 2)
|
||||||
*Touch Left* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it.
|
|
||||||
|
|
||||||
*Touch Right* - cancels the marker (blue circle not displayed).
|
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
var Yoff=80,pal2color=new Uint16Array([0,65535,2047,50712],0,2),buf=Graphics.createArrayBuffer(240,60,2,{msb:!0});Bangle.setLCDTimeout(30);function flip(b,c){g.drawImage({width:240,height:60,bpp:2,buffer:b.buffer,palette:pal2color},0,c);b.clear()}var labels="N NE E SE S SW W NW".split(" "),brg=null;
|
|
||||||
function drawCompass(b){buf.setColor(1);buf.setFont("Vector",24);var c=b-90;0>c&&(c+=360);buf.fillRect(28,45,212,49);var a=30,d=15-c%15;15>d?a+=d:d=0;for(var e=d;e<=180-d;e+=15){var f=c+e;0==f%90?(buf.drawString(labels[Math.floor(f/45)%8],a-8,0),buf.fillRect(a-2,25,a+2,45)):0==f%45?(buf.drawString(labels[Math.floor(f/45)%8],a-12,0),buf.fillRect(a-2,30,a+2,45)):0==f%15&&buf.fillRect(a,35,a+1,45);a+=15}brg&&(b=brg-b,180<b&&(b-=360),-180>b&&(b+=360),b+=120,30>b&&(b=14),210<b&&(b=226),buf.setColor(2),
|
|
||||||
buf.fillCircle(b,40,8));flip(buf,Yoff)}var heading=0;function newHeading(b,c){var a=Math.abs(b-c),d=b>c?1:-1;180<=a&&(a=360-a,d=-d);if(2>a)return c;a=c+d*(1+Math.round(a/5));0>a&&(a+=360);360<a&&(a-=360);return a}var candraw=!1,CALIBDATA=require("Storage").readJSON("magnav.json",1)||null;
|
|
||||||
function tiltfixread(b,c){Date.now();var a=Bangle.getCompass(),d=Bangle.getAccel();a.dx=(a.x-b.x)*c.x;a.dy=(a.y-b.y)*c.y;a.dz=(a.z-b.z)*c.z;var e=Math.atan(-d.x/-d.z),f=Math.cos(e);e=Math.sin(e);d=Math.atan(-d.y/(-d.x*e-d.z*f));var k=Math.sin(d);a=180*Math.atan2(a.dz*e-a.dx*f,a.dy*Math.cos(d)+a.dx*e*k+a.dz*f*k)/Math.PI;0>a&&(a+=360);return a}
|
|
||||||
function reading(){var b=tiltfixread(CALIBDATA.offset,CALIBDATA.scale);heading=newHeading(b,heading);drawCompass(heading);buf.setColor(1);buf.setFont("6x8",2);buf.setFontAlign(-1,-1);buf.drawString("o",170,0);buf.setFont("Vector",54);b=Math.round(heading);var c=b.toString();buf.drawString(10>b?"00"+c:100>b?"0"+c:c,70,10);flip(buf,Yoff+80)}
|
|
||||||
function calibrate(){var b=-32E3,c=-32E3,a=-32E3,d=32E3,e=32E3,f=32E3,k=setInterval(function(){var h=Bangle.getCompass();b=h.x>b?h.x:b;c=h.y>c?h.y:c;a=h.z>a?h.z:a;d=h.x<d?h.x:d;e=h.y<e?h.y:e;f=h.z<f?h.z:f},100);return new Promise(function(h){setTimeout(function(){k&&clearInterval(k);var m=(b-d)/2,n=(c-e)/2,p=(a-f)/2,l=(m+n+p)/3;h({offset:{x:(b+d)/2,y:(c+e)/2,z:(a+f)/2},scale:{x:l/m,y:l/n,z:l/p}})},1E4)})}
|
|
||||||
function docalibrate(b,c){function a(d){d?(buf.setColor(1),buf.setFont("Vector",24),buf.setFontAlign(0,-1),buf.drawString("Fig 8s to",120,0),buf.drawString("Calibrate",120,26),flip(buf,Yoff),calibrate().then(function(e){require("Storage").write("magnav.json",e);CALIBDATA=e;startdraw();setButtons()})):(startdraw(),setTimeout(setButtons,1E3))}void 0===c&&(c=!1);stopdraw();clearWatch();c?E.showAlert("takes 10 seconds","Calibrate").then(a.bind(null,!0)):E.showPrompt("takes 10 seconds",{title:"Calibrate",
|
|
||||||
buttons:{Start:!0,Cancel:!1}}).then(a)}Bangle.on("touch",function(b){candraw&&(1==b&&(brg=heading),2==b&&(brg=null))});var intervalRef;function startdraw(){g.clear();g.setColor(1,.5,.5);g.fillPoly([120,Yoff+60,110,Yoff+80,130,Yoff+80]);g.setColor(1,1,1);Bangle.drawWidgets();candraw=!0;intervalRef=setInterval(reading,200)}function stopdraw(){candraw=!1;intervalRef&&clearInterval(intervalRef)}
|
|
||||||
function setButtons(){setWatch(function(){load()},BTN1,{repeat:!1,edge:"falling"});setWatch(Bangle.showLauncher,BTN2,{repeat:!1,edge:"falling"});setWatch(docalibrate,BTN3,{repeat:!1,edge:"falling"})}var SCREENACCESS={withApp:!0,request:function(){this.withApp=!1;stopdraw();clearWatch()},release:function(){this.withApp=!0;startdraw();setButtons()}};Bangle.on("lcdPower",function(b){SCREENACCESS.withApp&&(b?startdraw():stopdraw())});Bangle.on("kill",function(){Bangle.setCompassPower(0)});Bangle.loadWidgets();
|
|
||||||
Bangle.setCompassPower(1);CALIBDATA?(startdraw(),setButtons()):docalibrate({},!0);
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
const Yoff = 80;
|
const Yoff = 80;
|
||||||
var pal2color = new Uint16Array([0x0000,0xffff,0x07ff,0xC618],0,2);
|
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg,g.theme.fg2,0xC618],0,2);
|
||||||
var buf = Graphics.createArrayBuffer(240,60,2,{msb:true});
|
var buf = Graphics.createArrayBuffer(240,60,2,{msb:true});
|
||||||
Bangle.setLCDTimeout(30);
|
Bangle.setLCDTimeout(30);
|
||||||
|
|
||||||
|
|
@ -13,6 +12,7 @@ const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||||
var brg=null;
|
var brg=null;
|
||||||
|
|
||||||
function drawCompass(course) {
|
function drawCompass(course) {
|
||||||
|
"ram"
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
buf.setFont("Vector",24);
|
buf.setFont("Vector",24);
|
||||||
var start = course-90;
|
var start = course-90;
|
||||||
|
|
@ -63,7 +63,7 @@ var candraw = false;
|
||||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||||
|
|
||||||
function tiltfixread(O,S){
|
function tiltfixread(O,S){
|
||||||
var start = Date.now();
|
"ram"
|
||||||
var m = Bangle.getCompass();
|
var m = Bangle.getCompass();
|
||||||
var g = Bangle.getAccel();
|
var g = Bangle.getAccel();
|
||||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||||
|
|
@ -117,13 +117,20 @@ function calibrate(){
|
||||||
var avg = (delta.x+delta.y+delta.z)/3;
|
var avg = (delta.x+delta.y+delta.z)/3;
|
||||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||||
resolve({offset:offset,scale:scale});
|
resolve({offset:offset,scale:scale});
|
||||||
},30000);
|
},20000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function docalibrate(e,first){
|
var calibrating=false;
|
||||||
|
function docalibrate(first){
|
||||||
|
calibrating=true;
|
||||||
const title = "Calibrate";
|
const title = "Calibrate";
|
||||||
const msg = "takes 30 seconds";
|
const msg = "takes 20 seconds";
|
||||||
|
function restart() {
|
||||||
|
calibrating=false;
|
||||||
|
setButtons();
|
||||||
|
startdraw();
|
||||||
|
}
|
||||||
function action(b){
|
function action(b){
|
||||||
if (b) {
|
if (b) {
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
|
|
@ -133,31 +140,22 @@ function docalibrate(e,first){
|
||||||
buf.drawString("Calibrate",120,26);
|
buf.drawString("Calibrate",120,26);
|
||||||
flip(buf,Yoff);
|
flip(buf,Yoff);
|
||||||
calibrate().then((r)=>{
|
calibrate().then((r)=>{
|
||||||
|
CALIBDATA=r;
|
||||||
require("Storage").write("magnav.json",r);
|
require("Storage").write("magnav.json",r);
|
||||||
CALIBDATA = r;
|
restart()
|
||||||
startdraw();
|
|
||||||
setButtons();
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
startdraw();
|
restart()
|
||||||
setTimeout(setButtons,1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first===undefined) first=false;
|
if (first===undefined) first=false;
|
||||||
stopdraw();
|
stopdraw();
|
||||||
clearWatch();
|
|
||||||
if (first)
|
if (first)
|
||||||
E.showAlert(msg,title).then(action.bind(null,true));
|
E.showAlert(msg,title).then(action.bind(null,true));
|
||||||
else
|
else
|
||||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('touch', function(b) {
|
|
||||||
if(!candraw) return;
|
|
||||||
if(b==1) brg=heading;
|
|
||||||
if(b==2) brg=null;
|
|
||||||
});
|
|
||||||
|
|
||||||
var intervalRef;
|
var intervalRef;
|
||||||
|
|
||||||
function startdraw(){
|
function startdraw(){
|
||||||
|
|
@ -176,29 +174,17 @@ function stopdraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setButtons(){
|
function setButtons(){
|
||||||
setWatch(()=>{load();}, BTN1, {repeat:false,edge:"falling"});
|
function actions(v){
|
||||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
if (!v) docalibrate(false);
|
||||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
else if (v==1) brg=null;
|
||||||
|
else brg=heading;
|
||||||
|
}
|
||||||
|
Bangle.setUI("updown",actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
var SCREENACCESS = {
|
|
||||||
withApp:true,
|
|
||||||
request:function(){
|
|
||||||
this.withApp=false;
|
|
||||||
stopdraw();
|
|
||||||
clearWatch();
|
|
||||||
},
|
|
||||||
release:function(){
|
|
||||||
this.withApp=true;
|
|
||||||
startdraw();
|
|
||||||
setButtons();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.on('lcdPower',function(on) {
|
Bangle.on('lcdPower',function(on) {
|
||||||
if (!SCREENACCESS.withApp) return;
|
|
||||||
if (on) {
|
if (on) {
|
||||||
startdraw();
|
if (!calibrating) startdraw();
|
||||||
} else {
|
} else {
|
||||||
stopdraw();
|
stopdraw();
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +195,7 @@ Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.setCompassPower(1);
|
Bangle.setCompassPower(1);
|
||||||
if (!CALIBDATA)
|
if (!CALIBDATA)
|
||||||
docalibrate({},true);
|
docalibrate(true);
|
||||||
else {
|
else {
|
||||||
startdraw();
|
startdraw();
|
||||||
setButtons();
|
setButtons();
|
||||||
|
|
@ -217,4 +203,3 @@ else {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
|
||||||
|
const Ypos = 40;
|
||||||
|
|
||||||
|
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||||
|
var brg=null;
|
||||||
|
|
||||||
|
function drawCompass(course) {
|
||||||
|
"ram"
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.setFont("Vector",18);
|
||||||
|
var start = course-90;
|
||||||
|
if (start<0) start+=360;
|
||||||
|
g.fillRect(16,Ypos+45,160,Ypos+49);
|
||||||
|
var xpos = 16;
|
||||||
|
var frag = 15 - start%15;
|
||||||
|
if (frag<15) xpos+=Math.floor((frag*4)/5); else frag = 0;
|
||||||
|
for (var i=frag;i<=180-frag;i+=15){
|
||||||
|
var res = start + i;
|
||||||
|
if (res%90==0) {
|
||||||
|
g.drawString(labels[Math.floor(res/45)%8],xpos-6,Ypos+6);
|
||||||
|
g.fillRect(xpos-2,Ypos+25,xpos+2,Ypos+45);
|
||||||
|
} else if (res%45==0) {
|
||||||
|
g.drawString(labels[Math.floor(res/45)%8],xpos-9,Ypos+6);
|
||||||
|
g.fillRect(xpos-2,Ypos+30,xpos+2,Ypos+45);
|
||||||
|
} else if (res%15==0) {
|
||||||
|
g.fillRect(xpos,Ypos+35,xpos+1,Ypos+45);
|
||||||
|
}
|
||||||
|
xpos+=12;
|
||||||
|
}
|
||||||
|
if (brg) {
|
||||||
|
var bpos = brg - course;
|
||||||
|
if (bpos>180) bpos -=360;
|
||||||
|
if (bpos<-180) bpos +=360;
|
||||||
|
bpos= Math.floor((bpos*4)/5)+88;
|
||||||
|
if (bpos<16) bpos = 8;
|
||||||
|
if (bpos>160) bpos = 170;
|
||||||
|
g.setColor(g.theme.fg2);
|
||||||
|
g.fillCircle(bpos,Ypos+45,6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var heading = 0;
|
||||||
|
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<2) return h;
|
||||||
|
var hd = h + delta*(1 + Math.round(s/5));
|
||||||
|
if (hd<0) hd+=360;
|
||||||
|
if (hd>360)hd-= 360;
|
||||||
|
return hd;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candraw = false;
|
||||||
|
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||||
|
|
||||||
|
function tiltfixread(O,S){
|
||||||
|
"ram"
|
||||||
|
var m = Bangle.getCompass();
|
||||||
|
var g = Bangle.getAccel();
|
||||||
|
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||||
|
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||||
|
if (d<0) d+=360;
|
||||||
|
var phi = Math.atan(-g.x/-g.z);
|
||||||
|
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||||
|
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||||
|
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||||
|
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||||
|
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||||
|
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||||
|
if (psi<0) psi+=360;
|
||||||
|
return psi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note actual mag is 360-m, error in firmware
|
||||||
|
function reading() {
|
||||||
|
"ram"
|
||||||
|
g.clearRect(0,24,175,175);
|
||||||
|
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||||
|
heading = newHeading(d,heading);
|
||||||
|
drawCompass(heading);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.setFont("6x8",2);
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
g.drawString("o",120,Ypos+80);
|
||||||
|
g.setFont("Vector",40);
|
||||||
|
var course = Math.round(heading);
|
||||||
|
var cs = course.toString();
|
||||||
|
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||||
|
g.drawString(cs,50,Ypos+90);
|
||||||
|
g.setColor(g.theme.fg2);
|
||||||
|
g.fillPoly([88,Ypos+60,78,Ypos+80,98,Ypos+80]);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
function calibrate(){
|
||||||
|
var max={x:-32000, y:-32000, z:-32000},
|
||||||
|
min={x:32000, y:32000, z:32000};
|
||||||
|
var ref = setInterval(()=>{
|
||||||
|
var m = Bangle.getCompass();
|
||||||
|
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);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(()=>{
|
||||||
|
if(ref) clearInterval(ref);
|
||||||
|
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;
|
||||||
|
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||||
|
resolve({offset:offset,scale:scale});
|
||||||
|
},20000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var calibrating=false;
|
||||||
|
function docalibrate(first){
|
||||||
|
calibrating=true;
|
||||||
|
const title = "Calibrate";
|
||||||
|
const msg = "takes 20 seconds";
|
||||||
|
function restart() {
|
||||||
|
calibrating=false;
|
||||||
|
setButtons();
|
||||||
|
startdraw();
|
||||||
|
}
|
||||||
|
function action(b){
|
||||||
|
if (b) {
|
||||||
|
g.clearRect(0,24,175,175);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.setFont("Vector",18);
|
||||||
|
g.setFontAlign(0,-1);
|
||||||
|
g.drawString("Fig 8s to",88,Ypos);
|
||||||
|
g.drawString("Calibrate",88,Ypos+18);
|
||||||
|
g.flip();
|
||||||
|
calibrate().then((r)=>{
|
||||||
|
CALIBDATA=r;
|
||||||
|
require("Storage").write("magnav.json",r);
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first===undefined) first=false;
|
||||||
|
stopdraw();
|
||||||
|
if (first)
|
||||||
|
E.showAlert(msg,title).then(action.bind(null,true));
|
||||||
|
else
|
||||||
|
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
var intervalRef;
|
||||||
|
|
||||||
|
function startdraw(){
|
||||||
|
g.clear(1);
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
candraw = true;
|
||||||
|
intervalRef = setInterval(reading,200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopdraw() {
|
||||||
|
candraw=false;
|
||||||
|
if(intervalRef) {clearInterval(intervalRef);}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtons(){
|
||||||
|
function actions(v){
|
||||||
|
if (!v) docalibrate(false);
|
||||||
|
else if (v==1) brg=null;
|
||||||
|
else brg=heading;
|
||||||
|
}
|
||||||
|
Bangle.setUI("updown",actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.setCompassPower(1);
|
||||||
|
if (!CALIBDATA)
|
||||||
|
docalibrate(true);
|
||||||
|
else {
|
||||||
|
startdraw();
|
||||||
|
setButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -61,6 +61,7 @@ function getMessageImage(msg) {
|
||||||
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
|
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
|
||||||
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
||||||
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
||||||
|
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
|
||||||
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
|
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
|
||||||
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
||||||
if (msg.id=="back") return getBackImage();
|
if (msg.id=="back") return getBackImage();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
0.06: add minute tick for efficiency and nifty A clock
|
0.06: add minute tick for efficiency and nifty A clock
|
||||||
0.07: compatible with Bang;e.js 2
|
0.07: compatible with Bang;e.js 2
|
||||||
0.08: fix minute tick bug
|
0.08: fix minute tick bug
|
||||||
|
0.09: use setUI clockupdown for controls + fix small display bug in nifty face
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ This is a clock app that supports multiple clock faces. The user can switch betw
|
||||||
|
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle.
|
Uses `setUI("clockupdown")`
|
||||||
|
BTN1 & BTH3 switch faces on the Bangle.
|
||||||
|
Touch upper right and lower right quadrant switch faces on the Bangle 2.
|
||||||
|
|
||||||
## Adding a new face
|
## Adding a new face
|
||||||
Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure:
|
Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure:
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ function setButtons(){
|
||||||
startdraw();
|
startdraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Bangle.setUI("leftright", newFace);
|
Bangle.setUI("clockupdown", newFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
E.on('kill',()=>{
|
E.on('kill',()=>{
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
|
const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0));
|
||||||
const minutes = d02(now.getMinutes());
|
const minutes = d02(now.getMinutes());
|
||||||
const day = d02(now.getDay());
|
const day = d02(now.getDate());
|
||||||
const month = d02(now.getMonth() + 1);
|
const month = d02(now.getMonth() + 1);
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
const month2 = locale.month(now, 3);
|
const month2 = locale.month(now, 3);
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -1,3 +1,3 @@
|
||||||
0.01: First version
|
0.01: First version
|
||||||
0.02: Add widget
|
0.02: Add widget
|
||||||
|
0.03: Bangle.js 2 support
|
||||||
|
|
|
||||||
|
|
@ -32,17 +32,16 @@ function formatTime(t) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
const menu = {
|
let menu = {"": {"title": "Quiet Mode"}};
|
||||||
"": {"title": "Quiet Mode"},
|
// "Current Mode""Silent" won't fit on Bangle.js 2
|
||||||
"Current Mode": {
|
menu["Current" + ((process.env.HWVERSION===2)?"":" Mode")]= {
|
||||||
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
|
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
|
||||||
format: v => modeNames[v],
|
format: v => modeNames[v],
|
||||||
onchange: function(v) {
|
onchange: function(v) {
|
||||||
if (v<0) {v = 2;}
|
if (v<0) {v = 2;}
|
||||||
if (v>2) {v = 0;}
|
if (v>2) {v = 0;}
|
||||||
require("qmsched").setMode(v);
|
require("qmsched").setMode(v);
|
||||||
this.value = v;
|
this.value = v;
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
scheds.sort((a, b) => (a.hr-b.hr));
|
scheds.sort((a, b) => (a.hr-b.hr));
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ Eat apples and don't bite your tail.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
- UP: BTN1
|
- BTN1: turn to left
|
||||||
- DOWN: BTN3
|
- BTN2: pause
|
||||||
- LEFT: BTN4
|
- BTN3: turn to right
|
||||||
- RIGHT: BTN5
|
|
||||||
- PAUSE: BTN2
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Fix issue with mode being undefined
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
var sui = Bangle.setUI;
|
var sui = Bangle.setUI;
|
||||||
Bangle.setUI = function(mode, cb) {
|
Bangle.setUI = function(mode, cb) {
|
||||||
sui(mode,cb);
|
sui(mode,cb);
|
||||||
|
if(!mode) return;
|
||||||
if (!mode.startsWith("clock")) return;
|
if (!mode.startsWith("clock")) return;
|
||||||
Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); };
|
Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); };
|
||||||
Bangle.on("swipe", Bangle.swipeHandler);
|
Bangle.on("swipe", Bangle.swipeHandler);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
highres: true,
|
highres: true,
|
||||||
animation : true,
|
animation : true,
|
||||||
frame : 3,
|
frame : 3,
|
||||||
debug: true
|
debug: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@
|
||||||
0.08: Refactor and reduce widget ram usage.
|
0.08: Refactor and reduce widget ram usage.
|
||||||
0.09: Fix crash when weather.json is absent.
|
0.09: Fix crash when weather.json is absent.
|
||||||
0.10: Use new Layout library
|
0.10: Use new Layout library
|
||||||
|
0.11: Bangle.js 2 support
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ function draw() {
|
||||||
layout.hum.label = current.hum+"%";
|
layout.hum.label = current.hum+"%";
|
||||||
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
|
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
|
||||||
layout.wind.label = wind[1];
|
layout.wind.label = wind[1];
|
||||||
layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase();
|
layout.windUnit.label = wind[2] + " " + (current.wrose||'').toUpperCase();
|
||||||
layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1);
|
layout.cond.label = current.txt.charAt(0).toUpperCase()+(current.txt||'').slice(1);
|
||||||
layout.loc.label = current.loc;
|
layout.loc.label = current.loc;
|
||||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
||||||
layout.update();
|
layout.update();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
|
const B2 = process.env.HWVERSION===2;
|
||||||
|
|
||||||
let expiryTimeout;
|
let expiryTimeout;
|
||||||
function scheduleExpiry(json) {
|
function scheduleExpiry(json) {
|
||||||
|
|
@ -54,13 +55,13 @@ scheduleExpiry(storage.readJSON('weather.json')||{});
|
||||||
|
|
||||||
exports.drawIcon = function(cond, x, y, r) {
|
exports.drawIcon = function(cond, x, y, r) {
|
||||||
function drawSun(x, y, r) {
|
function drawSun(x, y, r) {
|
||||||
g.setColor(g.theme.dark ? "#FE0" : "#FC0");
|
g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0"));
|
||||||
g.fillCircle(x, y, r);
|
g.fillCircle(x, y, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCloud(x, y, r, c) {
|
function drawCloud(x, y, r, c) {
|
||||||
const u = r/12;
|
const u = r/12;
|
||||||
if (c==null) c = g.theme.dark ? "#BBB" : "#AAA";
|
if (c==null) c = B2 ? '#FFF': (g.theme.dark ? "#BBB" : "#AAA");
|
||||||
g.setColor(c);
|
g.setColor(c);
|
||||||
g.fillCircle(x-8*u, y+3*u, 4*u);
|
g.fillCircle(x-8*u, y+3*u, 4*u);
|
||||||
g.fillCircle(x-4*u, y-2*u, 5*u);
|
g.fillCircle(x-4*u, y-2*u, 5*u);
|
||||||
|
|
@ -77,7 +78,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBrokenClouds(x, y, r) {
|
function drawBrokenClouds(x, y, r) {
|
||||||
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777");
|
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777"); // dithers on B2, but that's ok
|
||||||
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,23 +88,23 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRainLines(x, y, r) {
|
function drawRainLines(x, y, r) {
|
||||||
g.setColor(g.theme.dark ? "#0CF" : "#07F");
|
g.setColor(B2 ? '#0FF' : (g.theme.dark ? "#0CF" : "#07F"));
|
||||||
const y1 = y+1/2*r;
|
const y1 = y+1/2*r;
|
||||||
const y2 = y+1*r;
|
const y2 = y+1*r;
|
||||||
|
const poly = g.fillPolyAA ? p => g.fillPolyAA(p) : p => g.fillPoly(p);
|
||||||
g.fillPolyAA([
|
poly([
|
||||||
x-6/12*r, y1,
|
x-6/12*r, y1,
|
||||||
x-8/12*r, y2,
|
x-8/12*r, y2,
|
||||||
x-7/12*r, y2,
|
x-7/12*r, y2,
|
||||||
x-5/12*r, y1,
|
x-5/12*r, y1,
|
||||||
]);
|
]);
|
||||||
g.fillPolyAA([
|
poly([
|
||||||
x-2/12*r, y1,
|
x-2/12*r, y1,
|
||||||
x-4/12*r, y2,
|
x-4/12*r, y2,
|
||||||
x-3/12*r, y2,
|
x-3/12*r, y2,
|
||||||
x-1/12*r, y1,
|
x-1/12*r, y1,
|
||||||
]);
|
]);
|
||||||
g.fillPolyAA([
|
poly([
|
||||||
x+2/12*r, y1,
|
x+2/12*r, y1,
|
||||||
x+0/12*r, y2,
|
x+0/12*r, y2,
|
||||||
x+1/12*r, y2,
|
x+1/12*r, y2,
|
||||||
|
|
@ -123,7 +124,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
|
|
||||||
function drawThunderstorm(x, y, r) {
|
function drawThunderstorm(x, y, r) {
|
||||||
function drawLightning(x, y, r) {
|
function drawLightning(x, y, r) {
|
||||||
g.setColor(g.theme.dark ? "#FE0" : "#FC0");
|
g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0"));
|
||||||
g.fillPoly([
|
g.fillPoly([
|
||||||
x-2/6*r, y-r,
|
x-2/6*r, y-r,
|
||||||
x-4/6*r, y+1/6*r,
|
x-4/6*r, y+1/6*r,
|
||||||
|
|
@ -151,7 +152,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setColor(g.theme.dark ? "#FFF" : "#CCC");
|
g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC"));
|
||||||
const w = 1/12*r;
|
const w = 1/12*r;
|
||||||
for(let i = 0; i<=6; ++i) {
|
for(let i = 0; i<=6; ++i) {
|
||||||
const points = [
|
const points = [
|
||||||
|
|
@ -186,7 +187,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
[-0.2, 0.3],
|
[-0.2, 0.3],
|
||||||
];
|
];
|
||||||
|
|
||||||
g.setColor(g.theme.dark ? "#FFF" : "#CCC");
|
g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC"));
|
||||||
for(let i = 0; i<5; ++i) {
|
for(let i = 0; i<5; ++i) {
|
||||||
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
||||||
y+(0.4*i-0.7)*r-1);
|
y+(0.4*i-0.7)*r-1);
|
||||||
|
|
@ -196,7 +197,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawUnknown(x, y, r) {
|
function drawUnknown(x, y, r) {
|
||||||
drawCloud(x, y, r, "#777");
|
drawCloud(x, y, r, "#777"); // dithers on B2, but that's ok
|
||||||
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
1.00: Release for Bangle 2 (2021/11/18)
|
||||||
|
1.01: Internal id update to wid_* as per Gordon's request (2021/11/21)
|
||||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('charging',function(charging) { draw(); });
|
Bangle.on('charging',function(charging) { draw(); });
|
||||||
setInterval(()=>WIDGETS["a_battery_widget"].draw(), 60000);
|
setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), 60000);
|
||||||
|
|
||||||
WIDGETS["a_battery_widget"]={area:"tr",width:30,draw:draw};
|
WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||||
})();
|
})();
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
|
@ -209,6 +209,8 @@ apps.forEach((app,appIdx) => {
|
||||||
// prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?)
|
// prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?)
|
||||||
if (dataNames.includes(app.id+".settings.json") && !dataNames.includes(app.id+".json"))
|
if (dataNames.includes(app.id+".settings.json") && !dataNames.includes(app.id+".json"))
|
||||||
WARN(`App ${app.id} uses data file ${app.id+'.settings.json'} instead of ${app.id+'.json'}`)
|
WARN(`App ${app.id} uses data file ${app.id+'.settings.json'} instead of ${app.id+'.json'}`)
|
||||||
|
else if (dataNames.includes(app.id+".settings.json"))
|
||||||
|
WARN(`App ${app.id} uses data file ${app.id+'.settings.json'}`)
|
||||||
// settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?)
|
// settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?)
|
||||||
if (fileNames.includes(app.id+".settings.json"))
|
if (fileNames.includes(app.id+".settings.json"))
|
||||||
WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`)
|
WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
- Read/write app settings, stored in <appid>.json
|
||||||
|
- Read/write global settings (stored in setting.json)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```
|
||||||
|
// read a single app setting
|
||||||
|
value = require('Settings').get(appid, key, default);
|
||||||
|
// omit key to read all app settings
|
||||||
|
value = require('Settings').get();
|
||||||
|
// write a single app setting
|
||||||
|
require('Settings').set(appid, key, value)
|
||||||
|
// omit key and pass an object as values to overwrite all settings
|
||||||
|
require('Settings').set(appid, values)
|
||||||
|
|
||||||
|
// read Bangle settings by passing the Bangle object instead of an app name
|
||||||
|
value = require('Settings').get(Bangle, key, default);
|
||||||
|
// read all global settings
|
||||||
|
values = require('Settings').get(Bangle);
|
||||||
|
// write a global setting
|
||||||
|
require('Settings').set(Bangle, key, value)
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
require('Settings').set('test', 'foo', 123); // writes to 'test.json'
|
||||||
|
require('Settings').set('test', 'bar', 456); // updates 'test.json'
|
||||||
|
// 'test.json' now contains {baz:123,bam:456}
|
||||||
|
baz = require('Settings').get('test', 'foo'); // baz = 123
|
||||||
|
def = require('Settings').get('test', 'jkl', 789); // def = 789
|
||||||
|
all = require('Settings').get('test'); // all = {foo: 123, bar: 456}
|
||||||
|
baz = require('Settings').get('test', 'baz'); // baz = undefined
|
||||||
|
|
||||||
|
// read global setting
|
||||||
|
vibrate = require('Settings').get(Bangle, 'vibrate', true);
|
||||||
|
|
||||||
|
// Hint: if your app reads multiple settings, you can create a helper function:
|
||||||
|
function s(key, def) { return require('Settings').get('myapp', key, def); }
|
||||||
|
var foo = s('foo setting', 'default value'), bar = s('bar setting');
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read setting value from file
|
||||||
|
*
|
||||||
|
* @param {string} file Settings file
|
||||||
|
* @param {string} key Setting to get, omit to get all settings as object
|
||||||
|
* @param {*} def Default value
|
||||||
|
* @return {*} Setting value (or default if not found)
|
||||||
|
*/
|
||||||
|
function get(file, key, def) {
|
||||||
|
var s = require("Storage").readJSON(file);
|
||||||
|
if (def===undefined && ["object", "undefined"].includes(typeof key)) {
|
||||||
|
// get(file) or get(file, def): get all settings
|
||||||
|
return (s!==undefined) ? s : key;
|
||||||
|
}
|
||||||
|
return ((typeof s==="object") && (key in s)) ? s[key] : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write setting value to file
|
||||||
|
*
|
||||||
|
* @param {string} file Settings file
|
||||||
|
* @param {string} key Setting to change, omit to replace all settings
|
||||||
|
* @param {*} value Value to store
|
||||||
|
*/
|
||||||
|
function set(file, key, value) {
|
||||||
|
if (value===undefined && typeof key==="object") {
|
||||||
|
// set(file, value): overwrite settings completely
|
||||||
|
require("Storage").writeJSON(file, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var s = require("Storage").readJSON(file, 1);
|
||||||
|
if (typeof s!=="object") s = {};
|
||||||
|
s[key] = value;
|
||||||
|
require("Storage").write(file, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read setting value
|
||||||
|
*
|
||||||
|
* @param {string|object} app App name or Bangle
|
||||||
|
* @param {string} key Setting to get, omit to get all settings as object
|
||||||
|
* @param {*} def Default value
|
||||||
|
* @return {*} Setting value (or default if not found)
|
||||||
|
*/
|
||||||
|
exports.get = function(app, key, def) {
|
||||||
|
return get((app===Bangle) ? 'setting.json' : app+".json", key, def);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write setting value
|
||||||
|
*
|
||||||
|
* @param {string|object} app App name or Bangle
|
||||||
|
* @param {string} key Setting to change, omit to replace all settings
|
||||||
|
* @param {*} val Value to store
|
||||||
|
*/
|
||||||
|
exports.set = function(app, key, val) {
|
||||||
|
set((app===Bangle) ? 'setting.json' : app+".json", key, val);
|
||||||
|
};
|
||||||