Merge branch 'master' into master
|
|
@ -1,2 +1,4 @@
|
|||
apps/animclk/V29.LBM.js
|
||||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ package-lock.json
|
|||
appdates.csv
|
||||
.vscode
|
||||
.idea/
|
||||
_config.yml
|
||||
tests/Layout/bin/tmp.*
|
||||
tests/Layout/testresult.bmp
|
||||
|
|
|
|||
|
|
@ -29,3 +29,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
|||
* Added progress bar on Bangle.js for uploads
|
||||
* Provide a proper error message in case JSON decode fails
|
||||
* Check you're connecting with a Bangle.js of the correct version
|
||||
* Allow 'data' style app files to be uploaded with the app (and switch over settings files for various apps)
|
||||
|
|
|
|||
107
README.md
|
|
@ -1,10 +1,10 @@
|
|||
Bangle.js App Loader (and Apps)
|
||||
================================
|
||||
|
||||
[](https://travis-ci.org/espruino/BangleApps)
|
||||
[](https://app.travis-ci.com/github/espruino/BangleApps)
|
||||
|
||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
||||
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
||||
|
||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||
|
|
@ -49,25 +49,25 @@ easily distinguish between file types, we use the following:
|
|||
|
||||
## Adding your app to the menu
|
||||
|
||||
* Come up with a unique (all lowercase, nu spaces) name, we'll assume `7chname`. Bangle.js
|
||||
* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js
|
||||
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
|
||||
try and keep filenames short to avoid overflowing the buffer.
|
||||
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
|
||||
* Create a folder called `apps/<id>`, lets assume `apps/myappid`
|
||||
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
|
||||
* `apps/7chname/app.png` should be a 48px icon
|
||||
* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
|
||||
* `apps/myappid/app.png` should be a 48px icon
|
||||
* Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
|
||||
* Create an entry in `apps.json` as follows:
|
||||
|
||||
```
|
||||
{ "id": "7chname",
|
||||
{ "id": "myappid",
|
||||
"name": "My app's human readable name",
|
||||
"shortName" : "Short Name",
|
||||
"icon": "app.png",
|
||||
"description": "A detailed description of my great app",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"myappid.app.js","url":"app.js"},
|
||||
{"name":"myappid.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
},
|
||||
```
|
||||
|
|
@ -95,12 +95,12 @@ Be aware of the delay between commits and updates on github.io - it can take a f
|
|||
Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
|
||||
(4 discs), upload your files into the places described in your JSON:
|
||||
|
||||
* `app-icon.js` -> `7chname.img`
|
||||
* `app-icon.js` -> `myappid.img`
|
||||
|
||||
Now load `app.js` up in the editor, and click the down-arrow to the bottom
|
||||
right of the `Send to Espruino` icon. Click `Storage` and then either choose
|
||||
`7chname.app.js` (if you'd uploaded your app previously), or `New File`
|
||||
and then enter `7chname.app.js` as the name.
|
||||
`myappid.app.js` (if you'd uploaded your app previously), or `New File`
|
||||
and then enter `myappid.app.js` as the name.
|
||||
|
||||
Now, clicking the `Send to Espruino` icon will load the app directly into
|
||||
Espruino **and** will automatically run it.
|
||||
|
|
@ -115,10 +115,13 @@ and set it to `Load default application`.
|
|||
## Example Applications
|
||||
|
||||
To make the process easier we've come up with some example applications that you can use as a base
|
||||
when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app`
|
||||
or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
|
||||
when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app`
|
||||
or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to
|
||||
`apps.json`.
|
||||
|
||||
**Note:** the max filename length is 28 chars, so we suggest an app ID of under
|
||||
20 so that when `.app.js`/etc gets added to the end the filename isn't cropped.
|
||||
|
||||
**If you're making a widget** please start the name with `wid` to make
|
||||
it easy to find!
|
||||
|
||||
|
|
@ -192,8 +195,8 @@ and which gives information about the app for the Launcher.
|
|||
```
|
||||
{
|
||||
"name":"Short Name", // for Bangle.js menu
|
||||
"icon":"*7chname", // for Bangle.js menu
|
||||
"src":"-7chname", // source file
|
||||
"icon":"*myappid", // for Bangle.js menu
|
||||
"src":"-myappid", // source file
|
||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
||||
// if this is 'widget' then it's not displayed in the menu
|
||||
// if it's 'clock' then it'll be loaded by default at boot time
|
||||
|
|
@ -217,8 +220,10 @@ and which gives information about the app for the Launcher.
|
|||
{ "id": "appid", // 7 character app id
|
||||
"name": "Readable name", // readable name
|
||||
"shortName": "Short name", // short name for launcher
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"version": "0v01", // the version of this app
|
||||
"description": "...", // long description (can contain markdown)
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
|
||||
"type":"...", // optional(if app) -
|
||||
// 'app' - an application
|
||||
// 'widget' - a widget
|
||||
|
|
@ -226,7 +231,9 @@ and which gives information about the app for the Launcher.
|
|||
// 'bootloader' - code that runs at startup only
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
"tags": "", // comma separated tag list for searching
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
|
|
@ -237,6 +244,11 @@ and which gives information about the app for the Launcher.
|
|||
// like this one with 'storage','name' and 'id' set up
|
||||
// see below for more info
|
||||
|
||||
"customConnect": true, // if supplied, ensure we are connected to a device
|
||||
// before the "custom.html" iframe is loaded. An
|
||||
// onInit function in "custom.html" is then called
|
||||
// with info on the currently connected device.
|
||||
|
||||
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
|
||||
// iframe, and it may interact with the connected Bangle
|
||||
// to retrieve information from it
|
||||
|
|
@ -249,14 +261,23 @@ and which gives information about the app for the Launcher.
|
|||
{"name":"appid.js", // filename to use in storage.
|
||||
// If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
|
||||
"url":"", // URL of file to load (currently relative to apps/)
|
||||
"content":"..." // if supplied, this content is loaded directly
|
||||
"evaluate":true // if supplied, data isn't quoted into a String before upload
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
||||
// already exists
|
||||
"supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device
|
||||
// types named in the array. This allows different versions of
|
||||
// the app to be uploaded for different platforms
|
||||
},
|
||||
]
|
||||
"data": [ // list of files the app writes to
|
||||
{"name":"appid.data.json", // filename used in storage
|
||||
"storageFile":true // if supplied, file is treated as storageFile
|
||||
"url":"", // if supplied URL of file to load (currently relative to apps/)
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
},
|
||||
{"wildcard":"appid.data.*" // wildcard of filenames used in storage
|
||||
}, // this is mutually exclusive with using "name"
|
||||
|
|
@ -295,10 +316,10 @@ version of what's in `apps.json`:
|
|||
<script>
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
sendCustomizedApp({
|
||||
id : "7chname",
|
||||
id : "myappid",
|
||||
storage:[
|
||||
{name:"7chname.app.js", url:"app.js", content:app_source_code},
|
||||
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||
{name:"myappid.app.js", url:"app.js", content:app_source_code},
|
||||
{name:"myappid.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
@ -356,44 +377,53 @@ that handles configuring the app.
|
|||
When the app settings are opened, this function is called with one
|
||||
argument, `back`: a callback to return to the settings menu.
|
||||
|
||||
Usually it will save any information in `app.json` where `app` is the name
|
||||
Usually it will save any information in `myappid.json` where `myappid` is the name
|
||||
of your app - so you should change the example accordingly.
|
||||
|
||||
Example `settings.js`
|
||||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = require('Storage').readJSON('app.json',1)||{};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.json',settings);
|
||||
}
|
||||
function get(key, def) { return require('Settings').get('myappid', key, def); }
|
||||
function set(key, value) { require('Settings').set('myappid', key, value); }
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: settings.monkeys||12,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
value: get('monkeys', 12),
|
||||
onchange: (m) => set('monkeys', m)
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add `app.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
```json
|
||||
{ "id": "app",
|
||||
{ "id": "myappid",
|
||||
...
|
||||
"storage": [
|
||||
...
|
||||
{"name":"app.settings.js","url":"settings.js"},
|
||||
{"name":"myappid.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"app.json"}
|
||||
{"name":"myappid.json"}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
You can include any of [Espruino's modules](https://www.espruino.com/Modules) as
|
||||
normal with `require("modulename")`. If you want to develop your own module for your
|
||||
app(s) then you can do that too. Just add the module into the `modules` folder
|
||||
then you can use it from your app as normal.
|
||||
|
||||
You won't be able to develop apps using your own modules with the IDE,
|
||||
so instead we'd recommend you write your module to a Storage File called
|
||||
`modulename` on Bangle.js. You can then develop your app as normal on Bangle.js
|
||||
from the IDE.
|
||||
|
||||
## Coding hints
|
||||
|
||||
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
|
||||
|
|
@ -412,7 +442,7 @@ It should also add `app.json` to `data`, to make sure it is cleaned up when the
|
|||
|
||||
### Misc Notes
|
||||
|
||||
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `7chname.json`, then load it at startup.
|
||||
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `myappid.json`, then load it at startup.
|
||||
|
||||
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
|
||||
|
||||
|
|
@ -427,13 +457,16 @@ The screen is parted in a widget and app area for lcd mode `direct`(default).
|
|||
| areas | as rectangle or point |
|
||||
| :-:| :-: |
|
||||
| Widget | (0,0,239,23) |
|
||||
| Apps | (0,24,239,239) |
|
||||
| Widget bottom bar (optional) | (0,216,239,239) |
|
||||
| Apps | (0,24,239,239) (see below) |
|
||||
| BTN1 | (230, 55) |
|
||||
| BTN2 | (230, 140) |
|
||||
| BTN3 | (230, 210) |
|
||||
| BTN4 | (0,0,119, 239)|
|
||||
| BTN5 | (120,0,239,239) |
|
||||
|
||||
- If there are widgets at the bottom of the screen, apps should actually keep the bottom 24px free, so should keep to the area (0,24,239,215)
|
||||
|
||||
- Use `g.setFontAlign(0, 0, 3)` to draw rotated string to BTN1-BTN3 with `g.drawString()`.
|
||||
|
||||
- For BTN4-5 the touch area is named
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
theme: jekyll-theme-minimal
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
"off",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
|
|
@ -151,9 +151,13 @@
|
|||
"no-octal": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-unreachable": "warn",
|
||||
"no-cond-assign": "warn",
|
||||
"no-useless-catch": "warn",
|
||||
// TODO: "no-undef": "warn",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-useless-escape": "off"
|
||||
"no-useless-escape": "off",
|
||||
"no-control-regex" : "off"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset
|
||||
0.03: Add RMSSD recording
|
||||
0.04: Modify to work with new heart rate API, but still not sure it's working correctly
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
Monitor Heart Rate Variability using the Bangle.JS
|
||||
===================================================
|
||||
|
||||
One-time mode:
|
||||
-------------
|
||||
|
||||
This will take a HRV measurement over a single approx 30 second period. It will also provide you with a HR reading based on the post-processing of the signal.
|
||||
|
||||
HRV metrics displayed are currently RMSSD (Root Mean Square of the Successive Differences) and also SDNN (standard deviation of NN intervals).
|
||||
|
||||
Continuous mode:
|
||||
----------------
|
||||
|
||||
This will continually take measurements over 30 second periods every 3 and half minutes and log them to a CSV file on the Bangle until the watch is reset; this file can then be reviewed in Excel or other apps. The log file is reset each time you restart and select this mode to save on storage. The log file is just 1 line per each 3 minute cycle showing: timestamp, HR, SDNN, RMSSD, sample count, Temp (uncalibrated CPU temp), and movement based on the accelerometer. The additional metrics aside from the HRM data are useful in analysing sleep.
|
||||
|
||||
Note that in both modes, if the watch seems unresponsive, it's processing data and if you continue to hold the reset button it will eventually restart.
|
||||
|
||||
If your sample count is less than around 5 samples and/or the readings don’t look right, try repositioning the watch and try again - you can use the HR monitor app to confirm fitting.
|
||||
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAYAA8AAAA8AA8ABgB8AA8ADwD4AA8AH4HwAA8AP8PgAA8Af+fAAA8A//+AAA8B+P8AAA8D4H4AAA8HwDwAAA8PgBgAAA8PAAAAAA8GAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA/////+AA//////AAf/////AAP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
var option = null;
|
||||
|
||||
//debugging or analysis files
|
||||
var logfile = require("Storage").open("HRV_log.csv", "w");
|
||||
|
||||
logfile = require("Storage").open("HRV_log.csv", "a");
|
||||
|
||||
var csv = [
|
||||
"time",
|
||||
"sample count",
|
||||
"HR",
|
||||
"SDNN",
|
||||
"RMSSD",
|
||||
"Temp",
|
||||
"movement"
|
||||
];
|
||||
logfile.write(csv.join(",")+"\n");
|
||||
|
||||
var debugging = true;
|
||||
var samples = 0; // how many samples have we connected?
|
||||
var collectData = false; // are we currently collecting data?
|
||||
|
||||
var BPM_array = [];
|
||||
var raw_HR_array = new Float32Array(1536);
|
||||
var alternate_array = new Float32Array(3072);
|
||||
var pulse_array = [];
|
||||
var cutoff_threshold = 0.5;
|
||||
var sample_frequency = 51.6;
|
||||
var gap_threshold = 0.15;
|
||||
var movement = 0;
|
||||
|
||||
var px = g.getWidth()/2;
|
||||
var py = g.getHeight()/2;
|
||||
var accel; // interval for acceleration logging
|
||||
|
||||
function storeMyData(data, file_type) { "ram"
|
||||
log = raw_HR_array;
|
||||
// shift elements backwards - note the 4, because a Float32 is 4 bytes
|
||||
log.set(new Float32Array(log.buffer, 4 /*bytes*/));
|
||||
// add ad final element
|
||||
log[log.length - 1] = data;
|
||||
}
|
||||
|
||||
function average(samples) {
|
||||
return E.sum(samples) / samples.length; // faster builtin
|
||||
/* var sum = 0;
|
||||
for (var i = 0; i < samples.length; i++) {
|
||||
sum += parseFloat(samples[i]);
|
||||
}
|
||||
var avg = sum / samples.length;
|
||||
return avg;*/
|
||||
}
|
||||
|
||||
function StandardDeviation (array) {
|
||||
const n = array.length;
|
||||
const mean = E.sum(array) / n; //array.reduce((a, b) => a + b) / n;
|
||||
//return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n);
|
||||
return Math.sqrt(E.variance(array, mean));
|
||||
}
|
||||
|
||||
function turn_off() {
|
||||
Bangle.setHRMPower(0);
|
||||
|
||||
|
||||
g.clear();
|
||||
g.drawString("processing 1/5", px, py);
|
||||
|
||||
rolling_average(raw_HR_array,5);
|
||||
g.clear();
|
||||
g.drawString("processing 2/5", px, py);
|
||||
|
||||
upscale();
|
||||
g.clear();
|
||||
g.drawString("processing 3/5", px, py);
|
||||
|
||||
rolling_average(alternate_array,5);
|
||||
g.clear();
|
||||
g.drawString("processing 4/5", px, py);
|
||||
|
||||
apply_cutoff();
|
||||
find_peaks();
|
||||
|
||||
g.clear();
|
||||
g.drawString("processing 5/5", px, py);
|
||||
|
||||
calculate_HRV();
|
||||
}
|
||||
|
||||
function bernstein(A, B, C, D, E, t) { "ram"
|
||||
s = 1 - t;
|
||||
x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t)
|
||||
+ (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4));
|
||||
return x;
|
||||
}
|
||||
|
||||
function upscale() { "ram"
|
||||
var index = 0;
|
||||
for (let i = raw_HR_array.length - 1; i > 5; i -= 5) {
|
||||
p0 = raw_HR_array[i];
|
||||
p1 = raw_HR_array[i - 1];
|
||||
p2 = raw_HR_array[i - 2];
|
||||
p3 = raw_HR_array[i - 3];
|
||||
p4 = raw_HR_array[i - 4];
|
||||
for (let T = 0; T < 100; T += 10) {
|
||||
x = T / 100;
|
||||
D = bernstein(p0, p1, p2, p3, p4, x);
|
||||
alternate_array[index] = D;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rolling_average(values, count) { "ram"
|
||||
var temp_array = [];
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
temp_array = [];
|
||||
for (let x = 0; x < count; x++)
|
||||
temp_array.push(values[i + x]);
|
||||
values[i] = average(temp_array);
|
||||
}
|
||||
}
|
||||
|
||||
function apply_cutoff() { "ram"
|
||||
var x;
|
||||
for (let i = 0; i < alternate_array.length; i++) {
|
||||
x = alternate_array[i];
|
||||
if (x < cutoff_threshold)
|
||||
x = cutoff_threshold;
|
||||
alternate_array[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
function find_peaks() { "ram"
|
||||
var previous;
|
||||
var previous_slope = 0;
|
||||
var slope;
|
||||
var gap_size = 0;
|
||||
var temp_array = [];
|
||||
|
||||
for (let i = 0; i < alternate_array.length; i++) {
|
||||
if (previous == null)
|
||||
previous = alternate_array[i];
|
||||
slope = alternate_array[i] - previous;
|
||||
if (slope * previous_slope < 0) {
|
||||
if (gap_size > 30) {
|
||||
pulse_array.push(gap_size);
|
||||
gap_size = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
gap_size++;
|
||||
}
|
||||
previous_slope = slope;
|
||||
previous = alternate_array[i];
|
||||
}
|
||||
}
|
||||
|
||||
function RMSSD(samples){ "ram"
|
||||
var sum = 0;
|
||||
var square = 0;
|
||||
var data = [];
|
||||
var value = 0;
|
||||
|
||||
for (let i = 0; i < samples.length-1; i++) {
|
||||
value = Math.abs(samples[i]-samples[i+1])*((1 / (sample_frequency * 2)) * 1000);
|
||||
data.push(value);
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
square = data[i] * data[i];
|
||||
Math.round(square);
|
||||
sum += square;
|
||||
}
|
||||
|
||||
var meansquare = sum/data.length;
|
||||
var RMS = Math.sqrt(meansquare);
|
||||
RMS = parseInt(RMS);
|
||||
return RMS;
|
||||
}
|
||||
|
||||
function calculate_HRV() {
|
||||
var gap_average = average(pulse_array);
|
||||
var temp_array = [];
|
||||
var gap_max = (1 + gap_threshold) * gap_average;
|
||||
var gap_min = (1 - gap_threshold) * gap_average;
|
||||
for (let i = 0; i < pulse_array.length; i++) {
|
||||
if (pulse_array[i] > gap_min && pulse_array[i] < gap_max)
|
||||
temp_array.push(pulse_array[i]);
|
||||
}
|
||||
gap_average = average(temp_array);
|
||||
var calculatedHR = (sample_frequency*60)/(gap_average/2);
|
||||
if(option == 0)
|
||||
Bangle.setLCDPower(1);
|
||||
g.clear();
|
||||
//var display_stdv = StandardDeviation(pulse_array).toFixed(1);
|
||||
var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0);
|
||||
var RMS_SD = RMSSD(temp_array);
|
||||
g.drawString("SDNN:" + SDNN
|
||||
+"\nRMSSD:" + RMS_SD
|
||||
+ "\nHR:" + calculatedHR.toFixed(0)
|
||||
+"\nSample Count:" + temp_array.length, px, py);
|
||||
Bangle.setLCDPower(1);
|
||||
if(option == 0) { // single run
|
||||
Bangle.buzz(500,1);
|
||||
option = null;
|
||||
drawButtons();
|
||||
} else {
|
||||
var csv = [
|
||||
0|getTime(),
|
||||
temp_array.length,
|
||||
calculatedHR.toFixed(0),
|
||||
SDNN,
|
||||
RMS_SD,
|
||||
E.getTemperature(),
|
||||
movement.toFixed(5)
|
||||
];
|
||||
logfile.write(csv.join(",")+"\n");
|
||||
|
||||
|
||||
// for (let i = 0; i < raw_HR_array.length; i++) {
|
||||
// raw_HR_array[i] = null;
|
||||
//}
|
||||
|
||||
turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
function btn1Pressed() {
|
||||
if(option === null){
|
||||
g.clear();
|
||||
g.drawString("one-off assessment", px, py);
|
||||
option = 0;
|
||||
|
||||
turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
function btn3Pressed() {
|
||||
if(option === null){
|
||||
logfile.write(""); //reset HRV log
|
||||
g.clear();
|
||||
g.drawString("continuous mode", px, py);
|
||||
option = 1;
|
||||
|
||||
turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
function turn_on() {
|
||||
BPM_array = [];
|
||||
pulse_array = [];
|
||||
samples = 0;
|
||||
if (accel) clearInterval(accel);
|
||||
movement = 0;
|
||||
accel = setInterval(function () {
|
||||
movement = movement + Bangle.getAccel().diff;
|
||||
}, 1000);
|
||||
Bangle.setHRMPower(1);
|
||||
collectData = true;
|
||||
}
|
||||
|
||||
function drawButtons() {
|
||||
g.setColor("#00ff7f");
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(-1,1);
|
||||
g.drawString("continuous", 120, 210);
|
||||
g.drawString("one-time", 140, 50);
|
||||
g.setColor("#ffffff");
|
||||
g.setFontAlign(0, 0);
|
||||
}
|
||||
|
||||
g.clear();
|
||||
|
||||
drawButtons();
|
||||
|
||||
g.setFont("6x8", 2);
|
||||
g.setColor("#ffffff");
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.drawString("check app README\nfor more info", px, py);
|
||||
|
||||
setWatch(btn1Pressed, BTN1, {repeat:true});
|
||||
setWatch(btn3Pressed, BTN3, {repeat:true});
|
||||
|
||||
|
||||
|
||||
Bangle.on('HRM-raw', function (e) {
|
||||
if (!collectData) return;
|
||||
storeMyData(e.raw, 0);
|
||||
if (!(samples & 7)) {
|
||||
Bangle.setLCDPower(1);
|
||||
g.clearRect(0, py-10, g.getWidth(), py+22);
|
||||
if (samples < 100)
|
||||
g.drawString("setting up...\nremain still " + samples + "%", px, py, true);
|
||||
else
|
||||
g.drawString("logging: " + (samples*100/raw_HR_array.length).toFixed(0) + "%", px, py, true);
|
||||
}
|
||||
if (samples > raw_HR_array.length) {
|
||||
collectData = false;
|
||||
turn_off();
|
||||
}
|
||||
samples++;
|
||||
});
|
||||
|
After Width: | Height: | Size: 749 B |
|
|
@ -0,0 +1 @@
|
|||
0.01: 1st ver, defining a common UI/UX
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# UI/UX for Espruino Smartwatches
|
||||
|
||||
This is a very basic app that defines a common UI/UX for espruino smartwatchesm and specifically for the *bangle.js*, also it displays dinamically calculated x,y position coordinates and screen areas based in detected smartwatch models.
|
||||
|
||||
|
||||
Launcher icon
|
||||
|
||||

|
||||
|
||||
1st screen - Main page
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Open and see x,y coordinates for areas
|
||||
Interact with buttons or touch screen to print the event or leave the app
|
||||
|
||||
## Features
|
||||
|
||||
Colours, font, user input,, load widgets
|
||||
|
||||
|
||||
## Controls
|
||||
Press left area - Prints Touch1
|
||||
Press righ area - Prints Touch2
|
||||
Press center area - Prints Touch3
|
||||
Swipe Left - Prints <--
|
||||
Swipe Right - Prints -->
|
||||
BTN1 - Prints Button1
|
||||
BTN2 - Prints Button2
|
||||
BTN3 - Quit to Launcher
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
This app is so basic that probably the easiest is to just edit the code
|
||||
Otherwise you can contact me [here](https://github.com/dapgo)
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AEMimYASkQXBkYXTmQXzn//mYBBn/zC6nzC6wvTR/4XG/4ASC/4XlQgwOCkQAEdAIXEdI4OBBIwvGC5JHNC/J3XL/53/C/4JGO/533L4wAJC65HNC9rKFAB4XBAH4AeA="))
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
/* UI/UX for Espruino and Bangle.js
|
||||
Testing USer interface and User usability
|
||||
v20200307
|
||||
identify device and dimensions
|
||||
max printable position max_x-1 i.e 239
|
||||
*/
|
||||
|
||||
var colbackg='#111111';//black
|
||||
var colorange='#e56e06'; //RGB format rrggbb
|
||||
var v_color_lines=0xFFFF; //White hex format
|
||||
var v_color_b_area='#111111';
|
||||
var v_color_text=0x07E0;//'#FB0E01';//red
|
||||
var v_font1size=10; //out of quotes
|
||||
var v_font2size=12;
|
||||
var v_font_banner_size=30;
|
||||
var v_clicks=0;
|
||||
//pend identify widget area dinamically
|
||||
var v_model=process.env.BOARD;
|
||||
console.log("device="+v_model);
|
||||
|
||||
var x_max_screen=g.getWidth();//240;
|
||||
var y_max_screen=g.getHeight(); //240;
|
||||
var y_wg_bottom=g.getHeight()-25;
|
||||
var y_wg_top=25;
|
||||
if (v_model=='BANGLEJS') {
|
||||
var x_btn_area=215;
|
||||
var x_max_usable_area=x_btn_area;//Pend! only for bangle.js
|
||||
var y_btn2=124; //harcoded for bangle.js cuz it is not the half of display height
|
||||
} else x_max_usable_area=240;
|
||||
|
||||
|
||||
var x_mid_screen=x_max_screen/2;
|
||||
|
||||
//PEND comment
|
||||
console.log("*** UI dimensions***");
|
||||
console.log("x="+x_max_screen);
|
||||
console.log("y_wg_bottom="+y_wg_bottom);
|
||||
|
||||
|
||||
|
||||
//the biggest usable area, button area not included
|
||||
function ClearActiveArea(){
|
||||
g.setColor(v_color_b_area);
|
||||
g.fillRect(0,y_wg_top,x_max_usable_area,y_wg_bottom); //fill all screen except widget areas
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
//Clean fill top area with a color
|
||||
function ClearBannerArea(){
|
||||
g.setColor(v_color_b_area);
|
||||
g.fillRect(55,28,185,60);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
function PrintUserInput(boton){
|
||||
//var v_font_banner_size=30;//now global size= almost px height
|
||||
console.log("Pressed touch/BTN",boton);
|
||||
ClearBannerArea();
|
||||
g.setColor(colorange);
|
||||
//print in banner area
|
||||
g.setFontVector(v_font_banner_size).drawString(boton, 63, 29);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function PrintBtn1(boton){
|
||||
console.log("Pressed BTN1");
|
||||
PrintUserInput("Button1");
|
||||
|
||||
}
|
||||
|
||||
function PrintBtn2(boton){
|
||||
console.log("Pressed BTN2");
|
||||
PrintUserInput("Button2");
|
||||
}
|
||||
|
||||
function DrawBangleButtons(){
|
||||
g.setFontVector(v_font1size);
|
||||
g.setColor(v_color_lines);//White
|
||||
//line-separation with buttons area
|
||||
g.drawLine(x_btn_area, 35, x_btn_area, 180);//vline right sep buttons
|
||||
|
||||
//x for Border position in 2 lines
|
||||
g.drawString("x=",x_max_screen-g.stringWidth("x= "),68);
|
||||
g.drawString(x_max_screen,x_max_screen-g.stringWidth("3ch"),78);
|
||||
|
||||
g.drawString("Dwn", x_max_screen-g.stringWidth("Dwn"),y_wg_top+v_font1size+1);
|
||||
//above Btn2
|
||||
//g.setFontVector(v_font1size).drawString("Off", x_max_screen-g.stringWidth("Off"),y_btn2-(2*v_font1size));
|
||||
g.drawString("Set", x_max_screen-g.stringWidth("Set"),y_btn2-v_font1size);
|
||||
//above Btn3
|
||||
g.drawString("Quit", x_max_screen-g.stringWidth("Quit"),y_wg_bottom-(2*v_font1size));
|
||||
g.flip();
|
||||
g.setColor(v_color_text); //green
|
||||
g.setFontVector(v_font1size);
|
||||
g.drawString("B1", x_max_screen-g.stringWidth("B1"),y_wg_top);
|
||||
g.drawString("B2", x_max_screen-g.stringWidth("B2"),y_btn2);
|
||||
g.drawString("B3",x_max_screen-g.stringWidth("B3"),y_wg_bottom-v_font1size);
|
||||
g.flip();
|
||||
}
|
||||
function DrawBottomInfoBanner(){
|
||||
/* External Vars:v_color_text,v_font2size,x_max_usable_area,y_wg_bottom
|
||||
*/
|
||||
g.setColor(v_color_text);
|
||||
var info_text1="Swipe: Next/Back screen";
|
||||
var info_text2="Touch: Left=Up Right=Down";
|
||||
//aligned left of max usable area
|
||||
g.setFontVector(v_font2size);
|
||||
g.drawString(info_text2, x_max_usable_area-g.stringWidth(info_text2)-2 ,y_wg_bottom-(2*v_font2size));
|
||||
g.drawString(info_text1, x_max_usable_area-g.stringWidth(info_text1)-2 ,y_wg_bottom-v_font2size);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function PrintAreas(){
|
||||
console.log("********************************");
|
||||
console.log("Log: *** Print Areas in screen");
|
||||
ClearActiveArea();
|
||||
g.setColor(v_color_lines);
|
||||
|
||||
// **** Borders and Separation Lines for areas
|
||||
g.drawLine(1, 35, 1, 180);//line for left border
|
||||
//
|
||||
g.drawLine(x_max_screen-1, 50, x_max_screen-1, 75);//vlide right border
|
||||
g.drawLine(x_mid_screen, 80, x_mid_screen, 105);//vline middle separation part1 up
|
||||
g.setFontVector(v_font2size).drawString("Output area for "+v_model,(x_max_usable_area-g.stringWidth("Output area for "+v_model))/2,67);
|
||||
g.setFontVector(v_font2size).drawString("x="+x_mid_screen,x_mid_screen-g.stringWidth("x=xxx"),85);
|
||||
g.drawLine(x_mid_screen, 140, x_mid_screen, 180);//vline middle separation part2 down
|
||||
|
||||
|
||||
//y=26 after widget line y=215 below widget line
|
||||
|
||||
if (v_model=='BANGLEJS') DrawBangleButtons();
|
||||
|
||||
g.setFontVector(v_font2size);
|
||||
g.setColor(v_color_text);
|
||||
g.drawString("Touch", 80,110);
|
||||
g.drawString("Middle area", 80,125);
|
||||
g.drawString("Left area", 15, 145);
|
||||
g.drawString("Right area", 140,145);
|
||||
g.flip();
|
||||
DrawBottomInfoBanner();
|
||||
}
|
||||
|
||||
function UserInput(){
|
||||
Bangle.on('touch', function(button){
|
||||
switch(button){
|
||||
case 1:
|
||||
PrintUserInput("Touch 1");//left
|
||||
break;
|
||||
case 2:
|
||||
PrintUserInput("Touch 2");//right
|
||||
break;
|
||||
case 3:
|
||||
PrintUserInput("Touch 3");//center 1+2
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (v_model=='BANGLEJS') {
|
||||
//only the name of the function
|
||||
setWatch(PrintBtn1, BTN1, { repeat: true });
|
||||
setWatch(PrintBtn2, BTN2, { repeat: true });
|
||||
setWatch(Bangle.showLauncher, BTN3, { repeat: true });
|
||||
}
|
||||
Bangle.on('swipe', dir => {
|
||||
if(dir == 1) PrintUserInput(" --->");
|
||||
else PrintUserInput(" <---");
|
||||
});
|
||||
console.log("Log: Input conditions loaded");
|
||||
} //end of UserInput
|
||||
|
||||
//Main code
|
||||
ClearActiveArea();
|
||||
PrintAreas();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
||||
|
||||
//optional lines below and above both widget areas
|
||||
g.setColor(v_color_lines);
|
||||
//line-separation with top widget area
|
||||
g.drawLine(60, y_wg_top, 180, y_wg_top);
|
||||
g.setFontVector(v_font2size).drawString("y="+y_wg_top,10,y_wg_top+1);
|
||||
//line-separation with bottom widget area
|
||||
g.drawLine(60, y_wg_bottom, 180, y_wg_bottom);
|
||||
g.setFontVector(v_font2size).drawString("y="+y_wg_bottom,10,y_wg_bottom-v_font2size);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
g.flip();
|
||||
//end optional
|
||||
UserInput();
|
||||
|
After Width: | Height: | Size: 718 B |
|
|
@ -2,13 +2,14 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great app",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
{ "id": "7chname",
|
||||
"name": "My widget's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great widget",
|
||||
"tags": "widget",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.wid.js","url":"widget.js"}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,8 @@
|
|||
0.04: Actual pixels as of 9 Mar 2020
|
||||
0.05: Actual pixels as of 27 Apr 2020
|
||||
0.06: Actual pixels as of 12 Jun 2020
|
||||
0.07: Pressing a button now exits immediately (fix #618)
|
||||
0.08: Make about (mostly) work on non-240px screens
|
||||
0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021
|
||||
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Use the new multiplatform 'Layout' library
|
||||
0.03: Exit as first menu option, dont show decimal places for seconds
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Acceleration Logger
|
||||
|
||||
Logs XYZ acceleration data (at the normal 12.5Hz) to a CSV file that can be downloaded to your PC.
|
||||
|
||||
## Usage
|
||||
|
||||
* Run the app 'Accel Log' from Bangle.js menu
|
||||
* Optionally choose a file number if you want to record to multiple different files.
|
||||
* Choose `Start`
|
||||
* Acceleration data will now be recording
|
||||
* When you're done, press `BTN2`
|
||||
|
||||
## Downloading Data
|
||||
|
||||
* Go to the Bangle.js App Loader
|
||||
* Connect to your Bangle
|
||||
* Under `My Apps` look for `Acceleration Logger`
|
||||
* You'll see a download arrow next to it - click that
|
||||
* You can now choose to Save or Delete each track you have recorded
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgsphAXWxGACyoABDCwWVC/4XcXY8IDZsIxAXVXYQZDDwIGEC6gwMI5IvNGAJfVR5DXyPgczAAwSEC58iC4R3DC50xiMjC4QTBXY4XNGAIPCF6QTCI6gXCYYwXQcQ4XemUiC6IRBa4wXOABQX/C9OD/4AKC5WPC+Hzaw0zI/5H/C83zI/mIUo4ACnAXLABgXI"))
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
var fileNumber = 0;
|
||||
var MAXLOGS = 9;
|
||||
|
||||
function getFileName(n) {
|
||||
return "accellog."+n+".csv";
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
var menu = {
|
||||
"" : { title : "Accel Logger" },
|
||||
"Exit" : function() {
|
||||
load();
|
||||
},
|
||||
"File No" : {
|
||||
value : fileNumber,
|
||||
min : 0,
|
||||
max : MAXLOGS,
|
||||
onchange : v => { fileNumber=v; }
|
||||
},
|
||||
"Start" : function() {
|
||||
E.showMenu();
|
||||
startRecord();
|
||||
},
|
||||
"View Logs" : function() {
|
||||
viewLogs();
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function viewLog(n) {
|
||||
E.showMessage("Loading...");
|
||||
var f = require("Storage").open(getFileName(n), "r");
|
||||
var records = 0, l = "", ll="";
|
||||
while ((l=f.readLine())!==undefined) {records++;ll=l;}
|
||||
var length = 0;
|
||||
if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 );
|
||||
|
||||
var menu = {
|
||||
"" : { title : "Log "+n }
|
||||
};
|
||||
menu[records+" Records"] = "";
|
||||
menu[length+" Seconds"] = "";
|
||||
menu["DELETE"] = function() {
|
||||
E.showPrompt("Delete Log "+n).then(ok=>{
|
||||
if (ok) {
|
||||
E.showMessage("Erasing...");
|
||||
f.erase();
|
||||
viewLogs();
|
||||
} else viewLog(n);
|
||||
});
|
||||
};
|
||||
menu["< Back"] = function() {
|
||||
viewLogs();
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function viewLogs() {
|
||||
var menu = {
|
||||
"" : { title : "Logs" }
|
||||
};
|
||||
|
||||
var hadLogs = false;
|
||||
for (var i=0;i<=MAXLOGS;i++) {
|
||||
var f = require("Storage").open(getFileName(i), "r");
|
||||
if (f.readLine()!==undefined) {
|
||||
(function(i) {
|
||||
menu["Log "+i] = () => viewLog(i);
|
||||
})(i);
|
||||
hadLogs = true;
|
||||
}
|
||||
}
|
||||
if (!hadLogs)
|
||||
menu["No Logs Found"] = function(){};
|
||||
menu["< Back"] = function() { showMenu(); };
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function startRecord(force) {
|
||||
if (!force) {
|
||||
// check for existing file
|
||||
var f = require("Storage").open(getFileName(fileNumber), "r");
|
||||
if (f.readLine()!==undefined)
|
||||
return E.showPrompt("Overwrite Log "+fileNumber+"?").then(ok=>{
|
||||
if (ok) startRecord(true); else showMenu();
|
||||
});
|
||||
}
|
||||
// display
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var Layout = require("Layout");
|
||||
var layout = new Layout({ type: "v", c: [
|
||||
{type:"txt", font:"6x8", label:"Samples", pad:2},
|
||||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
]
|
||||
},{btns:[ // Buttons...
|
||||
{label:"STOP", cb:()=>{
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
}}
|
||||
]});
|
||||
layout.render();
|
||||
|
||||
// now start writing
|
||||
var f = require("Storage").open(getFileName(fileNumber), "w");
|
||||
f.write("Time (ms),X,Y,Z\n");
|
||||
var start = getTime();
|
||||
var sampleCount = 0;
|
||||
|
||||
function accelHandler(accel) {
|
||||
var t = getTime()-start;
|
||||
f.write([
|
||||
t*1000,
|
||||
accel.x*8192,
|
||||
accel.y*8192,
|
||||
accel.z*8192].map(n=>Math.round(n)).join(",")+"\n");
|
||||
|
||||
sampleCount++;
|
||||
layout.samples.label = sampleCount;
|
||||
layout.time.label = Math.round(t)+"s";
|
||||
layout.render(layout.samples);
|
||||
layout.render(layout.time);
|
||||
}
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz - the default
|
||||
Bangle.on('accel', accelHandler);
|
||||
}
|
||||
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
showMenu();
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,106 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="data"></div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
var dataElement = document.getElementById("data");
|
||||
|
||||
function getData() {
|
||||
// show loading window
|
||||
Util.showModal("Loading...");
|
||||
// get the data
|
||||
dataElement.innerHTML = "";
|
||||
var promise = Promise.resolve();
|
||||
Puck.eval('require("Storage").list(/accellog\\..\\.csv\\x01/)',files=>{
|
||||
if (files.length==0) {
|
||||
dataElement.innerHTML = "<p>No saved data</p>";
|
||||
} else {
|
||||
files.forEach(fn => {
|
||||
fn = fn.slice(0,-1);
|
||||
dataElement.innerHTML += `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">${fn}</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" fn="${fn}" act="save">Save</button>
|
||||
<button class="btn" fn="${fn}" act="delete">Delete</button>
|
||||
</div>
|
||||
</div>`;
|
||||
promise = promise.then(function() {
|
||||
document.querySelector(`.btn[fn='${fn}'][act='save']`).addEventListener("click", function() {
|
||||
Util.readStorageFile(fn, function(data) {
|
||||
Util.saveCSV(fn.slice(0,-4), data);
|
||||
});
|
||||
});
|
||||
document.querySelector(`.btn[fn='${fn}'][act='delete']`).addEventListener("click", function() {
|
||||
Util.showModal("Deleting...");
|
||||
Util.eraseStorageFile(fn, function() {
|
||||
Util.hideModal();
|
||||
getData();
|
||||
});
|
||||
});
|
||||
return new Promise(resolve=>{
|
||||
Puck.eval(`require("Storage").read(${JSON.stringify(fn)})`,csv=>{
|
||||
var el = document.querySelector(`.card-body[fn='${fn}']`);
|
||||
el.innerHTML = '<canvas width="400" height="100"></canvas>';
|
||||
var c = el.firstChild;
|
||||
var ctx = c.getContext("2d");
|
||||
var lines = csv.split("\n");
|
||||
var y = 50, sx = 400/lines.length, sy = 50/8;
|
||||
function plot(n) {
|
||||
var last;
|
||||
ctx.beginPath();
|
||||
lines.map((l,x)=>{
|
||||
l = l.split(",");
|
||||
var yc = y + parseFloat(l[n])*sy;
|
||||
if (!last) {
|
||||
ctx.moveTo(0, yc);
|
||||
} else {
|
||||
ctx.lineTo(x*sx, yc);
|
||||
}
|
||||
last = l;
|
||||
});
|
||||
ctx.stroke();
|
||||
};
|
||||
ctx.strokeStyle = 'red';
|
||||
plot(0);
|
||||
ctx.strokeStyle = 'green';
|
||||
plot(1);
|
||||
ctx.strokeStyle = 'blue';
|
||||
plot(2);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// remove window
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
|
||||
// You can call a utility function to save the data
|
||||
/*document.getElementById("btnSave").addEventListener("click", function() {
|
||||
Util.saveCSV("gpsdata", csvData);
|
||||
});
|
||||
// Or you can also delete the file
|
||||
document.getElementById("btnDelete").addEventListener("click", function() {
|
||||
Util.showModal("Deleting...");
|
||||
Util.eraseStorageFile("gpspoilog.csv", function() {
|
||||
Util.hideModal();
|
||||
getData();
|
||||
});
|
||||
});*/
|
||||
// Called when app starts
|
||||
function onInit() {
|
||||
getData();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# Acceleration Recorder
|
||||
|
||||
This app records a short period of acceleration data from the accelerometer
|
||||
and
|
||||
This app records a short period of acceleration data from the accelerometer at
|
||||
100Hz (starting when acceleration happens) and graphs it, working out max acceleration
|
||||
and max velocity. Data can also be downloaded to your PC.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,5 @@
|
|||
0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date
|
||||
0.12: Fix regression after 0.11
|
||||
0.13: Fix broken date padding (fix #376)
|
||||
0.14: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
||||
0.15: Add color scheme support
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
# Analogue Clock
|
||||
|
||||

|
||||
|
||||
|
|
@ -2,13 +2,16 @@
|
|||
const locale = require('locale');
|
||||
const p = Math.PI / 2;
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
|
||||
const faceWidth = g.getWidth()/2 - 20; // watch face radius (240/2 - 24px for widget area)
|
||||
const widgetHeight=24+1;
|
||||
let timer = null;
|
||||
let currentDate = new Date();
|
||||
const centerX = g.getWidth() / 2;
|
||||
const centerY = (g.getWidth() / 2) + widgetHeight/2;
|
||||
|
||||
g.theme.dark=false;
|
||||
let colSecA = g.theme.dark ? "#00A" : "#58F"; // before the second
|
||||
let colSecB = g.theme.dark ? "#58F" : "#00A"; // after the second
|
||||
let colSec1 = g.theme.dark ? "#F83" : "#000"; // ON the second
|
||||
|
||||
const seconds = (angle) => {
|
||||
const a = angle * pRad;
|
||||
|
|
@ -46,40 +49,35 @@ const drawAll = () => {
|
|||
// draw all secs
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
if (i > currentSec) {
|
||||
g.setColor(0, 0, 0.6);
|
||||
} else {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
}
|
||||
g.setColor((i > currentSec) ? colSecA : colSecB);
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
onSecond();
|
||||
|
||||
};
|
||||
|
||||
const resetSeconds = () => {
|
||||
g.setColor(0, 0, 0.6);
|
||||
g.setColor(colSecA);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
};
|
||||
|
||||
const onSecond = () => {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
g.setColor(colSecB);
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
if (currentDate.getSeconds() === 59) {
|
||||
resetSeconds();
|
||||
onMinute();
|
||||
}
|
||||
g.setColor(1, 0.7, 0.2);
|
||||
g.setColor(colSec1);
|
||||
currentDate = new Date();
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setColor(g.theme.fg);
|
||||
};
|
||||
|
||||
const drawDate = () => {
|
||||
g.reset();
|
||||
g.setColor(1, 0, 0);
|
||||
g.setColor("#f00");
|
||||
g.setFont('6x8', 2);
|
||||
|
||||
const dayString = locale.dow(currentDate, true);
|
||||
|
|
@ -89,7 +87,7 @@ const drawDate = () => {
|
|||
// console.log(`${dayString}|${dateString}`);
|
||||
// center date
|
||||
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
||||
const t = centerY + 37;
|
||||
const t = centerY + faceWidth*0.37;
|
||||
g.drawString(dateDisplay, l, t, true);
|
||||
// console.log(l, t);
|
||||
};
|
||||
|
|
@ -99,7 +97,7 @@ const onMinute = () => {
|
|||
resetSeconds();
|
||||
}
|
||||
// clear existing hands
|
||||
g.setColor(0, 0, 0);
|
||||
g.setColor(g.theme.bg);
|
||||
// Hour
|
||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||
// Minute
|
||||
|
|
@ -107,15 +105,12 @@ const onMinute = () => {
|
|||
|
||||
// get new date, then draw new hands
|
||||
currentDate = new Date();
|
||||
g.setColor(1, 0.9, 0.9);
|
||||
g.setColor(g.theme.fg);
|
||||
// Hour
|
||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||
g.setColor(1, 1, 0.9);
|
||||
g.setColor(g.theme.fg);
|
||||
// Minute
|
||||
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
||||
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
drawDate();
|
||||
};
|
||||
|
||||
|
|
@ -140,8 +135,9 @@ g.clear();
|
|||
resetSeconds();
|
||||
startTimers();
|
||||
drawAll();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -1,4 +1,9 @@
|
|||
0.01: New Widget!
|
||||
0.02: Distance calculation and display
|
||||
0.03: Data logging and display
|
||||
0.04: Steps are set to 0 in log on new day
|
||||
0.04: Steps are set to 0 in log on new day
|
||||
0.05: Fix default step/distance display if it hasn't been set up first
|
||||
0.06: Added WIDGETS.activepedom.getSteps()
|
||||
0.07: Added settings to be able to hide line1 and line2 so there is no visible widget
|
||||
0.08: Fixed zero steps issue caused by 0.07
|
||||
0.09: Prettied up user interface, decluttered graphs
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a
|
|||
* Step detection sensitivity from firmware can be configured
|
||||
* Steps are saved to a file and read-in at start (to not lose step progress)
|
||||
* Settings can be changed in Settings - App/widget settings - Active Pedometer
|
||||
* Can hide the widget display if required using settings
|
||||
|
||||
## Features App
|
||||
|
||||
|
|
|
|||
|
|
@ -88,28 +88,40 @@
|
|||
times = undefined;
|
||||
|
||||
//steps
|
||||
var csvFile = storage.open(filename, "r");
|
||||
csvFile = storage.open(filename, "r");
|
||||
steps = getArrayFromCSV(csvFile, 1);
|
||||
first = first + " " + steps[0] + "/" + setting('stepGoal');
|
||||
last = last + " " + steps[steps.length-1] + "/" + setting('stepGoal');
|
||||
|
||||
//define y-axis grid labels
|
||||
stepsLastEntry = steps[steps.length-1];
|
||||
if (stepsLastEntry < 1000) gridyValue = 100;
|
||||
if (stepsLastEntry >= 1000 && stepsLastEntry < 10000) gridyValue = 1000;
|
||||
if (stepsLastEntry > 10000) gridyValue = 5000;
|
||||
// the labels on the y axis are fairly unreadable so minimise them
|
||||
if (stepsLastEntry < 1000) gridyValue = 500;
|
||||
if (stepsLastEntry >= 1000 && stepsLastEntry < 2000) gridyValue = 1000;
|
||||
if (stepsLastEntry >= 2000 && stepsLastEntry < 5000) gridyValue = 2000;
|
||||
if (stepsLastEntry >= 5000 && stepsLastEntry < 10000) gridyValue = 5000;
|
||||
if (stepsLastEntry >= 10000 && stepsLastEntry < 20000) gridyValue = 10000;
|
||||
if (stepsLastEntry > 20000) gridyValue = 20000;
|
||||
|
||||
//draw
|
||||
drawMenu();
|
||||
g.drawString("First: " + first, 10, 30);
|
||||
g.drawString(" Last: " + last, 10, 40);
|
||||
// draw the chart
|
||||
g.clear();
|
||||
g.setFont("6x8", 2);
|
||||
g.setColor(1,1,1);
|
||||
require("graph").drawLine(g, steps, {
|
||||
//title: "Steps Counted",
|
||||
//title: "Steps",
|
||||
axes : true,
|
||||
gridy : gridyValue,
|
||||
y : 60, //offset on screen
|
||||
x : 5, //offset on screen
|
||||
});
|
||||
|
||||
// show steps and duration of the chart
|
||||
g.setFont("6x8", 2);
|
||||
g.setColor(0,1,0);
|
||||
g.drawString("steps", 30, 24);
|
||||
g.drawString(stepsLastEntry, 30, 44);
|
||||
g.drawString((history/3600000) + " hrs", 30, 64);
|
||||
|
||||
//free memory from big variables
|
||||
allData = undefined;
|
||||
allDataFile = undefined;
|
||||
|
|
@ -117,13 +129,48 @@
|
|||
times = undefined;
|
||||
}
|
||||
|
||||
function drawMenu () {
|
||||
g.clear();
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString("BTN1:Timespan | BTN2:Draw", 20, 10);
|
||||
g.drawString("Timespan: " + history/1000/60/60 + " hours", 20, 20);
|
||||
function getImage() {
|
||||
return require("heatshrink").decompress(atob("mEwwIGDvAEDgP+ApMD/4FVEZY1FABcP8AFDn/wAod/AocB//4AoUHAokPAokf5/8AocfAoc+j5HDvgFEvEf7+AAoP4AoJCC+E/54qCsE/wYkDn+AAos8AohZDj/AAohrEp4FEs5xEuJfDgF5Aon4GgYFBGgZOBnyJD+EeYgfgj4FEh6VD4AFDh+AAIJMCBoIFFLQQtBgYFCHIIFDjA3BC4I="));
|
||||
}
|
||||
|
||||
function drawMenu() {
|
||||
var x = 100;
|
||||
var y = 24;
|
||||
var stps ="-";
|
||||
var y_inc = 25;
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
g.drawImage(getImage(),0 ,60 , {scale:2} );
|
||||
g.setFont("6x8",2);
|
||||
|
||||
// timespan
|
||||
g.setColor('#7f8c8d');
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString("Timespan", x, y, true);
|
||||
y += y_inc;
|
||||
g.setColor('#bdc3c7');
|
||||
g.drawString(history/1000/60/60 + " hrs" , x, y, true);
|
||||
|
||||
// BTN1 info
|
||||
y += 2*y_inc;
|
||||
g.setColor('#7f8c8d');
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString("BTN1", x, y, true);
|
||||
y += y_inc;
|
||||
g.setColor('#bdc3c7');
|
||||
g.drawString("Timespan", x, y, true);
|
||||
|
||||
// BTN2 info
|
||||
y += 2*y_inc;
|
||||
g.setColor('#7f8c8d');
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString("BTN2", x, y, true);
|
||||
y += y_inc;
|
||||
g.setColor('#bdc3c7');
|
||||
g.drawString("Draw", x, y, true);
|
||||
}
|
||||
|
||||
setWatch(function() { //BTN1
|
||||
switch(history) {
|
||||
case 3600000 : //1h
|
||||
|
|
@ -140,7 +187,9 @@
|
|||
}, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
|
||||
setWatch(function() { //BTN2
|
||||
g.setFont("6x8", 2);
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("6x8", 3);
|
||||
g.drawString ("Drawing...",30,60);
|
||||
drawGraph();
|
||||
}, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
|
|
@ -161,5 +210,4 @@
|
|||
}
|
||||
|
||||
drawMenu();
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'activepedom.settings.json';
|
||||
const LINES = ['Steps', 'Distance'];
|
||||
const LINES = ['Steps', 'Distance', 'Hide'];
|
||||
|
||||
// initialize with default settings...
|
||||
let s = {
|
||||
|
|
@ -109,4 +109,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@
|
|||
'stepSensitivity' : 80,
|
||||
'stepGoal' : 10000,
|
||||
'stepLength' : 75,
|
||||
'lineOne' : "Distance",
|
||||
'lineTwo' : "Steps",
|
||||
};
|
||||
if (!settings) { loadSettings(); }
|
||||
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||
|
|
@ -139,6 +141,7 @@
|
|||
|
||||
function draw() {
|
||||
var height = 23; //width is deined globally
|
||||
|
||||
distance = (stepsCounted * setting('stepLength')) / 100 /1000; //distance in km
|
||||
|
||||
//Check if same day
|
||||
|
|
@ -152,6 +155,12 @@
|
|||
stepsOutsideTime = 0;
|
||||
}
|
||||
lastUpdate = date;
|
||||
|
||||
// not everyone likes a widget, having refreshed lastUpdate we can exit
|
||||
if (setting('lineOne') == 'Hide' && setting('lineTwo') == 'Hide') {
|
||||
settings = 0; //reset settings to save memory
|
||||
return;
|
||||
}
|
||||
|
||||
g.reset();
|
||||
g.clearRect(this.x, this.y, this.x+width, this.y+height);
|
||||
|
|
@ -160,7 +169,6 @@
|
|||
if (active == 1) g.setColor(0x07E0); //green
|
||||
else g.setColor(0xFFFF); //white
|
||||
g.setFont("6x8", 2);
|
||||
|
||||
if (setting('lineOne') == 'Steps') {
|
||||
g.drawString(kFormatterSteps(stepsCounted),this.x+1,this.y); //first line, big number, steps
|
||||
}
|
||||
|
|
@ -227,6 +235,6 @@
|
|||
|
||||
setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive)
|
||||
timerStoreData = setInterval(storeData, storeDataInterval); //store data regularly
|
||||
//Add widget
|
||||
WIDGETS["activepedom"]={area:"tl",width:width,draw:draw};
|
||||
})();
|
||||
//Add widget, use: WIDGETS.activepedom.getSteps() inside another App to return todays step count
|
||||
WIDGETS["activepedom"]={area:"tl",width:width,draw:draw, getSteps:()=>stepsCounted};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -8,3 +8,8 @@
|
|||
0.08: Make alarm scheduling more reliable
|
||||
0.09: Add per alarm auto-snooze option
|
||||
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
||||
0.11: Respect Quiet Mode
|
||||
0.12: Fix widget for bangle 2, now uses theme
|
||||
Widgets now shown on Alarm screen
|
||||
0.13: Alarm widget state now updates when setting/resetting an alarm
|
||||
0.14: Order of 'back' menu item
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ function showAlarm(alarm) {
|
|||
var buzzCount = 10;
|
||||
if (alarm.msg)
|
||||
msg += "\n"+alarm.msg;
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showPrompt(msg,{
|
||||
title:"ALARM!",
|
||||
title:alarm.timer ? "TIMER!" : "ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
|
|
@ -38,6 +40,7 @@ function showAlarm(alarm) {
|
|||
load();
|
||||
});
|
||||
function buzz() {
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
|
||||
Bangle.buzz(100).then(()=>{
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(100).then(function() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
|||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
as : false, // auto snooze
|
||||
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
|
||||
}
|
||||
];*/
|
||||
|
||||
|
|
@ -18,6 +19,12 @@ function formatTime(t) {
|
|||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function formatMins(t) {
|
||||
mins = (0|t)%60;
|
||||
hrs = 0|(t/60);
|
||||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentHr() {
|
||||
var time = new Date();
|
||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
|
|
@ -25,17 +32,25 @@ function getCurrentHr() {
|
|||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'New Alarm': ()=>editAlarm(-1)
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'< Back' : ()=>{load();},
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
if (alarm.timer) {
|
||||
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
|
||||
} else {
|
||||
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
}
|
||||
menu[txt] = function() {
|
||||
editAlarm(idx);
|
||||
if (alarm.timer) editTimer(idx);
|
||||
else editAlarm(idx);
|
||||
};
|
||||
});
|
||||
menu['< Back'] = ()=>{load();};
|
||||
|
||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +70,8 @@ function editAlarm(alarmIndex) {
|
|||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'': { 'title': 'Alarm' },
|
||||
'< Back' : showMainMenu,
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
|
|
@ -105,7 +121,60 @@ function editAlarm(alarmIndex) {
|
|||
showMainMenu();
|
||||
};
|
||||
}
|
||||
menu['< Back'] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editTimer(alarmIndex) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var hrs = 0;
|
||||
var mins = 5;
|
||||
var en = true;
|
||||
if (!newAlarm) {
|
||||
var a = alarms[alarmIndex];
|
||||
mins = (0|a.timer)%60;
|
||||
hrs = 0|(a.timer/60);
|
||||
en = a.on;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Timer' },
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
}
|
||||
};
|
||||
function getTimer() {
|
||||
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
|
||||
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
|
||||
// Save alarm
|
||||
return {
|
||||
on : en,
|
||||
timer : (hrs*60)+mins,
|
||||
hr : hr,
|
||||
rp : false, as: false
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
if (newAlarm) alarms.push(getTimer());
|
||||
else alarms[alarmIndex] = getTimer();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["> Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
(() => {
|
||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
||||
alarms = alarms.filter(alarm=>alarm.on);
|
||||
if (!alarms.length) return; // no alarms, no widget!
|
||||
delete alarms;
|
||||
// add the widget
|
||||
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
|
||||
g.setColor(-1);
|
||||
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
}};
|
||||
})()
|
||||
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
},reload:function() {
|
||||
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS["alarm"].reload();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
Alpine Navigator
|
||||
================
|
||||
App that performs GPS monitoring to track and display position relative to a given origin in realtime.
|
||||
|
||||

|
||||
|
||||
Functions
|
||||
---------
|
||||
Note if you've not used GPS yet I suggest using one of the GPS apps to get your first fix and confirm as I've found that helps initially.
|
||||
|
||||
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. all your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit but I've kept it fairly conservative here as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
|
||||
|
||||
1. altitude at origin, this is displayed left of the centre.
|
||||
2. current altitude, displayed centre right
|
||||
3. distance from origin, bottom left (meters)
|
||||
4. distance travelled, bottom right (meters)
|
||||
5. compass heading, at the top
|
||||
|
||||
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time because the waypoints will be reduced when it reaches a set threshold so you may see the path smooth out slightly at intervals.
|
||||
|
||||
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring but often just moving your wrist a bit is enough.
|
||||
|
||||
The buttons do the following:
|
||||
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! soft and hard reset will both still work.
|
||||
BTN2: this removes all waypoints aside from the origin and your current location; sometimes during smaller journeys and walks, the GPS can give sporadic differences in locations because of the error margins of GPS and this can add noise to your route.
|
||||
BTN3: this will pause the GPS and magnetometer, useful for saving power for situations where you don't necessarily need to track parts of your route e.g. you're going indoors/shelter for some time. You'll know it's paused because the compass won't update it's reading and all the metrics will be blacked out on the screen.
|
||||
|
||||
Things to do next
|
||||
-----------------
|
||||
There's a GPS widget that's been developed to leverage low-power mode capability on the sensor, will look to incorporate that.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywkEIf4A/AHUBiAYWgcwDC0v+IYW///C6sC+c/kAYUj/xj/wDCgvBgfyVihhBAQQASh6TCMikvYoRkU/73CMicD+ZnFViJFBj5MBMiU/+IuBJoJkRCoUvfIPy/5kQVgM//7gBC4KCDFxSsDgTHCl8QWgaRKmBJBFIzmDSJXzYBECWobbJAAKNIMhYlBOoK/IMhZXCmYMLABAkCS4RkSXZoNJRBo/CgK6UBwTWBBIs/SJBAGl7UFegIXMaogHEehAAHj/yIYsfehAAGMQISFMRxbCiEDU4ZiQZY5iQZYpiSbQ8/cwzLOCiQA/AH4A1A"))
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,237 @@
|
|||
var background_colour = "#000000";
|
||||
var foregound_colour = "#ccff99";
|
||||
var position_colour = "#ff3329";
|
||||
var log_limit = 1000;
|
||||
var max_array_size = 50;
|
||||
var pause_tracker = false;
|
||||
var temp;
|
||||
var file;
|
||||
var d;
|
||||
var origin_lat;
|
||||
var origin_lon;
|
||||
var current_lat;
|
||||
var current_lon;
|
||||
var current_speed;
|
||||
var distance_from_origin = 0;
|
||||
var distane_travelled = 0;
|
||||
var log_size;
|
||||
var waypoints = [];
|
||||
var start_alt = 0;
|
||||
var current_alt = 0;
|
||||
var button_lock = false;
|
||||
var display_waypoints = [];
|
||||
var waypoint = {
|
||||
lat: "",
|
||||
lon: ""
|
||||
};
|
||||
var compass_heading = "---";
|
||||
|
||||
function calcCrow(lat1, lon1, lat2, lon2) {
|
||||
var R = 6371e3;
|
||||
var dLat = toRad(lat2 - lat1);
|
||||
var dLon = toRad(lon2 - lon1);
|
||||
lat1 = toRad(lat1);
|
||||
lat2 = toRad(lat2);
|
||||
|
||||
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
|
||||
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
var d = R * c;
|
||||
return d;
|
||||
}
|
||||
|
||||
function toRad(Value) {
|
||||
return Value * Math.PI / 180;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (pause_tracker)
|
||||
g.setColor(background_colour);
|
||||
else
|
||||
g.setColor(foregound_colour);
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 1);
|
||||
g.drawString(distance_from_origin.toFixed(0), 40, 220, true);
|
||||
g.drawString(distane_travelled.toFixed(0), 200, 220, true);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(start_alt.toFixed(0), 40, 120, true);
|
||||
g.drawString(current_alt.toFixed(0), 200, 120, true);
|
||||
if (button_lock) {
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString("X", 120, 220, true);
|
||||
g.setFont("6x8", 1);
|
||||
}
|
||||
}
|
||||
|
||||
function cull_array() {
|
||||
for (var i = 2; i <= waypoints.length; i += 2)
|
||||
waypoints.splice(i, 1);
|
||||
}
|
||||
|
||||
function process_and_display() {
|
||||
g.setColor(background_colour);
|
||||
g.fillRect(10, 65, 230, 230);
|
||||
g.setColor(foregound_colour);
|
||||
if (waypoints.length > max_array_size)
|
||||
cull_array();
|
||||
rescale();
|
||||
if (display_waypoints.length > 0) {
|
||||
for (let x = 0; x < display_waypoints.length - 1; x++) {
|
||||
g.reset();
|
||||
g.setColor(foregound_colour);
|
||||
g.drawLine(display_waypoints[x].lon, display_waypoints[x].lat, display_waypoints[x + 1].lon, display_waypoints[x + 1].lat);
|
||||
}
|
||||
g.reset();
|
||||
g.setColor(position_colour);
|
||||
g.fillCircle(display_waypoints[display_waypoints.length - 1].lon, display_waypoints[display_waypoints.length - 1].lat, 3);
|
||||
}
|
||||
}
|
||||
|
||||
function process_GPS() {
|
||||
if (waypoints.length > 0) {
|
||||
//check distance
|
||||
temp_distance = calcCrow(current_lat, current_lon, waypoints[waypoints.length - 1].lat, waypoints[waypoints.length - 1].lon);
|
||||
if (temp_distance > 5) {
|
||||
var temp = Object.create(waypoint);
|
||||
temp.lat = current_lat;
|
||||
temp.lon = current_lon;
|
||||
waypoints.push(temp);
|
||||
distane_travelled += temp_distance;
|
||||
distance_from_origin = calcCrow(current_lat, current_lon, waypoints[0].lat, waypoints[0].lon);
|
||||
process_and_display();
|
||||
if (log_size < log_limit) {
|
||||
var csv = [
|
||||
d,
|
||||
origin_lat - current_lat,
|
||||
current_lon - origin_lon,
|
||||
current_speed,
|
||||
current_alt
|
||||
];
|
||||
file.write(csv.join(",") + "\n");
|
||||
log_size += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
g.setColor(position_colour);
|
||||
g.fillCircle(120, 120, 3);
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function rescale() {
|
||||
var max_val = 0;
|
||||
display_waypoints = [];
|
||||
|
||||
for (let x = 0; x < waypoints.length; x++) {
|
||||
if (Math.abs(waypoints[x].lat) > max_val)
|
||||
max_val = Math.abs(waypoints[x].lat);
|
||||
if (Math.abs(waypoints[x].lon) > max_val)
|
||||
max_val = Math.abs(waypoints[x].lon);
|
||||
}
|
||||
|
||||
scaler = 60 / max_val;
|
||||
|
||||
for (let x = 0; x < waypoints.length; x++) {
|
||||
temp = Object.create(waypoint);
|
||||
temp.lat = 140 - Math.round(waypoints[x].lat * scaler);
|
||||
temp.lon = 120 - Math.round(waypoints[x].lon * scaler);
|
||||
display_waypoints.push(temp);
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
g.clear();
|
||||
process_GPS();
|
||||
var poll_GPS = setInterval(process_GPS, 9000);
|
||||
|
||||
setWatch(function () {
|
||||
if (!button_lock) {
|
||||
waypoints.splice(1);
|
||||
process_GPS();
|
||||
}
|
||||
}, BTN2, { repeat: true, edge: "falling" });
|
||||
|
||||
setWatch(function () {
|
||||
if (!button_lock) {
|
||||
if (!pause_tracker) {
|
||||
Bangle.setCompassPower(0);
|
||||
Bangle.setGPSPower(0);
|
||||
pause_tracker = true;
|
||||
}
|
||||
else {
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
pause_tracker = false;
|
||||
}
|
||||
}
|
||||
}, BTN3, { repeat: true, edge: "falling" });
|
||||
|
||||
setWatch(function () {
|
||||
if (button_lock) {
|
||||
button_lock = false;
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(" ", 120, 220, true);
|
||||
}
|
||||
else {
|
||||
button_lock = true;
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString("X", 120, 220, true);
|
||||
}
|
||||
}, BTN1, { repeat: true, edge: "falling" });
|
||||
|
||||
Bangle.on('GPS', function (g) {
|
||||
if (g.fix) {
|
||||
if (waypoints.length == 0) {
|
||||
file = require("Storage").open("alpine_log.csv", "w");
|
||||
file.write("");
|
||||
file = require("Storage").open("alpine_log.csv", "a");
|
||||
Bangle.buzz();
|
||||
position_colour = 0xF81F;
|
||||
origin_lat = g.lat;
|
||||
origin_lon = g.lon;
|
||||
start_alt = g.alt;
|
||||
current_speed = g.speed;
|
||||
sat_count = g.satellites;
|
||||
var csv = [
|
||||
d,
|
||||
origin_lat,
|
||||
origin_lon,
|
||||
current_speed,
|
||||
current_alt
|
||||
];
|
||||
file.write(csv.join(",") + "\n");
|
||||
var temp = Object.create(waypoint);
|
||||
temp.lat = 0;
|
||||
temp.lon = 0;
|
||||
process_GPS();
|
||||
waypoints.push(temp);
|
||||
}
|
||||
else {
|
||||
current_lat = g.lat - origin_lat;
|
||||
current_lon = origin_lon - g.lon;
|
||||
current_speed = g.speed;
|
||||
sat_count = g.satellites;
|
||||
current_alt = g.alt;
|
||||
gps_time = g.time;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('mag', function (m) {
|
||||
if (isNaN(m.heading))
|
||||
compass_heading = "---";
|
||||
else
|
||||
compass_heading = 360 - Math.round(m.heading);
|
||||
current_colour = g.getColor();
|
||||
g.reset();
|
||||
g.setColor(background_colour);
|
||||
g.fillRect(140, 30, 190, 55);
|
||||
g.setColor(foregound_colour);
|
||||
g.setFont("6x8", 2);
|
||||
if(compass_heading<100)
|
||||
compass_heading = " " + compass_heading.toString();
|
||||
g.drawString(compass_heading, 150, 15, true);
|
||||
});
|
||||
|
After Width: | Height: | Size: 706 KiB |
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Add BTN2 -> launcher
|
||||
0.03: Update to use setUI
|
||||
|
|
|
|||
|
|
@ -114,5 +114,5 @@ if (g.drawImages) {
|
|||
E.showMessage("Please update\nBangle.js firmware\nto use this clock","analogimgclk");
|
||||
}
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Remove messages on disconnect
|
||||
Fix music control
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4MA///xF9FstggwFDuEOAoc//gFJv/+AoZHBAgUB8/nwAFCBYIFCgYFB4AFHABdjCIPGAoPzAoPPAvpHFMpYFFPosAnk8NgYFdjEYfMo="))
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
// Config app not implemented yet
|
||||
setTimeout(()=>load("messages.app.js"),10);
|
||||
|
After Width: | Height: | Size: 636 B |
|
|
@ -0,0 +1,62 @@
|
|||
(function() {
|
||||
function gbSend(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},event));
|
||||
|
||||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() { event.t="add";require("messages").pushMessage(event); },
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
event.t=t.cmd=="incoming"?"add":"remove";
|
||||
event.id="call";
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
};
|
||||
|
||||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
gbSend({ t: "act", stp: health.steps, hrm: health.bpm });
|
||||
});
|
||||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", n:cmd });
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Fix bug if image clock wasn't installed
|
||||
0.03: Update to use setUI
|
||||
|
|
|
|||
|
|
@ -102,5 +102,5 @@ if (g.drawImages) {
|
|||
} else {
|
||||
E.showMessage("Please update\nBangle.js firmware\nto use this clock","animclk");
|
||||
}
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
0.03: Clock now shows day of week under date.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA=="))
|
||||
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 696 B |
|
|
@ -0,0 +1,6 @@
|
|||
0.01: First version
|
||||
0.02: Moved arrow image load to global scope
|
||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||
0.04: removed LED1.write() as it was keeping LCD on
|
||||
0.05: Turn compass off when screen off
|
||||
Calibrate at start if no info
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Arrow Compass
|
||||
|
||||
A variation of jeffmer's Navigation Compass. The compass points
|
||||
North and shows the current heading.
|
||||
|
||||
This is a tilt and roll compensated compass with a linear
|
||||
display. The compass will display the same direction that it shows
|
||||
when flat as when it is tilted (rotation around the W-S axis) or
|
||||
rolled (rotation around the N-S) axis. *Even with compensation, it
|
||||
would be beyond foolish to rely solely on this app for any serious
|
||||
navigational purpose.*
|
||||
|
||||

|
||||
|
||||
## 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`.
|
||||
|
||||
It is also worth noting that the presence of the magnetic charging
|
||||
clamps will require the compass to be recalibrated after every
|
||||
charge.
|
||||
|
||||
## Controls
|
||||
|
||||
*BTN1* - switches to your selected clock app.
|
||||
|
||||
*BTN2* - switches to the app launcher.
|
||||
|
||||
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
||||
|
||||
|
||||
## Issues
|
||||
* detect when calibration data is missing
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
|
||||
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1);
|
||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
var intervalRef;
|
||||
var bearing=0; // always point north
|
||||
var heading = 0;
|
||||
var oldHeading = 0;
|
||||
var candraw = false;
|
||||
var isCalibrating = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function flip1(x,y) {
|
||||
g.drawImage({width:128,height:128,bpp:1,buffer:buf1.buffer, palette:pal1color},x,y);
|
||||
buf1.clear();
|
||||
}
|
||||
|
||||
function flip2(x,y) {
|
||||
g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal2color},x,y);
|
||||
buf2.clear();
|
||||
}
|
||||
|
||||
function radians(d) {
|
||||
return (d*Math.PI) / 180;
|
||||
}
|
||||
|
||||
// takes 32ms
|
||||
function drawCompass(hd) {
|
||||
if(!candraw) return;
|
||||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||
hd=hd*Math.PI/180;
|
||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||
|
||||
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
||||
var poly = [
|
||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||
64+44.7214*Math.sin(hd+p[1]), 64-44.7214*Math.cos(hd+p[1]),
|
||||
64+28.2843*Math.sin(hd+p[2]), 64-28.2843*Math.cos(hd+p[2]),
|
||||
64+63.2455*Math.sin(hd+p[3]), 64-63.2455*Math.cos(hd+p[3]),
|
||||
64+63.2455*Math.sin(hd+p[4]), 64-63.2455*Math.cos(hd+p[4]),
|
||||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||
];
|
||||
|
||||
buf1.fillPoly(poly);
|
||||
flip1(56, 56);
|
||||
}
|
||||
|
||||
// stops violent compass swings and wobbles, takes 3ms
|
||||
function newHeading(m,h){
|
||||
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;
|
||||
}
|
||||
|
||||
// takes approx 7ms
|
||||
function tiltfixread(O,S){
|
||||
var start = Date.now();
|
||||
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;
|
||||
}
|
||||
|
||||
function reading(m) {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
var dir = bearing - heading;
|
||||
if (dir < 0) dir += 360;
|
||||
if (dir > 360) dir -= 360;
|
||||
drawCompass(dir); // we want compass to show us where to go
|
||||
oldHeading = dir;
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(-1,-1);
|
||||
buf2.setFont("Vector",38);
|
||||
var course = Math.round(heading);
|
||||
var cs = course.toString();
|
||||
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||
buf2.drawString(cs,0,0);
|
||||
flip2(90, 200);
|
||||
}
|
||||
|
||||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
function onMag(m) {
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}
|
||||
Bangle.on('mag', onMag);
|
||||
Bangle.setCompassPower(1, "app");
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
Bangle.removeListener('mag', onMag);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},30000);
|
||||
});
|
||||
}
|
||||
|
||||
function docalibrate(e,first){
|
||||
const title = "Calibrate";
|
||||
const msg = "takes 30 seconds";
|
||||
function action(b){
|
||||
if (b) {
|
||||
buf1.setColor(1);
|
||||
buf1.setFont("Vector", 20);
|
||||
buf1.setFontAlign(0,-1);
|
||||
buf1.drawString("Figure 8s",64, 0);
|
||||
buf1.drawString("to",64, 40);
|
||||
buf1.drawString("Calibrate",64, 80);
|
||||
flip1(56,56);
|
||||
|
||||
calibrate().then((r)=>{
|
||||
isCalibrating = false;
|
||||
require("Storage").write("magnav.json",r);
|
||||
Bangle.buzz();
|
||||
CALIBDATA = r;
|
||||
startdraw();
|
||||
setButtons();
|
||||
});
|
||||
} else {
|
||||
startdraw();
|
||||
setTimeout(setButtons,1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (first === undefined) first = false;
|
||||
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
isCalibrating = true;
|
||||
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
function startdraw(){
|
||||
Bangle.setCompassPower(1, "app");
|
||||
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
if (intervalRef) clearInterval(intervalRef);
|
||||
intervalRef = setInterval(reading,200);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
|
||||
Bangle.setCompassPower(0, "app");
|
||||
if (intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(()=>{load();}, BTN1, {repeat:false,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (isCalibrating) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
stopdraw();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
setButtons();
|
||||
|
||||
Bangle.setLCDPower(1);
|
||||
if (CALIBDATA) startdraw(); else docalibrate({},true);
|
||||
|
After Width: | Height: | Size: 934 B |
|
After Width: | Height: | Size: 53 KiB |
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywIebg/4AocP//AAoUf//+BYgMDh/+j/8Dol/wEAgYFBg/wgEBFIV+AQIVCh4fBnwFBgISBj8AhgJCh+Ag4BB4ED8ED+ASCAYJDBnkAvkAIYIWBjw8B/EB8AcBn//gF4DwJdBAQMA/EP738FYM8g/nz+A+EPgHx8YKBgfAjF4sAKBHIItBBQJMBFoJEBHII1BIQIDCvAUCAYYUBHIIDBMIXACgQpBRAIUBMIIrBDAIWCVYaiBTYQJCn4FBQgIIBEYKrDQ4MBVYUf8CQCCoP/w6DBAAKIBAocHAoIwBBgb5DDoYAZA="))
|
||||
|
|
@ -12,10 +12,10 @@
|
|||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d" checked><i class="form-icon"></i> 1 day (8kB)
|
||||
<input type="radio" name="agpsperiod" value="1d"><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d"><i class="form-icon"></i> 2 days (14kB)
|
||||
<input type="radio" name="agpsperiod" value="2d" checked><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
|
|
@ -84,15 +84,13 @@
|
|||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
js += "\x10E.showMessage('Uploading...','AGPS');function p(n) {g.fillRect(0,g.getHeight()-80,g.getWidth()*n,g.getHeight()-60);}";
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
js += `\x10p(${Math.round(100*i/bin.length)/100});Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
}
|
||||
js += "\x10p(1);\n"; // finish progress bar
|
||||
return js;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: Create astral clock app
|
||||
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
||||
0.03: Update to use Bangle.setUI instead of setWatch
|
||||
|
After Width: | Height: | Size: 675 KiB |
|
|
@ -0,0 +1,48 @@
|
|||
Astral Clock
|
||||
============
|
||||
Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting.
|
||||
|
||||

|
||||
<sup>(The clock does have Pluto now - felt bad for leaving it out)</sup>
|
||||
|
||||
Functions
|
||||
---------
|
||||
**BTN1**: Refreshes Alt/Az readings. The coordinates are NOT continually updated, this is to save resources and battery usage plus it avoids you having to wait for calculations to finish before you can do anything else on the watch - it doesn't take long but it could still be annoying.
|
||||
**BTN2**: Load side-menu as standard for clocks.
|
||||
**BTN3**: Changes between planet mode and extra/other targets - discussed below (will still need to press button 1 after switching to update calcs).
|
||||
**BTN4**: This is the left touchscreen, and when the LCD is on you can use this to change the font between red/white. This will only work after the GPS location has been set initially.
|
||||
|
||||
The text will turn blue during calculation and then back again once complete.
|
||||
|
||||
When you first install it, all positions will be estimated from UK as the default location and all the text will be white; from the moment you get your first GPS lock with the clock, it will save your location, recalculate accordingly and change the text to red, ideal for maintaining night vision, the calculations will also now be relevant to your location and time. If you have not used the GPS yet, I suggest using it outside briefly to get your first fix as the initial one can take a bit longer, although it should still just be a minute or 2 max normally.
|
||||
Lat and Lon are saved in a file called **astral.config**. You can review this file if you want to confirm current coordinates or even hard set different values \- although be careful doing the latter as there is no error handling to manage bad values here so you would have to delete the file and have the app generate a new one if that happens, also the GPS functionality will overwrite anything you put in here once it picks up your location.
|
||||
|
||||
There can currently be a slight error mainly to the Az at times due to a firmware issue for acos (arccosine) that affect spherical calculations but I have used an estimator function that gives a good enough accuracy for general observation so shouldn't noticeably be too far off. I\'ll be implementing acos for better accuracy when the fix is in a standard release and the update will still include the current estimate function to support a level of backward compatibility.
|
||||
|
||||
The moon phases are split into the 8 phases with an image for each - new moon would show no image.
|
||||
|
||||
The compass is displayed above the minute digits, if you get strange values or dashes the compass needs calibration but you just need to move the watch around a bit for this each time - ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or doing Wing Chun Kuen.
|
||||
|
||||
Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading.
|
||||
|
||||
Additional Astronomy Targets
|
||||
----------------------------
|
||||
There are currently 12 extra targets as default in the config file, and these were selected based on well known named objects listed in various sources as good choices for both binoculars and telescopes. The objects are processed and then 9 are displayed and ordered descendingly by altitude on the basis those higher up will have better visibility.
|
||||
|
||||
You can input different objects rather than those listed in the galaxies/extras mode by changing the astral.config file with the relevant details for: Object name, Right Ascension and Declination, below is an example. Again, there is little in the way of error handling to streamline the app so be sure to input these in exactly the same format as you see in the file, namely signed 6 digit values with double quotes, example:
|
||||
|
||||
*{"name": "Andromeda", "ra": "004244", "de": "411609", "type": 3}*
|
||||
|
||||
The type property is not utilised as yet but relates to whether the object is (in order): a cluster, nebula or galaxy. If you try putting more than 12 or so, the clock will try processing all of them but I advise against doing that because you will get memory errors if you put in too many. A better approach is to put a limited set in seasonally based on what's best in your location.
|
||||
|
||||
Updates & Feedback
|
||||
------------------
|
||||
Put together, initially at least, by \"Ben Jabituya\", https://jabituyaben.wixsite.com/majorinput, jabituyaben@gmail.com. Feel free to get in touch for any feature request. Also I\'m not precious at all - if you know of efficiencies or improvements you could make, just put the changes in. One thing that would probably be ideal is to change some of the functions to inline C to make it faster.
|
||||
|
||||
Credit to various sources from which I have literally taken source code and shoehorned to fit on the Bangle:
|
||||
|
||||
-Stephen R. Schmitt:
|
||||
https://codepen.io/lulunac27/full/NRoyxE
|
||||
|
||||
-(Not sure who put this one together initially):
|
||||
http://www.voidware.com/moon_phase.htm
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUyxH+AH4AG3YAGF1w0oExYykEZwyhEIyRJGUAfEYpgxjLxQNEGEajMGTohPGMBTQOZwwTGKoyXDASVWGSwtHKYYAJZbYVEGR7bSGKQWkDRQbOCAoxYRI4wMCIYxXXpQSYP6L4NCRLGXLZwdVMJwAWGKgwbD6aUTSzoRKfCAxbAogcJBxQx/GP4x/GP4xNAAoKKBxwxaGRQZPSqwZmGOZ7VY8oxnPZoJPGP57TBJavWGL7gRRaiPVGJxRGBJgxcACYxfHJIRLSrTHxGODHvGSgwcAEY="))
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1,853 @@
|
|||
setupcomplete_colour = "#ff3329";
|
||||
default_colour = "#ffffff";
|
||||
calc_display_colour = "#00FFFF";
|
||||
display_colour = default_colour;
|
||||
|
||||
var processing = false;
|
||||
var all_extras_array = [];
|
||||
var ready_to_compute = false;
|
||||
var mode = "planetary";
|
||||
var modeswitch = false;
|
||||
|
||||
var colours_switched = false;
|
||||
|
||||
// Load fonts
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
// position on screen
|
||||
const Xaxis = 150, Yaxis = 55;
|
||||
|
||||
//lat lon settings loading
|
||||
var astral_settings;
|
||||
var config_file = require("Storage").open("astral.config.txt", "r");
|
||||
var test_file = config_file.read(config_file.getLength());
|
||||
|
||||
if (test_file !== undefined) {
|
||||
astral_settings = JSON.parse(test_file);
|
||||
if (astral_settings.astral_default)
|
||||
display_colour = default_colour;
|
||||
else
|
||||
display_colour = setupcomplete_colour;
|
||||
}
|
||||
|
||||
if (astral_settings === undefined) {
|
||||
astral_settings = {
|
||||
version: 1,
|
||||
lat: 51.5074,
|
||||
lon: 0.1278,
|
||||
astral_default: true,
|
||||
extras: [
|
||||
{ name: "Andromeda", ra: "004244", de: "411609", type: 3 },
|
||||
{ name: "Cigar", ra: "095552", de: "694047", type: 3 },
|
||||
{ name: "Pinwheel", ra: "140313", de: "542057", type: 3 },
|
||||
{ name: "Whirlpool", ra: "132953", de: "471143", type: 3 },
|
||||
{ name: "Orion", ra: "053517", de: "-052328", type: 2 },
|
||||
{ name: "Hercules", ra: "160515", de: "174455", type: 1 },
|
||||
{ name: "Beehive", ra: "084024", de: "195900", type: 1 },
|
||||
{ name: "SilverCoin", ra: "004733", de: "-251718", type: 3 },
|
||||
{ name: "Lagoon", ra: "180337", de: "-242312", type: 2 },
|
||||
{ name: "Trifid", ra: "180223", de: " -230148", type: 2 },
|
||||
{ name: "Dumbbell", ra: "195935", de: "224316", type: 2 },
|
||||
{ name: "Pleiades", ra: "034724", de: "240700", type: 1 }
|
||||
]
|
||||
};
|
||||
config_file = require("Storage").open("astral.config.txt", "w");
|
||||
config_file.write(JSON.stringify(astral_settings));
|
||||
}
|
||||
|
||||
var compass_heading = "--";
|
||||
var current_moonphase;
|
||||
var year;
|
||||
var month;
|
||||
var day;
|
||||
var hour;
|
||||
var mins;
|
||||
var secs;
|
||||
|
||||
var calc = {
|
||||
lat_degrees: "",
|
||||
lat_minutes: "",
|
||||
lon_degrees: "",
|
||||
lon_minutes: "",
|
||||
month: "",
|
||||
day: "",
|
||||
hour: "",
|
||||
minute: "",
|
||||
second: "",
|
||||
thisday: "",
|
||||
south: "",
|
||||
north: "",
|
||||
west: "",
|
||||
east: ""
|
||||
};
|
||||
|
||||
var pstrings = [];
|
||||
|
||||
var pname = new Array("Mercury", "Venus", "Sun",
|
||||
"Mars", "Jupiter", "Saturn ",
|
||||
"Uranus ", "Neptune", "Pluto");
|
||||
|
||||
function acos_estimate(x) {
|
||||
return (-0.69813170079773212 * x * x - 0.87266462599716477) * x + 1.5707963267948966;
|
||||
}
|
||||
|
||||
function ConvertDEGToDMS(deg, lat) {
|
||||
var absolute = Math.abs(deg);
|
||||
var degrees = Math.floor(absolute);
|
||||
var minutesNotTruncated = (absolute - degrees) * 60;
|
||||
var minutes = Math.floor(minutesNotTruncated);
|
||||
return minutes;
|
||||
}
|
||||
|
||||
function test() {
|
||||
// coords = [42.407211,-71.082439];
|
||||
coords = [astral_settings.lat, astral_settings.lon];
|
||||
//coords = [-33.8688, 133.775];
|
||||
calc.lat_degrees = Math.abs(coords[0]).toFixed(0);
|
||||
calc.lon_degrees = Math.abs(coords[1]).toFixed(0);
|
||||
|
||||
calc.lat_minutes = ConvertDEGToDMS(coords[0], true).toString();
|
||||
calc.lon_minutes = ConvertDEGToDMS(coords[1]).toString();
|
||||
|
||||
if (coords[1] < 0) {
|
||||
calc.west = false;
|
||||
calc.east = true;
|
||||
}
|
||||
else {
|
||||
calc.west = true;
|
||||
calc.east = false;
|
||||
}
|
||||
if (coords[0] < 0) {
|
||||
calc.south = true;
|
||||
calc.north = false;
|
||||
}
|
||||
else {
|
||||
calc.south = false;
|
||||
calc.north = true;
|
||||
}
|
||||
}
|
||||
|
||||
var DEGS = 180 / Math.PI; // convert radians to degrees
|
||||
var RADS = Math.PI / 180; // convert degrees to radians
|
||||
var EPS = 1.0e-12; // machine error constant
|
||||
|
||||
// right ascension, declination coordinate structure
|
||||
function coord() {
|
||||
ra = parseFloat("0"); // right ascension [deg]
|
||||
dec = parseFloat("0"); // declination [deg]
|
||||
rvec = parseFloat("0"); // distance [AU]
|
||||
}
|
||||
|
||||
// altitude, azimuth coordinate structure
|
||||
function horizon() {
|
||||
alt = parseFloat("0"); // altitude [deg]
|
||||
az = parseFloat("0"); // azimuth [deg]
|
||||
}
|
||||
|
||||
// orbital element structure
|
||||
function elem() {
|
||||
a = parseFloat("0"); // semi-major axis [AU]
|
||||
e = parseFloat("0"); // eccentricity of orbit
|
||||
i = parseFloat("0"); // inclination of orbit [deg]
|
||||
O = parseFloat("0"); // longitude of the ascending node [deg]
|
||||
w = parseFloat("0"); // longitude of perihelion [deg]
|
||||
L = parseFloat("0"); // mean longitude [deg]
|
||||
}
|
||||
|
||||
function process_extras_coord(coord_string) {
|
||||
var extras_second = parseInt(coord_string.slice(-2));
|
||||
var extras_minute;
|
||||
var extras_hour;
|
||||
var extras_calc;
|
||||
|
||||
var extras_signcheck = coord_string.charAt(0);
|
||||
|
||||
if (extras_signcheck == "-") {
|
||||
extras_minute = parseInt(coord_string.slice(3, -2));
|
||||
extras_hour = parseInt(coord_string.slice(1, 3));
|
||||
extras_calc = (extras_hour + extras_minute / 60 + extras_second / 3600) * -1;
|
||||
}
|
||||
else {
|
||||
extras_minute = parseInt(coord_string.slice(2, -2));
|
||||
extras_hour = parseInt(coord_string.slice(0, 2));
|
||||
extras_calc = extras_hour + extras_minute / 60 + extras_second / 3600;
|
||||
}
|
||||
return extras_calc;
|
||||
}
|
||||
|
||||
// compute ...
|
||||
function compute() {
|
||||
var lat_degrees = parseInt(calc.lat_degrees, 10);
|
||||
var lat_minutes = parseInt(calc.lat_minutes, 10);
|
||||
var lon_degrees = parseInt(calc.lon_degrees, 10);
|
||||
var lon_minutes = parseInt(calc.lon_minutes, 10);
|
||||
|
||||
var now = new Date();
|
||||
year = now.getFullYear();
|
||||
month = now.getMonth() + 1;
|
||||
day = now.getDay();
|
||||
hour = now.getHours();
|
||||
mins = now.getMinutes();
|
||||
secs = now.getSeconds();
|
||||
|
||||
if (isNaN(lat_degrees) || (lat_degrees < 0) || (lat_degrees >= 90) ||
|
||||
isNaN(lat_minutes) || (lat_minutes < 0) || (lat_minutes >= 60) ||
|
||||
isNaN(lon_degrees) || (lon_degrees < 0) || (lon_degrees >= 180) ||
|
||||
isNaN(lon_minutes) || (lon_minutes < 0) || (lon_minutes >= 60)) {
|
||||
print("Invalid input!");
|
||||
return;
|
||||
}
|
||||
|
||||
var lat = dms2real(lat_degrees, lat_minutes, 0);
|
||||
var lon = dms2real(lon_degrees, lon_minutes, 0);
|
||||
if (calc.south == true) lat = -lat;
|
||||
if (calc.west == true) lon = -lon;
|
||||
|
||||
// compute day number for date/time
|
||||
var dn = day_number(year, month, day, hour, mins);
|
||||
|
||||
var p;
|
||||
var obj = new coord();
|
||||
var h = new horizon();
|
||||
|
||||
pstrings = [];
|
||||
|
||||
if (mode == "planetary") {
|
||||
for (p = 0; p < 9; p++) {
|
||||
get_coord(obj, p, dn);
|
||||
coord_to_horizon(now, obj.ra, obj.dec, lat, lon, h);
|
||||
display_string = (pname[p] + " " + dec2str(h.alt) + " " + degr2str(h.az));
|
||||
|
||||
pstrings.push(display_string);
|
||||
}
|
||||
}
|
||||
else {
|
||||
all_extras_arrray = [];
|
||||
for (p = 0; p < astral_settings.extras.length; p++) {
|
||||
var extras_ra = process_extras_coord(astral_settings.extras[p].ra);
|
||||
extras_ra *= 15;
|
||||
|
||||
var extras_dec = process_extras_coord(astral_settings.extras[p].de);
|
||||
|
||||
coord_to_horizon(now, extras_ra, extras_dec, lat, lon, h);
|
||||
display_string = (astral_settings.extras[p].name + " " + dec2str(h.alt) + " " + degr2str(h.az));
|
||||
|
||||
all_extras_array.push([h.alt, display_string]);
|
||||
}
|
||||
|
||||
all_extras_array.sort(function (a, b) {
|
||||
return b[0] - a[0];
|
||||
});
|
||||
|
||||
for (p = 0; p < 9; p++) {
|
||||
pstrings.push(all_extras_array[p][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// day number to/from J2000 (Jan 1.5, 2000)
|
||||
function day_number(y, m, d, hour, mins) {
|
||||
var h = hour + mins / 60;
|
||||
var rv = 367 * y
|
||||
- Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4)
|
||||
+ Math.floor(275 * m / 9) + d - 730531.5 + h / 24;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// compute RA, DEC, and distance of planet-p for day number-d
|
||||
// result returned in structure obj in degrees and astronomical units
|
||||
function get_coord(obj, p, d) {
|
||||
var planet = new elem();
|
||||
mean_elements(planet, p, d);
|
||||
var ap = planet.a;
|
||||
var ep = planet.e;
|
||||
var ip = planet.i;
|
||||
var op = planet.O;
|
||||
var pp = planet.w;
|
||||
var lp = planet.L;
|
||||
|
||||
var earth = new elem();
|
||||
mean_elements(earth, 2, d);
|
||||
var ae = earth.a;
|
||||
var ee = earth.e;
|
||||
var ie = earth.i;
|
||||
var oe = earth.O;
|
||||
var pe = earth.w;
|
||||
var le = earth.L;
|
||||
|
||||
// position of Earth in its orbit
|
||||
var me = mod2pi(le - pe);
|
||||
var ve = true_anomaly(me, ee);
|
||||
var re = ae * (1 - ee * ee) / (1 + ee * Math.cos(ve));
|
||||
|
||||
// heliocentric rectangular coordinates of Earth
|
||||
var xe = re * Math.cos(ve + pe);
|
||||
var ye = re * Math.sin(ve + pe);
|
||||
var ze = 0.0;
|
||||
|
||||
// position of planet in its orbit
|
||||
var mp = mod2pi(lp - pp);
|
||||
var vp = true_anomaly(mp, planet.e);
|
||||
var rp = ap * (1 - ep * ep) / (1 + ep * Math.cos(vp));
|
||||
|
||||
// heliocentric rectangular coordinates of planet
|
||||
var xh = rp * (Math.cos(op) * Math.cos(vp + pp - op) - Math.sin(op) * Math.sin(vp + pp - op) * Math.cos(ip));
|
||||
var yh = rp * (Math.sin(op) * Math.cos(vp + pp - op) + Math.cos(op) * Math.sin(vp + pp - op) * Math.cos(ip));
|
||||
var zh = rp * (Math.sin(vp + pp - op) * Math.sin(ip));
|
||||
|
||||
if (p == 2) // earth --> compute sun
|
||||
{
|
||||
xh = 0;
|
||||
yh = 0;
|
||||
zh = 0;
|
||||
}
|
||||
|
||||
// convert to geocentric rectangular coordinates
|
||||
var xg = xh - xe;
|
||||
var yg = yh - ye;
|
||||
var zg = zh - ze;
|
||||
|
||||
// rotate around x axis from ecliptic to equatorial coords
|
||||
var ecl = 23.439281 * RADS; //value for J2000.0 frame
|
||||
var xeq = xg;
|
||||
var yeq = yg * Math.cos(ecl) - zg * Math.sin(ecl);
|
||||
var zeq = yg * Math.sin(ecl) + zg * Math.cos(ecl);
|
||||
|
||||
// find the RA and DEC from the rectangular equatorial coords
|
||||
obj.ra = mod2pi(Math.atan2(yeq, xeq)) * DEGS;
|
||||
obj.dec = Math.atan(zeq / Math.sqrt(xeq * xeq + yeq * yeq)) * DEGS;
|
||||
obj.rvec = Math.sqrt(xeq * xeq + yeq * yeq + zeq * zeq);
|
||||
}
|
||||
|
||||
// Compute the elements of the orbit for planet-i at day number-d
|
||||
// result is returned in structure p
|
||||
function mean_elements(p, i, d) {
|
||||
var cy = d / 36525; // centuries since J2000
|
||||
|
||||
switch (i) {
|
||||
case 0: // Mercury
|
||||
p.a = 0.38709893 + 0.00000066 * cy;
|
||||
p.e = 0.20563069 + 0.00002527 * cy;
|
||||
p.i = (7.00487 - 23.51 * cy / 3600) * RADS;
|
||||
p.O = (48.33167 - 446.30 * cy / 3600) * RADS;
|
||||
p.w = (77.45645 + 573.57 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((252.25084 + 538101628.29 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 1: // Venus
|
||||
p.a = 0.72333199 + 0.00000092 * cy;
|
||||
p.e = 0.00677323 - 0.00004938 * cy;
|
||||
p.i = (3.39471 - 2.86 * cy / 3600) * RADS;
|
||||
p.O = (76.68069 - 996.89 * cy / 3600) * RADS;
|
||||
p.w = (131.53298 - 108.80 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((181.97973 + 210664136.06 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 2: // Earth/Sun
|
||||
p.a = 1.00000011 - 0.00000005 * cy;
|
||||
p.e = 0.01671022 - 0.00003804 * cy;
|
||||
p.i = (0.00005 - 46.94 * cy / 3600) * RADS;
|
||||
p.O = (-11.26064 - 18228.25 * cy / 3600) * RADS;
|
||||
p.w = (102.94719 + 1198.28 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((100.46435 + 129597740.63 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 3: // Mars
|
||||
p.a = 1.52366231 - 0.00007221 * cy;
|
||||
p.e = 0.09341233 + 0.00011902 * cy;
|
||||
p.i = (1.85061 - 25.47 * cy / 3600) * RADS;
|
||||
p.O = (49.57854 - 1020.19 * cy / 3600) * RADS;
|
||||
p.w = (336.04084 + 1560.78 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((355.45332 + 68905103.78 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 4: // Jupiter
|
||||
p.a = 5.20336301 + 0.00060737 * cy;
|
||||
p.e = 0.04839266 - 0.00012880 * cy;
|
||||
p.i = (1.30530 - 4.15 * cy / 3600) * RADS;
|
||||
p.O = (100.55615 + 1217.17 * cy / 3600) * RADS;
|
||||
p.w = (14.75385 + 839.93 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((34.40438 + 10925078.35 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 5: // Saturn
|
||||
p.a = 9.53707032 - 0.00301530 * cy;
|
||||
p.e = 0.05415060 - 0.00036762 * cy;
|
||||
p.i = (2.48446 + 6.11 * cy / 3600) * RADS;
|
||||
p.O = (113.71504 - 1591.05 * cy / 3600) * RADS;
|
||||
p.w = (92.43194 - 1948.89 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((49.94432 + 4401052.95 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 6: // Uranus
|
||||
p.a = 19.19126393 + 0.00152025 * cy;
|
||||
p.e = 0.04716771 - 0.00019150 * cy;
|
||||
p.i = (0.76986 - 2.09 * cy / 3600) * RADS;
|
||||
p.O = (74.22988 - 1681.40 * cy / 3600) * RADS;
|
||||
p.w = (170.96424 + 1312.56 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((313.23218 + 1542547.79 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 7: // Neptune
|
||||
p.a = 30.06896348 - 0.00125196 * cy;
|
||||
p.e = 0.00858587 + 0.00002510 * cy;
|
||||
p.i = (1.76917 - 3.64 * cy / 3600) * RADS;
|
||||
p.O = (131.72169 - 151.25 * cy / 3600) * RADS;
|
||||
p.w = (44.97135 - 844.43 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((304.88003 + 786449.21 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 8: // Pluto
|
||||
p.a = 39.48168677 - 0.00076912 * cy;
|
||||
p.e = 0.24880766 + 0.00006465 * cy;
|
||||
p.i = (17.14175 + 11.07 * cy / 3600) * RADS;
|
||||
p.O = (110.30347 - 37.33 * cy / 3600) * RADS;
|
||||
p.w = (224.06676 - 132.25 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((238.92881 + 522747.90 * cy / 3600) * RADS);
|
||||
break;
|
||||
default:
|
||||
print("function mean_elements() failed!");
|
||||
}
|
||||
}
|
||||
|
||||
// compute the true anomaly from mean anomaly using iteration
|
||||
// M - mean anomaly in radians
|
||||
// e - orbit eccentricity
|
||||
function true_anomaly(M, e) {
|
||||
var V, E1;
|
||||
|
||||
// initial approximation of eccentric anomaly
|
||||
var E = M + e * Math.sin(M) * (1.0 + e * Math.cos(M));
|
||||
|
||||
do // iterate to improve accuracy
|
||||
{
|
||||
E1 = E;
|
||||
E = E1 - (E1 - e * Math.sin(E1) - M) / (1 - e * Math.cos(E1));
|
||||
}
|
||||
while (Math.abs(E - E1) > EPS);
|
||||
|
||||
// convert eccentric anomaly to true anomaly
|
||||
V = 2 * Math.atan(Math.sqrt((1 + e) / (1 - e)) * Math.tan(0.5 * E));
|
||||
|
||||
if (V < 0) V = V + (2 * Math.PI); // modulo 2pi
|
||||
|
||||
return V;
|
||||
}
|
||||
|
||||
// converts hour angle in degrees into hour angle string
|
||||
function ha2str(x) {
|
||||
if ((x < 0) || (360 < x)) print("function ha2str() range error!");
|
||||
|
||||
var ra = x / 15; // degrees to hours
|
||||
var h = Math.floor(ra);
|
||||
var m = 60 * (ra - h);
|
||||
return cintstr(h, 3) + "h " + frealstr(m, 4, 1) + "m";
|
||||
}
|
||||
|
||||
// converts declination angle in degrees into string
|
||||
function dec2str(x) {
|
||||
if ((x < -90) || (+90 < x)) print("function dec2str() range error!");
|
||||
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 2) + "° " + frealstr(m, 4, 1) + "'";
|
||||
}
|
||||
|
||||
// return the integer part of a number
|
||||
function abs_floor(x) {
|
||||
var r;
|
||||
if (x >= 0.0) r = Math.floor(x);
|
||||
else r = Math.ceil(x);
|
||||
return r;
|
||||
}
|
||||
|
||||
// return an angle in the range 0 to 2pi radians
|
||||
function mod2pi(x) {
|
||||
var b = x / (2 * Math.PI);
|
||||
var a = (2 * Math.PI) * (b - abs_floor(b));
|
||||
if (a < 0) a = (2 * Math.PI) + a;
|
||||
return a;
|
||||
}
|
||||
|
||||
//
|
||||
// compute horizon coordinates from ra, dec, lat, lon, and utc
|
||||
// ra, dec, lat, lon in degrees
|
||||
// utc is a time number in seconds
|
||||
//
|
||||
// results returned in h : horizon record structure
|
||||
//
|
||||
function coord_to_horizon(utc, ra, dec, lat, lon, h) {
|
||||
var lmst, ha, sin_alt, cos_az, alt, az;
|
||||
|
||||
// compute hour angle in degrees
|
||||
ha = mean_sidereal_time(0) - ra;
|
||||
//ha = mean_sidereal_time(lon) - ra;
|
||||
if (ha < 0) ha = ha + 360;
|
||||
|
||||
// convert degrees to radians
|
||||
ha = ha * RADS;
|
||||
dec = dec * RADS;
|
||||
lat = lat * RADS;
|
||||
|
||||
|
||||
// compute altitude in radians
|
||||
sin_alt = Math.sin(dec) * Math.sin(lat) + Math.cos(dec) * Math.cos(lat) * Math.cos(ha);
|
||||
alt = Math.asin(sin_alt);
|
||||
|
||||
// compute azimuth in radians
|
||||
// divide by zero error at poles or if alt = 90 deg
|
||||
cos_az = (Math.sin(dec) - Math.sin(alt) * Math.sin(lat)) / (Math.cos(alt) * Math.cos(lat));
|
||||
//az = Math.acos(cos_az);
|
||||
|
||||
az = acos_estimate(cos_az);
|
||||
|
||||
// convert radians to degrees
|
||||
h.alt = alt * DEGS;
|
||||
h.az = az * DEGS;
|
||||
|
||||
// choose hemisphere
|
||||
if (Math.sin(ha) > 0) h.az = 360 - h.az;
|
||||
}
|
||||
|
||||
//
|
||||
// "mean_sidereal_time" returns the Mean Sidereal Time in units of degrees.
|
||||
// Use lon = 0 to get the Greenwich MST.
|
||||
// East longitudes are positive; West longitudes are negative
|
||||
//
|
||||
// returns: time in degrees
|
||||
//
|
||||
function mean_sidereal_time(lon) {
|
||||
|
||||
if ((month == 1) || (month == 2)) {
|
||||
year = year - 1;
|
||||
month = month + 12;
|
||||
}
|
||||
|
||||
var a = Math.floor(year / 100);
|
||||
// var a = Math.floor(2019 / 100);
|
||||
|
||||
var b = 2 - a + Math.floor(a / 4);
|
||||
var c = Math.floor(365.25 * year);
|
||||
var da = Math.floor(30.6001 * (month + 1));
|
||||
|
||||
// days since J2000.0
|
||||
var jd = b + c + da - 730550.5 + day
|
||||
+ (hour + mins / 60.0 + secs / 3600.0) / 24.0;
|
||||
|
||||
// julian centuries since J2000.0
|
||||
var jt = jd / 36525.0;
|
||||
|
||||
// mean sidereal time
|
||||
var mst = 280.46061837 + 360.98564736629 * jd
|
||||
+ 0.000387933 * jt * jt - jt * jt * jt / 38710000 + lon;
|
||||
|
||||
if (mst > 0.0) {
|
||||
while (mst > 360.0)
|
||||
mst = mst - 360.0;
|
||||
}
|
||||
else {
|
||||
while (mst < 0.0)
|
||||
mst = mst + 360.0;
|
||||
}
|
||||
return mst;
|
||||
}
|
||||
|
||||
// convert angle (deg, min, sec) to degrees as real
|
||||
function dms2real(deg, min, sec) {
|
||||
var rv;
|
||||
if (deg < 0) rv = deg - min / 60 - sec / 3600;
|
||||
else rv = deg + min / 60 + sec / 3600;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// converts angle in degrees into string
|
||||
function degr2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'";
|
||||
}
|
||||
|
||||
// converts latitude in signed degrees into string
|
||||
function lat2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? " S" : " N";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
}
|
||||
|
||||
// converts longitude in signed degrees into string
|
||||
function lon2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? " W" : " E";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
}
|
||||
|
||||
// format two digits with leading zero if needed
|
||||
function d2(n) {
|
||||
if ((n < 0) || (99 < n)) return "xx";
|
||||
return (n < 10) ? ("0" + n) : n;
|
||||
}
|
||||
|
||||
// UTILITY FUNCTIONS
|
||||
|
||||
// format an integer
|
||||
function cintstr(num, width) {
|
||||
var str = num.toString(10);
|
||||
var len = str.length;
|
||||
var intgr = "";
|
||||
var i;
|
||||
|
||||
for (i = 0; i < width - len; i++) // append leading spaces
|
||||
intgr += ' ';
|
||||
|
||||
for (i = 0; i < len; i++) // append digits
|
||||
intgr += str.charAt(i);
|
||||
|
||||
return intgr;
|
||||
}
|
||||
|
||||
function frealstr(num, width, fract) {
|
||||
var str = num.toFixed(fract);
|
||||
var len = str.length;
|
||||
var real = "";
|
||||
var i;
|
||||
|
||||
for (i = 0; i < width - len; i++) // append leading spaces
|
||||
real += ' ';
|
||||
|
||||
for (i = 0; i < len; i++) // append digits
|
||||
real += str.charAt(i);
|
||||
|
||||
return real;
|
||||
}
|
||||
|
||||
function getMoonPhase() {
|
||||
var now = new Date();
|
||||
year = now.getFullYear();
|
||||
month = now.getMonth() + 1;
|
||||
day = now.getDate();
|
||||
|
||||
if (month < 3) {
|
||||
year = year - 1;
|
||||
month += 12;
|
||||
}
|
||||
month = month + 1;
|
||||
c = 365.25 * year;
|
||||
e = 30.6 * month;
|
||||
jd = c + e + day - 694039.09; //jd is total days elapsed
|
||||
jd /= 29.5305882; //divide by the moon cycle
|
||||
b = parseInt(jd); //int(jd) -> b, take integer part of jd
|
||||
jd -= b; //subtract integer part to leave fractional part of original jd
|
||||
b = Math.round(jd * 8); //scale fraction from 0-8 and round
|
||||
if (b >= 8) {
|
||||
b = 0; //0 and 8 are the same so turn 8 into 0
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
function write_refresh_note(colour) {
|
||||
g.setColor(colour);
|
||||
cursor = Yaxis + 50;
|
||||
if (!ready_to_compute) {
|
||||
g.drawString("mode change:", Xaxis + 50, cursor, false);
|
||||
cursor += 15;
|
||||
g.drawString("BTN1 to refresh", Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
g.drawString("BTN3 to cancel", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, false);
|
||||
}
|
||||
|
||||
function draw_moon(phase) {
|
||||
g.setColor(display_colour);
|
||||
if (phase == 5) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(220, 25, 240, 90);
|
||||
}
|
||||
else if (phase == 6) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(200, 25, 240, 90);
|
||||
}
|
||||
else if (phase == 1) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(180, Yaxis, 30);
|
||||
}
|
||||
else if (phase == 4)
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
else if (phase == 3) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 25, 180, 90);
|
||||
}
|
||||
else if (phase == 2) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 25, 200, 90);
|
||||
}
|
||||
else if (phase == 7) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(220, Yaxis, 30);
|
||||
}
|
||||
g.setColor(display_colour);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (astral_settings.astral_default)
|
||||
display_colour = default_colour;
|
||||
else if (!colours_switched)
|
||||
display_colour = setupcomplete_colour;
|
||||
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var h = d.getHours(), m = d.getMinutes();
|
||||
var time = (" " + h).substr(-2) + ":" + ("0" + m).substr(-2);
|
||||
// Reset the state of the graphics library
|
||||
g.reset();
|
||||
g.setColor(display_colour);
|
||||
// draw the current time (4x size 7 segment)
|
||||
g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFontAlign(1, 1); // align right bottom
|
||||
g.drawString(time, Xaxis + 20, Yaxis + 30, true /*clear background*/);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(1, 1); // align center bottom
|
||||
// pad the date - this clears the background if the date were to change length
|
||||
var dateStr = " " + require("locale").date(d) + " ";
|
||||
g.drawString(dateStr, Xaxis - 40, Yaxis - 40, true /*clear background*/);
|
||||
|
||||
//compute location of objects
|
||||
g.setFontAlign(1, 1);
|
||||
g.setFont("6x8");
|
||||
|
||||
if (ready_to_compute)
|
||||
g.setColor(calc_display_colour);
|
||||
|
||||
if (modeswitch)
|
||||
g.setColor("#000000");
|
||||
|
||||
cursor = Yaxis + 50;
|
||||
if (pstrings.length == 0) {
|
||||
if (ready_to_compute)
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, true);
|
||||
else
|
||||
g.drawString("press BTN1 to update", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < pstrings.length; i++) {
|
||||
g.drawString(pstrings[i], Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
}
|
||||
}
|
||||
|
||||
if (modeswitch)
|
||||
if (ready_to_compute)
|
||||
write_refresh_note(calc_display_colour);
|
||||
else
|
||||
write_refresh_note(display_colour);
|
||||
|
||||
if (ready_to_compute) {
|
||||
processing = true;
|
||||
ready_to_compute = false;
|
||||
test();
|
||||
compute();
|
||||
g.setColor("#000000");
|
||||
g.fillRect(Xaxis - 150, Yaxis + 40, Xaxis + 200, Yaxis + 200);
|
||||
modeswitch = false;
|
||||
processing = false;
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
current_moonphase = getMoonPhase();
|
||||
all_extras_array = [];
|
||||
}
|
||||
|
||||
g.clear();
|
||||
current_moonphase = getMoonPhase();
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
draw_moon(current_moonphase);
|
||||
draw();
|
||||
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower', on => {
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
Bangle.setCompassPower(0);
|
||||
if (!astral_settings.astral_default)
|
||||
Bangle.setGPSPower(0);
|
||||
if (on) {
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
if (current_moonphase !== undefined) {
|
||||
draw_moon(current_moonphase);
|
||||
}
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setClockMode();
|
||||
|
||||
Bangle.setUI("clockupdown", btn => {
|
||||
if (btn==0) {
|
||||
if (!processing) {
|
||||
if (!modeswitch) {
|
||||
modeswitch = true;
|
||||
if (mode == "planetary") mode = "extras";
|
||||
else mode = "planetary";
|
||||
}
|
||||
else
|
||||
modeswitch = false;
|
||||
}
|
||||
} else {
|
||||
if (!processing)
|
||||
ready_to_compute = true;
|
||||
}
|
||||
});
|
||||
|
||||
setWatch(function () {
|
||||
if (!astral_settings.astral_default) {
|
||||
colours_switched = true;
|
||||
if (display_colour == setupcomplete_colour)
|
||||
display_colour = default_colour;
|
||||
else
|
||||
display_colour = setupcomplete_colour;
|
||||
draw_moon(current_moonphase);
|
||||
}
|
||||
}, BTN4, { repeat: true });
|
||||
|
||||
//events
|
||||
Bangle.on('mag', function (m) {
|
||||
g.setFont("6x8",2);
|
||||
if (isNaN(m.heading))
|
||||
compass_heading = "---";
|
||||
else
|
||||
compass_heading = 360 - Math.round(m.heading);
|
||||
// g.setColor("#000000");
|
||||
// g.fillRect(160, 10, 160, 20);
|
||||
g.setColor(display_colour);
|
||||
if(compass_heading<100)
|
||||
compass_heading = " " + compass_heading;
|
||||
g.drawString(compass_heading, 150, 20, true /*clear background*/);
|
||||
});
|
||||
|
||||
Bangle.on('GPS', function (g) {
|
||||
if (g.fix) {
|
||||
astral_settings.lat = g.lat;
|
||||
astral_settings.lon = g.lon;
|
||||
astral_settings.astral_default = false;
|
||||
config_file = require("Storage").open("astral.config.txt", "w");
|
||||
config_file.write(JSON.stringify(astral_settings));
|
||||
}
|
||||
});
|
||||
|
|
@ -1 +1,2 @@
|
|||
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
|
||||
0.03: Bangle 2 support
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
Bangle.setLCDMode("doublebuffered");
|
||||
|
||||
var BTNL, BTNR, BTNU, BTNA;
|
||||
if (process.env.HWVERSION==2) {
|
||||
var tap = {};
|
||||
// use tapping on screen for left,right,accel
|
||||
Bangle.on('drag',e=>tap=e);
|
||||
BTNL = { read : _=>tap.b && tap.x < 58};
|
||||
BTNR = { read : _=>tap.b && tap.x > 117};
|
||||
BTNU = { read : _=>tap.b && tap.x > 58 && tap.x < 117};
|
||||
// use button for fire
|
||||
BTNA = BTN1;
|
||||
} else {
|
||||
// use hard buttons
|
||||
BTNL = BTN4;
|
||||
BTNR = BTN5;
|
||||
BTNU = BTN1;
|
||||
BTNA = BTN2;
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
}
|
||||
var W = g.getWidth();
|
||||
var H = g.getHeight();
|
||||
g.setFontAlign(0,-1);
|
||||
var BTNL = BTN4;
|
||||
var BTNR = BTN5;
|
||||
var BTNU = BTN1;
|
||||
var BTNA = BTN2;
|
||||
g.clear().setFontAlign(0,-1);
|
||||
|
||||
function newAst(x,y) {
|
||||
var a = {
|
||||
|
|
@ -92,8 +104,7 @@ function onFrame() {
|
|||
}
|
||||
|
||||
g.clear();
|
||||
|
||||
g.drawString(score,120,0);
|
||||
g.drawString(score,W-20,0);
|
||||
var rs = Math.PI*0.8;
|
||||
g.drawPoly([
|
||||
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: First release
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Authentiwatch - 2FA Authenticator
|
||||
|
||||
## Supports
|
||||
|
||||
* Google Authenticator compatible 2-factor authentication
|
||||
* Hash calculations:
|
||||
* Bangle 1: SHA1 only (same as Google Authenticator)
|
||||
* Bangle 2: All (SHA1, SHA256, SHA512)
|
||||
* Timed (TOTP) and Counter (HOTP) modes
|
||||
* Custom periods
|
||||
* Between 6 and 10 digits
|
||||
* Phone/PC configuration web page:
|
||||
* Add/edit/delete/arrange tokens
|
||||
* Scan QR codes
|
||||
* Produce scannable QR codes
|
||||
|
||||
## Usage
|
||||
|
||||
* Use the Phone/PC web page interface to manage the tokens stored on the watch.
|
||||
* Tokens are stored *ONLY* on the watch.
|
||||
* Swipe right to exit to the app launcher.
|
||||
* Swipe left on selected counter token to advance the counter to the next value.
|
||||
|
||||
## Creator
|
||||
|
||||
Andrew Gregory (andrew.gregory at gmail)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN"))
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
const tokenentryheight = 46;
|
||||
// Hash functions
|
||||
const crypto = require("crypto");
|
||||
const algos = {
|
||||
"SHA512":{sha:crypto.SHA512,retsz:64,blksz:128},
|
||||
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
|
||||
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
|
||||
};
|
||||
|
||||
var tokens = require("Storage").readJSON("authentiwatch.json", true) || [];
|
||||
|
||||
// QR Code Text
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret}
|
||||
//
|
||||
// ${algorithm} : one of SHA1 / SHA256 / SHA512
|
||||
// ${digits} : one of 6 / 8
|
||||
// ${period} : one of 30 / 60
|
||||
// ${url} : a domain name "example.com"
|
||||
// ${secret} : the seed code
|
||||
|
||||
function b32decode(seedstr) {
|
||||
// RFC4648
|
||||
var i, buf = 0, bitcount = 0, retstr = "";
|
||||
for (i in seedstr) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0);
|
||||
if (c != -1) {
|
||||
buf <<= 5;
|
||||
buf |= c;
|
||||
bitcount += 5;
|
||||
if (bitcount >= 8) {
|
||||
retstr += String.fromCharCode(buf >> (bitcount - 8));
|
||||
buf &= (0xFF >> (16 - bitcount));
|
||||
bitcount -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
retstr += String.fromCharCode(buf << (8 - bitcount));
|
||||
}
|
||||
var retbuf = new Uint8Array(retstr.length);
|
||||
for (i in retstr) {
|
||||
retbuf[i] = retstr.charCodeAt(i);
|
||||
}
|
||||
return retbuf;
|
||||
}
|
||||
function do_hmac(key, message, algo) {
|
||||
var a = algos[algo];
|
||||
// RFC2104
|
||||
if (key.length > a.blksz) {
|
||||
key = a.sha(key);
|
||||
}
|
||||
var istr = new Uint8Array(a.blksz + message.length);
|
||||
var ostr = new Uint8Array(a.blksz + a.retsz);
|
||||
for (var i = 0; i < a.blksz; ++i) {
|
||||
var c = (i < key.length) ? key[i] : 0;
|
||||
istr[i] = c ^ 0x36;
|
||||
ostr[i] = c ^ 0x5C;
|
||||
}
|
||||
istr.set(message, a.blksz);
|
||||
ostr.set(a.sha(istr), a.blksz);
|
||||
var ret = a.sha(ostr);
|
||||
// RFC4226 dynamic truncation
|
||||
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
||||
return v.getUint32(0) & 0x7FFFFFFF;
|
||||
}
|
||||
function hotp(token) {
|
||||
var tick;
|
||||
var d = new Date();
|
||||
if (token.period > 0) {
|
||||
// RFC6238 - timed
|
||||
var seconds = Math.floor(d.getTime() / 1000);
|
||||
tick = Math.floor(seconds / token.period);
|
||||
} else {
|
||||
// RFC4226 - counter
|
||||
tick = -token.period;
|
||||
}
|
||||
var msg = new Uint8Array(8);
|
||||
var v = new DataView(msg.buffer);
|
||||
v.setUint32(0, tick >> 16 >> 16);
|
||||
v.setUint32(4, tick & 0xFFFFFFFF);
|
||||
var ret = "";
|
||||
try {
|
||||
var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase());
|
||||
ret = "" + hash % Math.pow(10, token.digits);
|
||||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
} catch(err) {
|
||||
ret = "Not supported";
|
||||
}
|
||||
return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
|
||||
}
|
||||
|
||||
var state = {
|
||||
listy: 0,
|
||||
prevcur:0,
|
||||
curtoken:-1,
|
||||
nextTime:0,
|
||||
otp:"",
|
||||
rem:0,
|
||||
hide:0
|
||||
};
|
||||
|
||||
function drawToken(id, r) {
|
||||
var x1 = r.x;
|
||||
var y1 = r.y;
|
||||
var x2 = r.x + r.w - 1;
|
||||
var y2 = r.y + r.h - 1;
|
||||
var adj;
|
||||
g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ),
|
||||
Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2));
|
||||
if (id == state.curtoken) {
|
||||
// current token
|
||||
g.setColor(g.theme.fgH);
|
||||
g.setBgColor(g.theme.bgH);
|
||||
g.setFont("Vector", 16);
|
||||
// center just below top line
|
||||
g.setFontAlign(0, -1, 0);
|
||||
adj = y1;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setFont("Vector", 30);
|
||||
// center in box
|
||||
g.setFontAlign(0, 0, 0);
|
||||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period);
|
||||
g.fillRect(x1, y2 - 4, xr, y2 - 1);
|
||||
adj = 0;
|
||||
} else {
|
||||
// counter - draw triangle as swipe hint
|
||||
let yc = (y1 + y2) / 2;
|
||||
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
|
||||
adj = 5;
|
||||
}
|
||||
// digits just below label
|
||||
g.setFont("Vector", (state.otp.length > 8) ? 26 : 30);
|
||||
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
g.drawLine(x1, y1, x2, y1);
|
||||
g.drawLine(x1, y2, x2, y2);
|
||||
g.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
if (state.curtoken != -1) {
|
||||
var t = tokens[state.curtoken];
|
||||
if (d.getTime() > state.nextTime) {
|
||||
if (state.hide == 0) {
|
||||
// auto-hide the current token
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nextTime = 0;
|
||||
} else {
|
||||
// time to generate a new token
|
||||
var r = hotp(t);
|
||||
state.nextTime = r.next;
|
||||
state.otp = r.hotp;
|
||||
if (t.period <= 0) {
|
||||
state.hide = 1;
|
||||
}
|
||||
state.hide--;
|
||||
}
|
||||
}
|
||||
state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000));
|
||||
}
|
||||
if (tokens.length > 0) {
|
||||
var drewcur = false;
|
||||
var id = Math.floor(state.listy / tokenentryheight);
|
||||
var y = id * tokenentryheight + Bangle.appRect.y - state.listy;
|
||||
while (id < tokens.length && y < Bangle.appRect.y2) {
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight});
|
||||
if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
|
||||
drewcur = true;
|
||||
}
|
||||
id += 1;
|
||||
y += tokenentryheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - draw it again in 1sec
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
}
|
||||
state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime());
|
||||
if (tokens[state.curtoken].period <= 0) {
|
||||
state.hide = 0;
|
||||
}
|
||||
} else {
|
||||
// de-select the current token if it is scrolled out of view
|
||||
if (state.curtoken != -1) {
|
||||
state.prevcur = state.curtoken;
|
||||
state.curtoken = -1;
|
||||
}
|
||||
state.nexttime = 0;
|
||||
}
|
||||
} else {
|
||||
g.setFont("Vector", 30);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
}
|
||||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
|
||||
if (id == state.curtoken || tokens.length == 0) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
if (id != -1) {
|
||||
var y = id * tokenentryheight - state.listy;
|
||||
if (y < 0) {
|
||||
state.listy += y;
|
||||
y = 0;
|
||||
}
|
||||
y += tokenentryheight;
|
||||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
}
|
||||
state.nextTime = 0;
|
||||
state.curtoken = id;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
|
||||
newy = Math.max(0, newy);
|
||||
if (newy != state.listy) {
|
||||
state.listy = newy;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function onSwipe(e) {
|
||||
if (e == 1) {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
|
||||
tokens[state.curtoken].period--;
|
||||
require("Storage").writeJSON("authentiwatch.json", tokens);
|
||||
state.nextTime = 0;
|
||||
state.hide = 2;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function bangle1Btn(e) {
|
||||
if (tokens.length > 0) {
|
||||
if (state.curtoken == -1) {
|
||||
state.curtoken = state.prevcur;
|
||||
} else {
|
||||
switch (e) {
|
||||
case -1: state.curtoken--; break;
|
||||
case 1: state.curtoken++; break;
|
||||
}
|
||||
}
|
||||
state.curtoken = Math.max(state.curtoken, 0);
|
||||
state.curtoken = Math.min(state.curtoken, tokens.length - 1);
|
||||
var fakee = {};
|
||||
fakee.y = state.curtoken * tokenentryheight - state.listy + Bangle.appRect.y;
|
||||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on('touch', onTouch);
|
||||
Bangle.on('drag' , onDrag );
|
||||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.drawWidgets();
|
||||
|
After Width: | Height: | Size: 964 B |
|
|
@ -0,0 +1,373 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style type="text/css">
|
||||
body{font-family:sans-serif}
|
||||
body div{display:none}
|
||||
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#tokenqr{display:block}
|
||||
#tokens th,#tokens td{padding:5px}
|
||||
#tokens tr:nth-child(odd){background-color:#ccc}
|
||||
#tokens tr:nth-child(even){background-color:#eee}
|
||||
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
|
||||
#advbtn,#scan,#tokenqr table{text-align:center}
|
||||
#edittoken tbody#adv{display:none}
|
||||
#edittoken.showadv tbody#adv{display:table-row-group}
|
||||
#advbtn button:before,#advbtn button:after{content:"\25bc"}
|
||||
#edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"}
|
||||
button{height:3em}
|
||||
table button{width:100%}
|
||||
form.totp tr.hotp,form.hotp tr.totp{display:none}
|
||||
</style>
|
||||
|
||||
<!-- https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js -->
|
||||
<script src="qr_packed.js"></script>
|
||||
|
||||
<!-- https://davidshimjs.github.io/qrcodejs/ -->
|
||||
<script src="../../core/lib/qrcode.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/* Start of all TOTP URLs */
|
||||
const otpAuthUrl = 'otpauth://';
|
||||
|
||||
const tokentypes = ['TOTP (Timed)', 'HOTP (Counter)'];
|
||||
|
||||
/* Array of TOTP tokens */
|
||||
var tokens=[];
|
||||
|
||||
/* Remove any non-base-32 characters from the given string and collapses
|
||||
* whitespace to a single space. Optionally removes all whitespace from
|
||||
* the string.
|
||||
*/
|
||||
function base32clean(val, nows) {
|
||||
var ret = val.replaceAll(/\s+/g, ' ');
|
||||
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
||||
if (nows) {
|
||||
ret = ret.replaceAll(/\s+/g, '');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Save changes to a token to the global tokens[] array.
|
||||
* id is the index into the global tokens[].
|
||||
* forget is a flag indicating if the token should be forgotten.
|
||||
*/
|
||||
function saveEdit(id, forget) {
|
||||
if (forget) {
|
||||
if (confirm('Forget token?')) {
|
||||
tokens.splice(id, 1);
|
||||
updateTokens();
|
||||
}
|
||||
} else {
|
||||
let fe = document.forms['edittoken'].elements;
|
||||
let d = parseInt(fe['digits'].value);
|
||||
let p = parseInt(fe['period'].value);
|
||||
let c = parseInt(fe['count'].value);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: p = (c > 0) ? -c : 0; break;
|
||||
default : p = (p > 0) ? p : 30; break;
|
||||
}
|
||||
tokens[id] = {
|
||||
'algorithm':fe['algorithm'].value,
|
||||
'digits':((d > 0) ? d : 6),
|
||||
'period':p,
|
||||
'issuer':fe['issuer'].value,
|
||||
'account':fe['account'].value,
|
||||
'secret':base32clean(fe['secret'].value, false),
|
||||
'label':fe['label'].value
|
||||
};
|
||||
updateTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate and display a QR-code representing the current token.
|
||||
*/
|
||||
function showQrCode() {
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
var url = new String(otpAuthUrl);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]: url += 'hotp/'; break;
|
||||
default : url += 'totp/'; break;
|
||||
}
|
||||
if (fe['account'].value.length > 0) {
|
||||
url += encodeURIComponent(fe['account'].value);
|
||||
} else {
|
||||
url += encodeURIComponent(fe['label'].value);
|
||||
}
|
||||
url += '?';
|
||||
if (fe['issuer'].value.length > 0) {
|
||||
url += 'issuer=' + encodeURIComponent(fe['issuer'].value);
|
||||
url += '&';
|
||||
}
|
||||
url += 'secret=' + base32clean(fe['secret'].value, true);
|
||||
switch (fe['type'].value) {
|
||||
case tokentypes[1]:
|
||||
if (parseInt(fe['count'].value) != 0) {
|
||||
url += '&counter=' + fe['count'].value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (parseInt(fe['period'].value) != 30) {
|
||||
url += '&period=' + fe['period'].value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parseInt(fe['digits'].value) != 6) {
|
||||
url += '&digits=' + fe['digits'].value;
|
||||
}
|
||||
if (fe['algorithm'].value != 'SHA1') {
|
||||
url += '&algorithm=' + fe['algorithm'].value;
|
||||
}
|
||||
tokenqr.clear();
|
||||
tokenqr.makeCode(url);
|
||||
document.body.className = 'showqr';
|
||||
}
|
||||
|
||||
function onTypeChanged() {
|
||||
var f = document.forms['edittoken'];
|
||||
var fe = f.elements;
|
||||
if (fe['type'].value == tokentypes[0]) { f.classList.add('totp'); f.classList.remove('hotp'); }
|
||||
if (fe['type'].value == tokentypes[1]) { f.classList.add('hotp'); f.classList.remove('totp'); }
|
||||
}
|
||||
|
||||
/* Generate a form for editing the specified token.
|
||||
* id is the index into the global tokens[].
|
||||
*/
|
||||
function editToken(id) {
|
||||
var p;
|
||||
const selectMarkup = function(name, ary, cur, onchg) {
|
||||
var ret = '<select name="' + name + '"' + ((typeof onchg == 'string') ? ' onchange="' + onchg + '"' : '') + '>';
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
ret += '<option' + ((ary[i] == cur) ? ' selected=selected' : '') + '>' + ary[i] + '</option>';
|
||||
}
|
||||
return ret + '</select>';
|
||||
};
|
||||
scanning=false;
|
||||
var markup = '<form id="edittoken"><input type="hidden" name="tokenid" value="' + id + '"><table>';
|
||||
markup += '<tr><td>Name:</td><td><input name="label" type="text" value="' + tokens[id].label + '"></td></tr>';
|
||||
markup += '<tr><td>Secret:</td><td><input name="secret" type="text" value="' + tokens[id].secret + '"></td></tr>';
|
||||
markup += '<tbody id="adv">';
|
||||
markup += '<tr><td>Account:</td><td><input name="account" type="text" value="' + tokens[id].account + '"></td></tr>';
|
||||
markup += '<tr><td>Issuer:</td><td><input name="issuer" type="text" value="' + tokens[id].issuer + '"></td></tr>';
|
||||
p = parseInt(tokens[id].period);
|
||||
markup += '<tr><td>Type:</td><td>';
|
||||
markup += selectMarkup('type', tokentypes, (tokens[id].period > 0) ? tokentypes[0] : tokentypes[1], 'onTypeChanged()');
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr class="totp"><td>Period:</td><td><input name="period" type="text" value="' + ((p > 0) ? p : 30) + '"></td></tr>';
|
||||
markup += '<tr class="hotp"><td>Count:</td><td><input name="count" type="text" value="' + ((p >= 0) ? 0 : -p) + '"></td></tr>';
|
||||
markup += '<tr><td>Digits:</td><td>';
|
||||
markup += selectMarkup('digits', ['6','7','8','9','10'], tokens[id].digits);
|
||||
markup += '</td></tr>';
|
||||
markup += '<tr><td>Hash:</td><td>';
|
||||
markup += selectMarkup('algorithm', ['SHA1','SHA256','SHA512'], tokens[id].algorithm);
|
||||
markup += '</td></tr>';
|
||||
markup += '</tbody><tr><td id="advbtn" colspan="2">';
|
||||
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
|
||||
markup += '</td></tr></table></form>';
|
||||
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
|
||||
if (tokens[id].isnew) {
|
||||
markup += '<button type="button" onclick="startScan()">Scan QR Code</button>';
|
||||
} else {
|
||||
markup += '<button type="button" onclick="showQrCode()">Show QR Code</button>';
|
||||
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
|
||||
}
|
||||
document.getElementById('edit').innerHTML = markup;
|
||||
document.body.className = 'editing';
|
||||
onTypeChanged();
|
||||
}
|
||||
|
||||
/* Create a new blank token and open the editor for it.
|
||||
*/
|
||||
function addToken() {
|
||||
tokens[tokens.length] = {'algorithm':'SHA1','digits':6,'period':30,'issuer':'','account':'','secret':'','label':'','isnew':true};
|
||||
editToken(tokens.length - 1);
|
||||
}
|
||||
|
||||
/* Move the specified token up or down in the global tokens[].
|
||||
* id is the index in the global tokens[] of the token to move.
|
||||
* dir is the direction to move: -1=up, 1=down.
|
||||
*/
|
||||
function moveToken(id, dir) {
|
||||
tokens.splice(id + dir, 0, tokens.splice(id, 1)[0]);
|
||||
updateTokens();
|
||||
}
|
||||
|
||||
/* Update the display listing all the tokens.
|
||||
*/
|
||||
function updateTokens() {
|
||||
const tokenButton = function(fn, id, label, dir) {
|
||||
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
|
||||
};
|
||||
var markup = '<table><tr><th>Token</th><th colspan="2">Order</th></tr>';
|
||||
/* any tokens marked new are cancelled new additions and must be removed */
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if (tokens[i].isnew) {
|
||||
tokens.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
markup += '<tr><td>';
|
||||
markup += tokenButton('editToken', i, tokens[i].label);
|
||||
markup += '</td><td>';
|
||||
if (i < (tokens.length - 1)) {
|
||||
markup += tokenButton('moveToken', i, '▼', 1);
|
||||
}
|
||||
markup += '</td><td>';
|
||||
if (i > 0) {
|
||||
markup += tokenButton('moveToken', i, '▲', -1);
|
||||
}
|
||||
markup += '</td></tr>';
|
||||
}
|
||||
markup += '</table>';
|
||||
markup += '<button type="button" onclick="addToken()">Add Token</button>';
|
||||
markup += '<button type="button" onclick="saveTokens()">Save to watch</button>';
|
||||
document.getElementById('tokens').innerHTML = markup;
|
||||
document.body.className = 'select';
|
||||
}
|
||||
|
||||
/* Original QR-code reader: https://www.sitepoint.com/create-qr-code-reader-mobile-website/ */
|
||||
qrcode.callback = res => {
|
||||
if (res) {
|
||||
if (res.startsWith(otpAuthUrl)) {
|
||||
res = decodeURIComponent(res);
|
||||
var paramsidx = res.indexOf('?');
|
||||
var params = res.substr(paramsidx+1).split('&');
|
||||
var t = {
|
||||
'algorithm':'SHA1',
|
||||
'digits':'6',
|
||||
'counter':'0',
|
||||
'period':'30',
|
||||
'secret':'',
|
||||
'issuer':''
|
||||
};
|
||||
var otpok = true;
|
||||
for (let pi in params) {
|
||||
var param = params[pi].split('=');
|
||||
if (param[0] in t) {
|
||||
t[param[0]] = param[1];
|
||||
} else {
|
||||
otpok = false;
|
||||
}
|
||||
}
|
||||
t['account'] = res.substring(res.lastIndexOf('/', paramsidx)+1, paramsidx);
|
||||
if ((t['account'] == '') || (t['secret'] == '')) {
|
||||
otpok = false;
|
||||
}
|
||||
if (otpok) {
|
||||
scanning = false;
|
||||
editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value));
|
||||
t['label'] = (t['issuer'] == '') ? t['account'] : t['issuer'] + ' (' + t['account'] + ')';
|
||||
var fe = document.forms['edittoken'].elements;
|
||||
if (res.startsWith(otpAuthUrl + 'hotp/')) {
|
||||
t['period'] = '30';
|
||||
fe['type'].value = tokentypes[1];
|
||||
} else {
|
||||
t['counter'] = '0';
|
||||
fe['type'].value = tokentypes[0];
|
||||
}
|
||||
fe['algorithm'].value = t['algorithm'];
|
||||
fe['digits' ].value = t['digits' ];
|
||||
fe['count' ].value = t['counter' ];
|
||||
fe['period' ].value = t['period' ];
|
||||
fe['secret' ].value = t['secret' ];
|
||||
fe['issuer' ].value = t['issuer' ];
|
||||
fe['account' ].value = t['account' ];
|
||||
fe['label' ].value = t['label' ];
|
||||
onTypeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function startScan() {
|
||||
document.body.className = 'scanning';
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({video:{facingMode:'environment'}})
|
||||
.then(function(stream){
|
||||
scanning=true;
|
||||
video.setAttribute('playsinline',true);
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
scanTick();
|
||||
doScan();
|
||||
});
|
||||
}
|
||||
function scanTick() {
|
||||
canvasElement.height = video.videoHeight;
|
||||
canvasElement.width = video.videoWidth;
|
||||
canvas.drawImage(video,0,0,canvasElement.width,canvasElement.height);
|
||||
if (scanning) {
|
||||
requestAnimationFrame(scanTick);
|
||||
} else {
|
||||
video.srcObject.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
function doScan() {
|
||||
try {
|
||||
qrcode.decode();
|
||||
} catch (e) {
|
||||
setTimeout(doScan,300);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load settings JSON file from the watch.
|
||||
*/
|
||||
function loadTokens() {
|
||||
Util.showModal('Loading...');
|
||||
Puck.eval(`require('Storage').read(${JSON.stringify('authentiwatch.json')})`,data=>{
|
||||
Util.hideModal();
|
||||
try {
|
||||
tokens = JSON.parse(data);
|
||||
updateTokens();
|
||||
} catch {
|
||||
tokens = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function saveTokens() {
|
||||
Util.showModal('Saving...');
|
||||
Puck.write(`\x10require('Storage').write(${JSON.stringify('authentiwatch.json')},${JSON.stringify(tokens)})\n`,()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
function onInit() {
|
||||
loadTokens();
|
||||
updateTokens();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="select">
|
||||
<h1>Authentiwatch</h1>
|
||||
<div id="tokens">
|
||||
<p>No watch comms.</p>
|
||||
</div>
|
||||
<div id="scan">
|
||||
<table>
|
||||
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
|
||||
<tr><td><button type="button" onclick="editToken(parseInt(document.forms['edittoken'].elements['tokenid'].value))">Cancel</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="edit">
|
||||
</div>
|
||||
<div id="tokenqr">
|
||||
<table><tr><td id="qrcode"></td></tr><tr><td>
|
||||
<button type="button" onclick="document.body.className='editing'">Back</button>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const video=document.createElement('video');
|
||||
const canvasElement=document.getElementById('qr-canvas');
|
||||
const canvas=canvasElement.getContext('2d');
|
||||
let scanning=false;
|
||||
const tokenqr=new QRCode(document.getElementById('qrcode'), '');
|
||||
</script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/* Packed with Google Closure
|
||||
*
|
||||
* Ported to JavaScript by Lazar Laszlo 2011
|
||||
* lazarsoft@gmail.com, www.lazarsoft.info
|
||||
*
|
||||
* Copyright 2007 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks",
|
||||
function(){for(var d=0,c=0;c<this.ecBlocks.length;c++)d+=this.ecBlocks[c].length;return d});this.getECBlocks=function(){return this.ecBlocks}}function k(h,b,e,d,c,a){this.versionNumber=h;this.alignmentPatternCenters=b;this.ecBlocks=[e,d,c,a];h=0;b=e.ECCodewordsPerBlock;e=e.getECBlocks();for(d=0;d<e.length;d++)c=e[d],h+=c.Count*(c.DataCodewords+b);this.totalCodewords=h;this.__defineGetter__("VersionNumber",function(){return this.versionNumber});this.__defineGetter__("AlignmentPatternCenters",function(){return this.alignmentPatternCenters});
|
||||
this.__defineGetter__("TotalCodewords",function(){return this.totalCodewords});this.__defineGetter__("DimensionForVersion",function(){return 17+4*this.versionNumber});this.buildFunctionPattern=function(){var d=this.DimensionForVersion,c=new I(d);c.setRegion(0,0,9,9);c.setRegion(d-8,0,8,9);c.setRegion(0,d-8,9,8);for(var e=this.alignmentPatternCenters.length,b=0;b<e;b++)for(var a=this.alignmentPatternCenters[b]-2,h=0;h<e;h++)0==b&&(0==h||h==e-1)||b==e-1&&0==h||c.setRegion(this.alignmentPatternCenters[h]-
|
||||
2,a,5,5);c.setRegion(6,9,1,d-17);c.setRegion(9,6,d-17,1);6<this.versionNumber&&(c.setRegion(d-11,0,3,6),c.setRegion(0,d-11,6,3));return c};this.getECBlocksForLevel=function(c){return this.ecBlocks[c.ordinal()]}}function z(h,b,e,d,c,a,l,m,f){this.a11=h;this.a12=d;this.a13=l;this.a21=b;this.a22=c;this.a23=m;this.a31=e;this.a32=a;this.a33=f;this.transformPoints1=function(c){for(var d=c.length,e=this.a11,b=this.a12,h=this.a13,a=this.a21,l=this.a22,p=this.a23,m=this.a31,f=this.a32,g=this.a33,y=0;y<d;y+=
|
||||
2){var q=c[y],k=c[y+1],n=h*q+p*k+g;c[y]=(e*q+a*k+m)/n;c[y+1]=(b*q+l*k+f)/n}};this.transformPoints2=function(c,d){for(var e=c.length,b=0;b<e;b++){var h=c[b],a=d[b],l=this.a13*h+this.a23*a+this.a33;c[b]=(this.a11*h+this.a21*a+this.a31)/l;d[b]=(this.a12*h+this.a22*a+this.a32)/l}};this.buildAdjoint=function(){return new z(this.a22*this.a33-this.a23*this.a32,this.a23*this.a31-this.a21*this.a33,this.a21*this.a32-this.a22*this.a31,this.a13*this.a32-this.a12*this.a33,this.a11*this.a33-this.a13*this.a31,this.a12*
|
||||
this.a31-this.a11*this.a32,this.a12*this.a23-this.a13*this.a22,this.a13*this.a21-this.a11*this.a23,this.a11*this.a22-this.a12*this.a21)};this.times=function(c){return new z(this.a11*c.a11+this.a21*c.a12+this.a31*c.a13,this.a11*c.a21+this.a21*c.a22+this.a31*c.a23,this.a11*c.a31+this.a21*c.a32+this.a31*c.a33,this.a12*c.a11+this.a22*c.a12+this.a32*c.a13,this.a12*c.a21+this.a22*c.a22+this.a32*c.a23,this.a12*c.a31+this.a22*c.a32+this.a32*c.a33,this.a13*c.a11+this.a23*c.a12+this.a33*c.a13,this.a13*c.a21+
|
||||
this.a23*c.a22+this.a33*c.a23,this.a13*c.a31+this.a23*c.a32+this.a33*c.a33)}}function P(h,b){this.bits=h;this.points=b}function Q(h){this.image=h;this.resultPointCallback=null;this.sizeOfBlackWhiteBlackRun=function(b,e,d,c){var h=Math.abs(c-e)>Math.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=e<c?1:-1,x=b<d?1:-1,v=0,t=b,a=e;t!=d;t+=x){var J=h?a:t,n=h?t:a;1==v?this.image[J+n*g.width]&&v++:this.image[J+n*g.width]||v++;if(3==v)return c=t-b,e=a-e,Math.sqrt(c*
|
||||
c+e*e);q+=f;if(0<q){if(a==c)break;a+=k;q-=m}}b=d-b;e=c-e;return Math.sqrt(b*b+e*e)};this.sizeOfBlackWhiteBlackRunBothWays=function(b,e,d,c){var a=this.sizeOfBlackWhiteBlackRun(b,e,d,c),h=1;d=b-(d-b);0>d?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X),
|
||||
Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b,
|
||||
d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5,
|
||||
3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0<a.AlignmentPatternCenters.length)for(a=1-3/m,f=Math.floor(e.X+a*(d.X-e.X+b.X-e.X)),a=Math.floor(e.Y+a*(d.Y-e.Y+b.Y-e.Y));;){f=this.findAlignmentInRegion(c,
|
||||
f,a,4);break}c=this.createTransform(e,d,b,f,h);h=this.sampleGrid(this.image,c,h);return new P(h,null==f?[b,e,d]:[b,e,d,f])};this.detect=function(){var b=(new S).findFinderPattern(this.image);return this.processFinderPatternInfo(b)}}function r(h){this.errorCorrectionLevel=C.forBits(h>>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<<
|
||||
3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h&
|
||||
31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e<this.bits.length;e++)this.bits[e]=0;this.__defineGetter__("Width",function(){return this.width});this.__defineGetter__("Height",function(){return this.height});this.__defineGetter__("Dimension",function(){if(this.width!=this.height)throw"Can't call getDimension() on a non-square matrix";return this.width});this.get_Renamed=function(d,c){return 0!=(u(this.bits[c*this.rowSize+(d>>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+
|
||||
(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;c<d;c++)this.bits[c]=0};this.setRegion=function(d,c,e,b){if(0>c||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c<b;c++)for(var h=c*this.rowSize,a=d;a<e;a++)this.bits[h+(a>>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords=
|
||||
a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e=
|
||||
0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d<c;d++)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;throw"Error readFormatInformation";
|
||||
};this.readVersion=function(){if(null!=this.parsedVersion)return this.parsedVersion;var e=this.bitMatrix.Dimension,d=e-17>>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=
|
||||
this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0<k;k-=2){6==k&&k--;for(var x=0;x<b;x++)for(var v=a?b-1-x:x,t=0;2>t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k-
|
||||
t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1<e&&0==b[0]){for(var d=1;d<e&&0==b[d];)d++;if(d==e)this.coefficients=a.Zero.coefficients;else{this.coefficients=Array(e-d);for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=0;for(e=0;e<this.coefficients.length;e++)this.coefficients[e]=b[d+e]}}else this.coefficients=b;this.__defineGetter__("Zero",
|
||||
function(){return 0==this.coefficients[0]});this.__defineGetter__("Degree",function(){return this.coefficients.length-1});this.__defineGetter__("Coefficients",function(){return this.coefficients});this.getCoefficient=function(c){return this.coefficients[this.coefficients.length-1-c]};this.evaluateAt=function(c){if(0==c)return this.getCoefficient(0);var d=this.coefficients.length;if(1==c){for(var b=c=0;b<d;b++)c=n.addOrSubtract(c,this.coefficients[b]);return c}for(var e=this.coefficients[0],b=1;b<
|
||||
d;b++)e=n.addOrSubtract(this.field.multiply(c,e),this.coefficients[b]);return e};this.addOrSubtract=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(this.Zero)return c;if(c.Zero)return this;var d=this.coefficients;c=c.coefficients;if(d.length>c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;h<e;h++)b[h]=c[h];for(h=e;h<c.length;h++)b[h]=n.addOrSubtract(d[h-e],c[h]);return new w(a,b)};this.multiply1=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";
|
||||
if(this.Zero||c.Zero)return this.field.Zero;var d=this.coefficients,b=d.length;c=c.coefficients;for(var e=c.length,a=Array(b+e-1),h=0;h<b;h++)for(var f=d[h],g=0;g<e;g++)a[h+g]=n.addOrSubtract(a[h+g],this.field.multiply(f,c[g]));return new w(this.field,a)};this.multiply2=function(c){if(0==c)return this.field.Zero;if(1==c)return this;for(var d=this.coefficients.length,b=Array(d),e=0;e<d;e++)b[e]=this.field.multiply(this.coefficients[e],c);return new w(this.field,b)};this.multiplyByMonomial=function(c,
|
||||
d){if(0>c)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a<e.length;a++)e[a]=0;for(a=0;a<b;a++)e[a]=this.field.multiply(this.coefficients[a],d);return new w(this.field,e)};this.divide=function(c){if(this.field!=c.field)throw"GF256Polys do not have same GF256 field";if(c.Zero)throw"Divide by 0";for(var d=this.field.Zero,b=this,e=c.getCoefficient(c.Degree),e=this.field.inverse(e);b.Degree>=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree,
|
||||
h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero});
|
||||
this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e<b.length;e++)b[e]=0;b[0]=c;return new w(this,b)};this.exp=function(d){return this.expTable[d]};this.log=function(d){if(0==d)throw"System.ArgumentException";return this.logTable[d]};this.inverse=function(d){if(0==d)throw"System.ArithmeticException";return this.expTable[255-this.logTable[d]]};this.multiply=function(d,c){return 0==
|
||||
d||0==c?0:1==d?c:1==c?d:this.expTable[(this.logTable[d]+this.logTable[c])%255]}}function u(a,b){return 0<=a?a>>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++};
|
||||
this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped=
|
||||
!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<<D)/7);e=Math.floor(b/2);return Math.abs(b-(a[0]<<D))<e&&Math.abs(b-
|
||||
(a[1]<<D))<e&&Math.abs(3*b-(a[2]<<D))<3*e&&Math.abs(b-(a[3]<<D))<e&&Math.abs(b-(a[4]<<D))<e};this.centerFromEnd=function(a,b){return b-a[4]-a[3]-a[2]/2};this.crossCheckVertical=function(a,b,e,d){for(var c=this.image,h=g.height,l=this.CrossCheckStateCount,m=a;0<=m&&c[b+m*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[b+m*g.width];)l[2]++,m++;if(m==h)return NaN;
|
||||
for(;m<h&&!c[b+m*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[b+m*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<=
|
||||
e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m<h&&c[m+b*g.width];)l[2]++,m++;if(m==h)return NaN;for(;m<h&&!c[m+b*g.width]&&l[3]<e;)l[3]++,m++;if(m==h||l[3]>=e)return NaN;for(;m<h&&c[m+b*g.width]&&l[4]<e;)l[4]++,m++;return l[4]>=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e),
|
||||
Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;h<c;h++){var l=this.possibleCenters[h];if(l.aboutEquals(a,b,e)){l.incrementCount();d=!0;break}}d||(e=new U(e,b,a),this.possibleCenters.push(e),null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(e));return!0}return!1};this.selectBestPatterns=function(){var a=this.possibleCenters.length;if(3>a)throw"Couldn't find enough finder patterns (found "+a+")";if(3<a){for(var b=0,e=0,d=0;d<a;d++)var c=
|
||||
this.possibleCenters[d].EstimatedModuleSize,b=b+c,e=e+c*c;var p=b/a;this.possibleCenters.sort(function(c,d){var b=Math.abs(d.EstimatedModuleSize-p),e=Math.abs(c.EstimatedModuleSize-p);return b<e?-1:b==e?0:1});a=Math.max(.2*p,Math.sqrt(e/a-p*p));for(d=this.possibleCenters.length-1;0<=d;d--)Math.abs(this.possibleCenters[d].EstimatedModuleSize-p)>a&&this.possibleCenters.splice(d,1)}3<this.possibleCenters.length&&this.possibleCenters.sort(function(c,d){return c.count>d.count?-1:c.count<d.count?1:0});
|
||||
return[this.possibleCenters[0],this.possibleCenters[1],this.possibleCenters[2]]};this.findRowSkip=function(){var a=this.possibleCenters.length;if(1>=a)return 0;for(var b=null,e=0;e<a;e++){var d=this.possibleCenters[e];if(d.Count>=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c<d;c++)a=this.possibleCenters[c],a.Count>=K&&(b++,e+=a.EstimatedModuleSize);
|
||||
if(3>b)return!1;for(var b=e/d,p=0,c=0;c<d;c++)a=this.possibleCenters[c],p+=Math.abs(a.EstimatedModuleSize-b);return p<=.05*e};this.findFinderPattern=function(a){var b;this.image=a;var e=g.height,d=g.width,c=Math.floor(3*e/(4*W));c<L&&(c=L);for(var h=!1,l=Array(5),m=c-1;m<e&&!h;m+=c){l[0]=0;l[1]=0;l[2]=0;l[3]=0;for(var f=b=l[4]=0;f<d;f++)if(a[f+m*g.width])1==(b&1)&&b++,l[b]++;else if(0==(b&1))if(4==b)if(this.foundPatternCross(l)){if(b=this.handlePossibleCenter(l,m,f))c=2,this.hasSkipped?h=this.haveMultiplyConfirmedCenters():
|
||||
(b=this.findRowSkip(),b>l[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f<d&&!a[f+m*g.width]);f--}b=0;l[0]=0;l[1]=0;l[2]=0;l[3]=0;l[4]=0}else l[0]=l[2],l[1]=l[3],l[2]=l[4],l[3]=1,l[4]=0,b=3;else l[++b]++;else l[b]++;this.foundPatternCross(l)&&(b=this.handlePossibleCenter(l,m,d))&&(c=l[0],this.hasSkipped&&(h=this.haveMultiplyConfirmedCenters()))}a=this.selectBestPatterns();g.orderBestPatterns(a);return new V(a)}}function M(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",
|
||||
function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return Math.floor(this.x)});this.__defineGetter__("Y",function(){return Math.floor(this.y)});this.incrementCount=function(){this.count++};this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters=
|
||||
[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN;
|
||||
for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;l<h&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l++;if(l==h||f[1]>b)return NaN;for(;l<h&&!e[d+l*g.width]&&f[2]<=b;)f[2]++,l++;return f[2]>b||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length,
|
||||
e=0;e<a;e++)if(this.possibleCenters[e].aboutEquals(c,d,b))return new M(b,d,c);b=new M(b,d,c);this.possibleCenters.push(b);null!=this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(b)}return null};this.find=function(){for(var c,b=this.startX,h=this.height,f=b+d,l=e+(h>>1),p=[0,0,0],k=0;k<h;k++){var n=l+(0==(k&1)?k+1>>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A<f&&!a[A+g.width*n];)A++;for(c=0;A<f;){if(a[A+n*g.width])if(1==c)p[c]++;else if(2==c){if(this.foundPatternCross(p)&&
|
||||
(c=this.handlePossibleCenter(p,n,A),null!=c))return c;p[0]=p[2];p[1]=1;p[2]=0;c=1}else p[++c]++;else 1==c&&c++,p[c]++;A++}if(this.foundPatternCross(p)&&(c=this.handlePossibleCenter(p,n,f),null!=c))return c}if(0!=this.possibleCenters.length)return this.possibleCenters[0];throw"Couldn't find enough alignment patterns";}}function X(a,b,e){this.blockPointer=0;this.bitPointer=7;this.dataLength=0;this.blocks=a;this.numErrorCorrectionCode=e;9>=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<=
|
||||
b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b<this.bitPointer+1){var a=0;for(d=0;d<b;d++)a+=1<<d;a<<=this.bitPointer-b+1;d=(this.blocks[this.blockPointer]&a)>>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b<this.bitPointer+1+8){for(d=c=0;d<this.bitPointer+1;d++)c+=1<<d;d=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;d+=this.blocks[this.blockPointer]>>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer=
|
||||
8+this.bitPointer);return d}if(b<this.bitPointer+1+16){for(d=a=c=0;d<this.bitPointer+1;d++)c+=1<<d;c=(this.blocks[this.blockPointer]&c)<<b-(this.bitPointer+1);this.blockPointer++;var e=this.blocks[this.blockPointer]<<b-(this.bitPointer+1+8);this.blockPointer++;for(d=0;d<b-(this.bitPointer+1+8);d++)a+=1<<d;a<<=8-(b-(this.bitPointer+1+8));d=(this.blocks[this.blockPointer]&a)>>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0};
|
||||
this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1<b){var a=this.getNextBits(11);var e=a%45,c=c+d[Math.floor(a/45)],c=c+d[e];b-=2}else 1==b&&(a=this.getNextBits(6),c+=d[a],--b);while(0<
|
||||
b);return c};this.getFigureString=function(b){var c=0,d="";do 3<=b?(c=this.getNextBits(10),100>c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0<b);return d};this.get8bitByteArray=function(b){var c=[];do{var d=this.getNextBits(8);c.push(d);b--}while(0<b);return c};this.getKanjiString=function(b){var c="";do{var d=this.getNextBits(13);d=(d/192<<8)+d%192;c+=String.fromCharCode(40956>=d+33088?d+33088:d+49472);b--}while(0<
|
||||
b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0<b.length)break;else throw"Empty data block";if(1!=c&&2!=c&&4!=c&&8!=c&&7!=c)throw"Invalid mode: "+c+" in (block:"+this.blockPointer+" bit:"+this.bitPointer+")";if(7==c)this.parseECIValue();else{var a=this.getDataLength(c);
|
||||
if(1>a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 2:c=this.getRomanAndFigureString(a);a=Array(c.length);for(e=0;e<c.length;e++)a[e]=c.charCodeAt(e);b.push(a);break;case 4:c=this.get8bitByteArray(a);b.push(c);break;case 8:c=this.getKanjiString(a),b.push(c)}}}while(1);return b})}var F={checkAndNudgePoints:function(a,b){for(var e,d,c=g.width,h=g.height,f=!0,m=0;m<b.length&&f;m+=2){d=
|
||||
Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h<b;h++){for(var f=
|
||||
c.length,m=h+.5,k=0;k<f;k+=2)c[k]=(k>>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k<f;k+=2)a[Math.floor(c[k])+g.width*Math.floor(c[k+1])]&&d.set_Renamed(k>>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,
|
||||
84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18,
|
||||
new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22,
|
||||
new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))),
|
||||
new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5,
|
||||
87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)),
|
||||
new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26,
|
||||
new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30,
|
||||
new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30,
|
||||
new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10,
|
||||
48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46),
|
||||
new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14,
|
||||
122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138,
|
||||
166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40<a)throw"ArgumentException";return k.VERSIONS[a-1]};k.getProvisionalVersionForDimension=function(a){if(1!=a%4)throw"Error getProvisionalVersionForDimension";
|
||||
try{return k.getVersionForNumber(a-17>>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d<k.VERSION_DECODE_INFO.length;d++){var c=k.VERSION_DECODE_INFO[d];if(c==a)return this.getVersionForNumber(d+7);c=r.numBitsDiffering(a,c);c<b&&(e=d+7,b=c)}return 3>=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k,
|
||||
q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427,
|
||||
9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!=
|
||||
b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d<N.length;d++){var c=N[d],h=c[0];if(h==a)return new r(c[1]);h=this.numBitsDiffering(a,h);h<b&&(e=c[1],b=h)}return 3>=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException";
|
||||
var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;b<c.length;b++)e+=c[b].Count;e=Array(e);for(var h=0,f=0;f<c.length;f++){var g=c[f];for(b=0;b<g.Count;b++){var k=g.DataCodewords,q=d.ECCodewordsPerBlock+k;e[h++]=new G(k,Array(q))}}b=e[0].codewords.length;for(c=e.length-1;0<=c&&e[c].codewords.length!=b;)c--;c++;d=b-d.ECCodewordsPerBlock;for(b=g=0;b<d;b++)for(f=0;f<h;f++)e[f].codewords[b]=a[g++];for(f=c;f<h;f++)e[f].codewords[d]=a[g++];k=e[0].codewords.length;for(b=d;b<k;b++)for(f=0;f<
|
||||
h;f++)e[f].codewords[f<c?b:b+1]=a[g++];return e};var H={forReference:function(a){if(0>a||7<a)throw"System.ArgumentException";return H.DATA_MASKS[a]}};H.DATA_MASKS=[new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a&1)}},
|
||||
new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==b%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(a+b)%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){return 0==(u(a,
|
||||
1)+b/3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==(e&1)+e%3}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,e)};this.isMasked=function(a,b){var e=a*b;return 0==((e&1)+e%3&1)}},new function(){this.unmaskBitMatrix=function(a,b){for(var e=0;e<b;e++)for(var d=0;d<b;d++)this.isMasked(e,d)&&a.flip(d,
|
||||
e)};this.isMasked=function(a,b){return 0==((a+b&1)+a*b%3&1)}}];n.QR_CODE_FIELD=new n(285);n.DATA_MATRIX_FIELD=new n(301);n.addOrSubtract=function(a,b){return a^b};var E={};E.rsDecoder=new function(a){this.field=a;this.decode=function(a,e){for(var b=new w(this.field,a),c=Array(e),f=0;f<c.length;f++)c[f]=0;for(var h=!0,f=0;f<e;f++){var g=b.evaluateAt(this.field.exp(f));c[c.length-1-f]=g;0!=g&&(h=!1)}if(!h)for(f=new w(this.field,c),b=this.runEuclideanAlgorithm(this.field.buildMonomial(e,1),f,e),f=b[1],
|
||||
b=this.findErrorLocations(b[0]),c=this.findErrorMagnitudes(f,b,!1),f=0;f<b.length;f++){h=a.length-1-this.field.log(b[f]);if(0>h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree<e.Degree){var b=a;a=e;e=b}for(var b=this.field.One,f=this.field.Zero,h=this.field.Zero,g=this.field.One;e.Degree>=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree);
|
||||
for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1==
|
||||
b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&c<b;f++)0==a.evaluateAt(f)&&(d[c]=this.field.inverse(f),c++);if(c!=b)throw"Error locator degree does not match number of roots";return d};this.findErrorMagnitudes=function(a,e,d){for(var b=e.length,f=Array(b),h=0;h<b;h++){for(var g=this.field.inverse(e[h]),k=1,q=0;q<b;q++)h!=q&&(k=this.field.multiply(k,n.addOrSubtract(1,this.field.multiply(e[q],g))));f[h]=this.field.multiply(a.evaluateAt(g),this.field.inverse(k));d&&(f[h]=this.field.multiply(f[h],
|
||||
g))}return f}}(n.QR_CODE_FIELD);E.correctErrors=function(a,b){for(var e=a.length,d=Array(e),c=0;c<e;c++)d[c]=a[c]&255;e=a.length-b;try{E.rsDecoder.decode(d,e)}catch(p){throw p;}for(c=0;c<b;c++)a[c]=d[c]};E.decode=function(a){var b=new T(a);a=b.readVersion();for(var e=b.readFormatInformation().ErrorCorrectionLevel,b=b.readCodewords(),b=G.getDataBlocks(b,a,e),d=0,c=0;c<b.length;c++)d+=b[c].NumDataCodewords;for(var d=Array(d),f=0,h=0;h<b.length;h++){var c=b[h],g=c.Codewords,k=c.NumDataCodewords;E.correctErrors(g,
|
||||
k);for(c=0;c<k;c++)d[f++]=g[c]}return new X(d,a.VersionNumber,e.Bits)};var g={imagedata:null,width:0,height:0,qrCodeSymbol:null,debug:!1,maxImgSize:1048576,sizeOfDataLengthInfo:[[10,9,8,8],[12,11,16,10],[14,13,16,12]],callback:null,vidSuccess:function(a){g.localstream=a;g.webkit?g.video.src=window.webkitURL.createObjectURL(a):g.moz?(g.video.mozSrcObject=a,g.video.play()):g.video.src=a;g.gUM=!0;g.canvas_qr2=document.createElement("canvas");g.canvas_qr2.id="qr-canvas";g.qrcontext2=g.canvas_qr2.getContext("2d");
|
||||
g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;setTimeout(g.captureToCanvas,500)},vidError:function(a){g.gUM=!1},captureToCanvas:function(){if(g.gUM)try{if(0==g.video.videoWidth)setTimeout(g.captureToCanvas,500);else{g.canvas_qr2.width=g.video.videoWidth;g.canvas_qr2.height=g.video.videoHeight;g.qrcontext2.drawImage(g.video,0,0);try{g.decode()}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}}}catch(h){console.log(h),setTimeout(g.captureToCanvas,500)}},setWebcam:function(a){var b=
|
||||
navigator;g.video=document.getElementById(a);var e=!0;if(navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices)try{navigator.mediaDevices.enumerateDevices().then(function(a){a.forEach(function(a){console.log("deb1");"videoinput"===a.kind&&-1<a.label.toLowerCase().search("back")&&(e=[{sourceId:a.deviceId}]);console.log(a.kind+": "+a.label+" id = "+a.deviceId)})})}catch(d){console.log(d)}else console.log("no navigator.mediaDevices.enumerateDevices");b.getUserMedia?b.getUserMedia({video:e,
|
||||
audio:!1},g.vidSuccess,g.vidError):b.webkitGetUserMedia?(g.webkit=!0,b.webkitGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError)):b.mozGetUserMedia&&(g.moz=!0,b.mozGetUserMedia({video:e,audio:!1},g.vidSuccess,g.vidError))},decode:function(a){if(0==arguments.length){if(g.canvas_qr2){var b=g.canvas_qr2;var e=g.qrcontext2}else b=document.getElementById("qr-canvas"),e=b.getContext("2d");g.width=b.width;g.height=b.height;g.imagedata=e.getImageData(0,0,g.width,g.height);g.result=g.process(e);null!=
|
||||
g.callback&&g.callback(g.result);return g.result}var d=new Image;d.crossOrigin="Anonymous";d.onload=function(){var a=document.getElementById("out-canvas");null!=a&&(a=a.getContext("2d"),a.clearRect(0,0,320,240),a.drawImage(d,0,0,320,240));var a=document.createElement("canvas"),b=a.getContext("2d"),e=d.height,f=d.width;d.width*d.height>g.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata=
|
||||
b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)},
|
||||
decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;d<g.height;d++)for(var c=0;c<g.width;c++){var f=4*c+d*g.width*4;g.imagedata.data[f]=0;g.imagedata.data[f+1]=0;g.imagedata.data[f+2]=e[c+d*g.width]?255:0}a.putImageData(g.imagedata,0,0)}e=
|
||||
(new Q(e)).detect();if(g.debug){for(d=0;d<e.bits.Height;d++)for(c=0;c<e.bits.Width;c++)f=8*c+2*d*g.width*4,g.imagedata.data[f]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+1]=(e.bits.get_Renamed(c,d),0),g.imagedata.data[f+2]=e.bits.get_Renamed(c,d)?255:0;a.putImageData(g.imagedata,0,0)}f=E.decode(e.bits).DataByte;a="";for(d=0;d<f.length;d++)for(c=0;c<f[d].length;c++)a+=String.fromCharCode(f[d][c]);f=(new Date).getTime();console.log(f-b);return g.decode_utf8(a)},getPixel:function(a,b){if(g.width<
|
||||
a)throw"point error";if(g.height<b)throw"point error";var e=4*a+b*g.width*4;return(33*g.imagedata.data[e]+34*g.imagedata.data[e+1]+33*g.imagedata.data[e+2])/100},binarize:function(a){for(var b=Array(g.width*g.height),e=0;e<g.height;e++)for(var d=0;d<g.width;d++){var c=g.getPixel(d,e);b[d+e*g.width]=c<=a?!0:!1}return b},getMiddleBrightnessPerArea:function(a){for(var b=Math.floor(g.width/4),e=Math.floor(g.height/4),d=Array(4),c=0;4>c;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f=
|
||||
0;4>f;f++){d[f][c][0]=255;for(var h=0;h<e;h++)for(var m=0;m<b;m++){var k=a[b*f+m+(e*c+h)*g.width];k<d[f][c][0]&&(d[f][c][0]=k);k>d[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h<e;h++)for(var m=0;m<e;m++)for(var k=
|
||||
0;k<c;k++)for(var n=0;n<d;n++)f[d*m+n+(c*h+k)*g.width]=a[d*m+n+(c*h+k)*g.width]<b[m][h]?!0:!1;return f},grayscale:function(){for(var a=new ArrayBuffer(g.width*g.height),a=new Uint8Array(a),b=0;b<g.height;b++)for(var e=0;e<g.width;e++){var d=g.getPixel(e,b);a[e+b*g.width]=d}return a}},L=3,W=57,D=8,K=2;g.orderBestPatterns=function(a){function b(a,b){var c=a.X-b.X,d=a.Y-b.Y;return Math.sqrt(c*c+d*d)}var e=b(a[0],a[1]),d=b(a[1],a[2]),c=b(a[0],a[2]);d>=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1],
|
||||
e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}();
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
(() => {
|
||||
(() => {
|
||||
Bangle.setLCDTimeout(0);
|
||||
let intervalID;
|
||||
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial version of Balltastic released! Happy!
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
Bangle.setLCDBrightness(1);
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
||||
let points = 0;
|
||||
let level = 1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App
|
||||
Part of smartPPE project https://jorgepramos.github.io/Smart_PPE/index.html
|
||||
|
||||
# BangleBridge
|
||||
|
||||
Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App.
|
||||
|
||||
## Full Project
|
||||
|
||||
Part of smartPPE project [SmartPEE](https://jorgepramos.github.io/Smart_PPE/index.html).
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -0,0 +1 @@
|
|||
00堽<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><EFBFBD><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C>??<0C><><EFBFBD><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><EFBFBD><0C><><0C><><EFBFBD><0C><><0C><><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<0C><><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<0C><><EFBFBD><0C><>''''<27><><EFBFBD><0C><>''''<27><><EFBFBD><0C><>'''''<27><><EFBFBD><EFBFBD>'<27><>''''''<27><><EFBFBD><EFBFBD>'??'''''''<27><><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''''''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>''<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
(() => {
|
||||
/**
|
||||
* Widget measurements
|
||||
* Description:
|
||||
* name: connection.wid.js
|
||||
*icon: conectionIcon.icon
|
||||
*
|
||||
*/
|
||||
|
||||
//Font
|
||||
|
||||
g.setFont("Vector", 100);
|
||||
//variabangle.Sensorss
|
||||
let acclS, bttS, compssS, gpsS, hrmS, stepS; //Strings
|
||||
let accelN, compssN, gpsN, hrmN, stepN; //Num
|
||||
let prueba = 1;
|
||||
let data = [0, 0, 0, 0, 0, 0];
|
||||
//Constants for redabangle.Sensors code
|
||||
let storage = require('Storage');
|
||||
let deCom = require('heatshrink');
|
||||
|
||||
|
||||
|
||||
|
||||
//Sensors code
|
||||
/**
|
||||
*
|
||||
* @author Jorge
|
||||
*/
|
||||
function accel() {
|
||||
|
||||
Bangle.on('accel', function (acc) {
|
||||
// acc = {x,y,z,diff,mag}
|
||||
accelN = acc;
|
||||
});
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||
data[3] = accelN;
|
||||
}, 2 * 1000);
|
||||
|
||||
}
|
||||
|
||||
function btt() {
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
bttS = E.getBattery(); //return String
|
||||
data[2] = E.getBattery();
|
||||
}, 15 * 1000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function compss() {
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.on('mag', function (mag) {
|
||||
// mag = {x,y,z,dx,dy,dz,heading}
|
||||
compssN = mag;
|
||||
});
|
||||
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||
"B: " + compssN.dx + " ## " + compssN.dy + " ## " + compssN.dz + " ## " + "\n" +
|
||||
"C: " + compssN.heading; //return String
|
||||
data[4] = compssN;
|
||||
}, 2 * 1000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function gps() {
|
||||
|
||||
Bangle.setGPSPower(1);
|
||||
Bangle.on('GPS', function (gps) {
|
||||
// gps = {lat,lon,alt,speed,etc}
|
||||
gpsN = gps;
|
||||
|
||||
});
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||
"C: " + gpsN.satellites + " ## " + gpsN.fix; //return String
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var year = d.getFullYear();
|
||||
|
||||
var month = d.getMonth() + 1;
|
||||
var finalMonth = 0;
|
||||
if (month < 10) {
|
||||
finalMonth = "0" + month;
|
||||
} else {
|
||||
finalMonth = month;
|
||||
}
|
||||
var day = d.getDate();
|
||||
var finalDay = 0;
|
||||
if (day < 10) {
|
||||
finalDay = "0" + day;
|
||||
} else {
|
||||
finalDay = day;
|
||||
}
|
||||
var h = d.getHours(),
|
||||
m = d.getMinutes();
|
||||
var finalh = 0;
|
||||
if (h < 10) {
|
||||
finalh = "0" + h;
|
||||
} else {
|
||||
finalh = h;
|
||||
}
|
||||
var finalM = 0;
|
||||
if (m < 10) {
|
||||
finalM = "0" + m;
|
||||
} else {
|
||||
finalM = m;
|
||||
}
|
||||
|
||||
var s = d.getSeconds();
|
||||
var finalS = 0;
|
||||
if (s < 10) {
|
||||
finalS = "0" + s;
|
||||
} else {
|
||||
finalS = s;
|
||||
}
|
||||
var z = d.getMilliseconds();
|
||||
var zFinal = new String(z);
|
||||
zFinal = zFinal.replace('.', '');
|
||||
var completeTime = year + "-" + finalMonth + "-" + finalDay + "T" + finalh + ":" + finalM + ":" + finalS + "." + z + "Z";
|
||||
var time = h + ":" + ("0" + m).substr(-2);
|
||||
gpsN.time = completeTime;
|
||||
data[5] = gpsN;
|
||||
}, 2 * 1000);
|
||||
}
|
||||
|
||||
//2021-06-11T19:21:58.000Z
|
||||
|
||||
function hrm() {
|
||||
|
||||
let msr = [0, 0, 0, 0, 0];
|
||||
let lastInsert = -1;
|
||||
|
||||
function roundInsert(nueva) {
|
||||
let indexFinal = (lastInsert + 1) % (msr.length);
|
||||
//console.log("Index ==> "+ index);
|
||||
msr[indexFinal] = nueva;
|
||||
|
||||
item = nueva;
|
||||
lastInsert = indexFinal;
|
||||
|
||||
}
|
||||
|
||||
function normalize(nueva) {
|
||||
|
||||
let normalize = 0;
|
||||
roundInsert(nueva);
|
||||
|
||||
|
||||
msr.forEach(function (number) {
|
||||
normalize += number;
|
||||
});
|
||||
normalize = normalize / msr.length;
|
||||
|
||||
return normalize;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
if (!isNaN(hrmN)) {
|
||||
|
||||
|
||||
hrmN = normalize(hrmN);
|
||||
var roundedRate = parseFloat(hrmN).toFixed(2);
|
||||
hrmS = String.valueOf(roundedRate); //return String
|
||||
//console.log("array----->" + msr);
|
||||
data[0] = roundedRate;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}, 2 * 1000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function steps() {
|
||||
|
||||
Bangle.on('step', s => {
|
||||
|
||||
stepN = s;
|
||||
});
|
||||
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
stepS = String.valueOf(stepN); //return String
|
||||
data[1] = stepN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
||||
}
|
||||
|
||||
function initSensors() {
|
||||
|
||||
//need power control
|
||||
Bangle.setHRMPower(1);
|
||||
|
||||
Bangle.on('HRM', function (hrm) {
|
||||
hrmN = hrm.bpm;
|
||||
|
||||
|
||||
});
|
||||
console.log("Sensors are being Init....");
|
||||
accel();
|
||||
btt();
|
||||
compss();
|
||||
gps();
|
||||
hrm();
|
||||
steps();
|
||||
|
||||
}
|
||||
|
||||
var flip = 1;
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
/*
|
||||
prueba ++;
|
||||
Bangle.drawWidgets();
|
||||
g.setFont("Vector", 45);
|
||||
g.drawString(prueba,100,200);*/
|
||||
if (flip == 1) { //when off
|
||||
|
||||
flip = 0;
|
||||
//Bangle.buzz(1000);
|
||||
g.clear();
|
||||
} else { //when on
|
||||
|
||||
flip = 1;
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(data[0], 65, 180);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
function draw() {
|
||||
g.drawImage(storage.read("banglebridge.watch.img"),this.x + 1,this.y + 1);
|
||||
g.drawImage(storage.read("banglebridge.heart.img"), 145, 167);
|
||||
}
|
||||
|
||||
|
||||
// Finally add widget
|
||||
|
||||
|
||||
initSensors();
|
||||
// Bangle.drawWidgets();
|
||||
// Terminal.println("Running BangleBridge");
|
||||
data[0] = 80.5;
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(data[0], 65, 180);
|
||||
// Bangle.drawWidgets();
|
||||
setInterval(function () {
|
||||
//console.log("---------------------------------------------------------------");
|
||||
//console.log(data);
|
||||
//Bluetooth.println(data[0]);
|
||||
var measurement = {
|
||||
hrm: data[0],
|
||||
step: data[1],
|
||||
batt: data[2],
|
||||
acc: data[3],
|
||||
com: data[4],
|
||||
gps: data[5]
|
||||
};
|
||||
/* g.clear();
|
||||
g.drawString(compssS,100,200);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
Bluetooth.println(JSON.stringify(measurement) + "#");
|
||||
//draw();
|
||||
|
||||
}, 5 * 1000);
|
||||
|
||||
WIDGETS["banglebridge"]={
|
||||
area: "tl",
|
||||
width: 10,
|
||||
draw: draw,
|
||||
};
|
||||
})(); //End of Widget
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -3,3 +3,11 @@
|
|||
0.03: Fix distance >=10 km (fix #529)
|
||||
0.04: Use offscreen buffer for flickerless updates
|
||||
0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging
|
||||
0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above)
|
||||
0.07: Fixed GPS update, added guards against NaN values
|
||||
0.08: Fix issue with GPS coordinates being wrong after the first one
|
||||
0.09: Another GPS fix (log raw coordinates - not filtered ones)
|
||||
0.10: Removed kalman filtering to allow distance log to work
|
||||
Only log data every 5 seconds (not 1 sec)
|
||||
Don't create a file until the first log entry is ready
|
||||
Add labels for buttons
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# BangleRun
|
||||
|
||||
An app for running sessions. Displays info and logs your run for later viewing.
|
||||
|
||||
## Compilation
|
||||
|
||||
The app is written in Typescript, and needs to be transpiled in order to be
|
||||
run on the BangleJS. The easiest way to perform this step is by using the
|
||||
ubiquitous [NPM package manager](https://www.npmjs.com/get-npm).
|
||||
|
||||
After having installed NPM for your platform, checkout the `BangleApps` repo,
|
||||
open a terminal, and navigate into the `apps/banglerun` folder. Then issue:
|
||||
|
||||
```
|
||||
npm i
|
||||
```
|
||||
|
||||
to install the project's build tools, and:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
To build the app. The last command will generate the `app.js` file, containing
|
||||
the transpiled code for the BangleJS.
|
||||