Merge branch 'master' of https://github.com/espruino/BangleApps into app/sonic-clock
|
@ -9,3 +9,4 @@ appdates.csv
|
|||
_config.yml
|
||||
tests/Layout/bin/tmp.*
|
||||
tests/Layout/testresult.bmp
|
||||
apps.local.json
|
48
README.md
|
@ -12,7 +12,7 @@ and that it is not licensed in another way that would make this impossible.
|
|||
|
||||
## How does it work?
|
||||
|
||||
* A list of apps is in `apps.json`
|
||||
* A list of apps is in `apps.json` (this is auto-generated from all the `apps/yourapp/metadata.json` using Jekyll or `bin/create_apps_json.sh`)
|
||||
* Each element references an app in `apps/<id>` which is uploaded
|
||||
* When it starts, BangleAppLoader checks the JSON and compares
|
||||
it with the files it sees in the watch's storage.
|
||||
|
@ -53,10 +53,10 @@ easily distinguish between file types, we use the following:
|
|||
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/myappid`
|
||||
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
|
||||
* We'd recommend that you copy files from one of the Examples in `apps/_example_*` (see below), or...
|
||||
* `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:
|
||||
* Create/modify `apps/myappid/metadata.json` as follows:
|
||||
|
||||
```
|
||||
{ "id": "myappid",
|
||||
|
@ -116,8 +116,7 @@ and set it to `Load default application`.
|
|||
|
||||
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 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`.
|
||||
or `apps/_example_widget` to `apps/myappid`, and edit `apps/myappid/metadata.json` accordingly.
|
||||
|
||||
**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.
|
||||
|
@ -131,7 +130,7 @@ The app example is available in [`apps/_example_app`](apps/_example_app)
|
|||
|
||||
Apps are listed in the Bangle.js menu, accessible from a clock app via the middle button.
|
||||
|
||||
* `add_to_apps.json` - insert into `apps.json`, describes the app to bootloader and loader
|
||||
* `metadata.json` - describes the app to bootloader and loader
|
||||
* `app.png` - app icon - 48x48px
|
||||
* `app-icon.js` - JS version of the icon (made with http://www.espruino.com/Image+Converter) for use in Bangle.js's menu
|
||||
* `app.js` - app code
|
||||
|
@ -144,11 +143,11 @@ Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and
|
|||
|
||||
Follow this steps to create a readable icon as image string.
|
||||
|
||||
1. upload a png file
|
||||
1. upload a 48x48 png file - THE IMAGE SHOULD BE 48x48 OR LESS
|
||||
2. set _X_ Use Compression
|
||||
3. set _X_ Transparency (optional)
|
||||
4. set Diffusion: _flat_
|
||||
5. set Colours: _1 bit_, _4 bit_ or _8 bit Web Palette_
|
||||
5. set Colours: _1 bit_, any of the Optimised options, or _8 bit Web Palette_ are best
|
||||
6. set Output as: _Image String_
|
||||
|
||||
Replace this line with the image converter output:
|
||||
|
@ -157,6 +156,8 @@ Replace this line with the image converter output:
|
|||
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))
|
||||
```
|
||||
|
||||
**Do not add a trailing semicolon**
|
||||
|
||||
You can also use this converter for creating images you like to draw with `g.drawImage()` with your app.
|
||||
|
||||
Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load
|
||||
|
@ -167,17 +168,18 @@ has call to completely clear the screen. Widgets themselves will update as and w
|
|||
|
||||
The widget example is available in [`apps/_example_widget`](apps/_example_widget)
|
||||
|
||||
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader
|
||||
* `metadata.json` - describes the widget to bootloader and loader
|
||||
* `widget.js` - widget code
|
||||
|
||||
Widgets are just small bits of code that run whenever an app that supports them
|
||||
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
|
||||
widget bars at the top and bottom of the screen they can add themselves to
|
||||
the global `WIDGETS` array with:
|
||||
widget bar at the top of the screen they can add themselves to the global
|
||||
`WIDGETS` array with:
|
||||
|
||||
```
|
||||
WIDGETS["mywidget"]={
|
||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
||||
area:"tl", // tl (top left), tr (top right)
|
||||
sortorder:0, // (Optional) determines order of widgets in the same corner
|
||||
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||
draw:draw // called to draw the widget
|
||||
};
|
||||
|
@ -202,7 +204,7 @@ and which gives information about the app for the Launcher.
|
|||
// if it's 'clock' then it'll be loaded by default at boot time
|
||||
// if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
|
||||
"version":"1.23",
|
||||
// added by BangleApps loader on upload based on apps.json
|
||||
// added by BangleApps loader on upload based on metadata.json
|
||||
"files:"file1,file2,file3",
|
||||
// added by BangleApps loader on upload - lists all files
|
||||
// that belong to the app so it can be deleted
|
||||
|
@ -214,7 +216,7 @@ and which gives information about the app for the Launcher.
|
|||
}
|
||||
```
|
||||
|
||||
### `apps.json` format
|
||||
### `metadata.json` format
|
||||
|
||||
```
|
||||
{ "id": "appid", // 7 character app id
|
||||
|
@ -293,9 +295,9 @@ and which gives information about the app for the Launcher.
|
|||
* storage is used to identify the app files and how to handle them
|
||||
* data is used to clean up files when the app is uninstalled
|
||||
|
||||
### `apps.json`: `custom` element
|
||||
### `metadata.json`: `custom` element
|
||||
|
||||
Apps that can be customised need to define a `custom` element in `apps.json`,
|
||||
Apps that can be customised need to define a `custom` element in `metadata.json`,
|
||||
which names an HTML file in that app's folder.
|
||||
|
||||
When `custom` is defined, the 'upload' button is replaced by a customize
|
||||
|
@ -303,7 +305,7 @@ button, and when clicked it opens the HTML page specified in an iframe.
|
|||
|
||||
In that HTML file you're then responsible for handling a button
|
||||
press and calling `sendCustomizedApp` with your own customised
|
||||
version of what's in `apps.json`:
|
||||
version of what's in `metadata.json`:
|
||||
|
||||
```
|
||||
<html>
|
||||
|
@ -335,9 +337,9 @@ for a clean example.
|
|||
and will never be loaded. This is so the app loader can tell if it's a JavaScript
|
||||
file based on the extension, and if so it can minify and pretokenise it.
|
||||
|
||||
### `apps.json`: `interface` element
|
||||
### `metadata.json`: `interface` element
|
||||
|
||||
Apps that create data that can be read back can define a `interface` element in `apps.json`,
|
||||
Apps that create data that can be read back can define a `interface` element in `metadata.json`,
|
||||
which names an HTML file in that app's folder.
|
||||
|
||||
When `interface` is defined, a `Download from App` button is added to
|
||||
|
@ -401,7 +403,7 @@ Example `settings.js`
|
|||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`.
|
||||
In this example the app needs to add `myappid.settings.js` to `storage` in `metadata.json`.
|
||||
It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled.
|
||||
```json
|
||||
{ "id": "myappid",
|
||||
|
@ -461,16 +463,13 @@ 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) |
|
||||
| Widget bottom bar (optional) | (0,216,239,239) |
|
||||
| Apps | (0,24,239,239) (see below) |
|
||||
| Apps | (0,24,239,239) |
|
||||
| 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
|
||||
|
@ -515,7 +514,6 @@ The [`testing`](testing) folder contains snippets of code that might be useful f
|
|||
|
||||
* `testing/colors.js` - 16 bit colors as name value pairs
|
||||
* `testing/gpstrack.js` - code to store a GPS track in Bangle.js storage and output it back to the console
|
||||
* `testing/map` - code for splitting an image into map tiles and then displaying them
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "1button",
|
||||
"name": "One-Button-Tracker",
|
||||
"version": "0.01",
|
||||
"description": "A widget that turns BTN1 into a tracker, records time of button press/release.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "tool,quantifiedself,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"1button.wid.js","url":"widget.js"}
|
||||
],
|
||||
"data": [{"name":"one_button_presses.csv","storageFile":true}]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "93dub",
|
||||
"name": "93 Dub",
|
||||
"shortName":"93 Dub",
|
||||
"icon": "93dub.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.06",
|
||||
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"supports":["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"93dub.app.js","url":"app.js"},
|
||||
{"name":"93dub.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "BLEcontroller",
|
||||
"name": "BLE Customisable Controller with Joystick",
|
||||
"shortName": "BLE Controller",
|
||||
"version": "0.01",
|
||||
"description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.",
|
||||
"icon": "BLEcontroller.png",
|
||||
"tags": "tool,bluetooth",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": false,
|
||||
"storage": [
|
||||
{"name":"BLEcontroller.app.js","url":"app.js"},
|
||||
{"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "HRV",
|
||||
"name": "Heart Rate Variability monitor",
|
||||
"shortName": "HRV monitor",
|
||||
"version": "0.04",
|
||||
"description": "Heart Rate Variability monitor, see Readme for more info",
|
||||
"icon": "hrv.png",
|
||||
"tags": "",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"HRV.app.js","url":"app.js"},
|
||||
{"name":"HRV.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "UI4swatch",
|
||||
"name": "UI 4 swatch",
|
||||
"shortName": "UI 4 swatch",
|
||||
"version": "0.01",
|
||||
"description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
|
||||
"icon": "app.png",
|
||||
"tags": "Color,input,buttons,touch,UI",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}],
|
||||
"storage": [
|
||||
{"name":"UI4swatch.app.js","url":"app.js"},
|
||||
{"name":"UI4swatch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName":"Short Name",
|
|
@ -1,4 +1,3 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "7chname",
|
||||
"name": "My widget's human readable name",
|
||||
"shortName":"Short Name",
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "a_clock_timer",
|
||||
"name": "A Clock with Timer",
|
||||
"version": "0.01",
|
||||
"description": "A Clock with Timer, Map and Time Zones",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"a_clock_timer.app.js","url":"app.js"},
|
||||
{"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id":"a_speech_timer",
|
||||
"name":"Speech Timer",
|
||||
"icon": "app.png",
|
||||
"version":"1.01",
|
||||
"description": "A timer designed to help keeping your speeches and presentations to time.",
|
||||
"tags": "tool,timer",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"a_speech_timer.app.js","url":"app.js"},
|
||||
{"name":"a_speech_timer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "about",
|
||||
"name": "About",
|
||||
"version": "0.12",
|
||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"bangle1-about-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]},
|
||||
{"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"about.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"sortorder": -4
|
||||
}
|
|
@ -0,0 +1,890 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:100%; height:100%">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
<meta name="viewport" content="minimal-ui, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<style type="text/css">
|
||||
html {
|
||||
text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-o-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'self' 'unsafe-inline' 'unsafe-eval' file: https:;
|
||||
">
|
||||
<meta http-equiv="X-Content-Security-Policy" content="
|
||||
default-src 'self' 'unsafe-inline' 'unsafe-eval' file: https:;
|
||||
">
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<style>
|
||||
body { background:white; color:black }
|
||||
|
||||
table.centered td {
|
||||
text-align:center; vertical-align:top;
|
||||
}
|
||||
|
||||
label.Preview {
|
||||
display:inline-block; position:relative;
|
||||
}
|
||||
label.Preview > input[type="radio"] {
|
||||
display:none; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:0px; height:0px;
|
||||
}
|
||||
label.Preview > input[type="radio"] + img {
|
||||
display:inline-block; position:relative;
|
||||
width:88px; height:88px;
|
||||
margin:0px; padding:0px;
|
||||
border:solid 3px white;
|
||||
box-shadow:0px 0px 1px 1px lightgray;
|
||||
}
|
||||
label.Preview > input[type="radio"]:checked + img {
|
||||
border:solid 3px red;
|
||||
}
|
||||
|
||||
|
||||
.ColorPatch {
|
||||
display:inline-block; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:30px; height:30px; box-sizing:border-box;
|
||||
border:solid 3px white;
|
||||
margin:0px; padding:0px;
|
||||
box-shadow:inset 0px 0px 1px 1px black;
|
||||
vertical-align:top;
|
||||
}
|
||||
.ColorPatch:checked {
|
||||
border:solid 3px red;
|
||||
}
|
||||
|
||||
|
||||
label.ColorPatch {
|
||||
display:inline-block; position:relative;
|
||||
width:70px;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"] {
|
||||
display:none; position:relative;
|
||||
-webkit-appearance:none; -moz-appearance:none; appearance:none;
|
||||
width:0px; height:0px;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"] + span {
|
||||
display:inline-block; position:absolute;
|
||||
left:0px; top:0px; right:0px; bottom:0px;
|
||||
margin:0px; padding:0px;
|
||||
border:solid 3px white;
|
||||
box-shadow:inset 0px 0px 1px 1px black;
|
||||
font-size:16px; line-height:24px;
|
||||
text-align:center;
|
||||
}
|
||||
label.ColorPatch > input[type="radio"]:checked + span {
|
||||
border:solid 3px red;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
let ClockSize, ClockSizeURL
|
||||
let ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots
|
||||
let ClockHands, ClockHandsURL, SecondHand, FillColor
|
||||
let ComplicationTL, ComplicationTLURL
|
||||
let ComplicationT, ComplicationTURL
|
||||
let ComplicationTR, ComplicationTRURL
|
||||
let ComplicationL, ComplicationLURL
|
||||
let ComplicationR, ComplicationRURL
|
||||
let ComplicationBL, ComplicationBLURL
|
||||
let ComplicationB, ComplicationBURL
|
||||
let ComplicationBR, ComplicationBRURL
|
||||
let Foreground, Background
|
||||
|
||||
/**** backupConfiguration ****/
|
||||
|
||||
function backupConfiguration () {
|
||||
let Configuration = {
|
||||
ClockSize, ClockSizeURL,
|
||||
ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots,
|
||||
ClockHands, ClockHandsURL, SecondHand, FillColor,
|
||||
ComplicationTL, ComplicationTLURL,
|
||||
ComplicationT, ComplicationTURL,
|
||||
ComplicationTR, ComplicationTRURL,
|
||||
ComplicationL, ComplicationLURL,
|
||||
ComplicationR, ComplicationRURL,
|
||||
ComplicationBL, ComplicationBLURL,
|
||||
ComplicationB, ComplicationBURL,
|
||||
ComplicationBR, ComplicationBRURL,
|
||||
Foreground, Background
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem('ac_ac',JSON.stringify(Configuration))
|
||||
} catch (Signal) {
|
||||
console.error('could not backup clock configuration, reason',Signal)
|
||||
}
|
||||
}
|
||||
|
||||
/**** restoreConfiguration - warning: no input validations yet! ****/
|
||||
|
||||
function restoreConfiguration () {
|
||||
let Configuration = {}
|
||||
try {
|
||||
Configuration = JSON.parse(localStorage.getItem('ac_ac') || '')
|
||||
} catch (Signal) { /* nop */ }
|
||||
for (let Key in Configuration) {
|
||||
if (Configuration.hasOwnProperty(Key)) {
|
||||
if ((Key == null) || (typeof Configuration[Key] !== 'string')) {
|
||||
Configuration[Key] = ''
|
||||
} else {
|
||||
Configuration[Key] = Configuration[Key].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('input[name="clock-size"][value="' + Configuration.ClockSize + '"]').attr('checked','checked')
|
||||
$('#clock-size-custom-url').val(Configuration.ClockSizeURL)
|
||||
|
||||
$('input[name="clock-face"][value="' + Configuration.ClockFace + '"]').attr('checked','checked')
|
||||
$('input[name="clock-face-numerals"][value="' + Configuration.ClockFaceNumerals + '"]').attr('checked','checked')
|
||||
$('input[name="clock-face-dots"][value="' + Configuration.ClockFaceDots + '"]').attr('checked','checked')
|
||||
$('#clock-face-custom-url').val(Configuration.ClockFaceURL)
|
||||
|
||||
$('input[name="clock-hands"][value="' + Configuration.ClockHands + '"]').attr('checked','checked')
|
||||
$('input[name="fill-color"][value="' + Configuration.FillColor + '"]').attr('checked','checked')
|
||||
$('input[name="second-hand"][value="' + Configuration.SecondHand + '"]').attr('checked','checked')
|
||||
$('#clock-hands-custom-url').val(Configuration.ClockHandsURL)
|
||||
|
||||
$('#complication-tl').val(Configuration.ComplicationTL)
|
||||
$('#complication-tl-custom-url').val(Configuration.ComplicationTLURL)
|
||||
|
||||
$('#complication-t').val(Configuration.ComplicationT)
|
||||
$('#complication-t-custom-url').val(Configuration.ComplicationTURL)
|
||||
|
||||
$('#complication-tr').val(Configuration.ComplicationTR)
|
||||
$('#complication-tr-custom-url').val(Configuration.ComplicationTRURL)
|
||||
|
||||
$('#complication-l').val(Configuration.ComplicationL)
|
||||
$('#complication-l-custom-url').val(Configuration.ComplicationLURL)
|
||||
|
||||
$('#complication-r').val(Configuration.ComplicationR)
|
||||
$('#complication-r-custom-url').val(Configuration.ComplicationRURL)
|
||||
|
||||
$('#complication-bl').val(Configuration.ComplicationBL)
|
||||
$('#complication-bl-custom-url').val(Configuration.ComplicationBLURL)
|
||||
|
||||
$('#complication-b').val(Configuration.ComplicationB)
|
||||
$('#complication-b-custom-url').val(Configuration.ComplicationBURL)
|
||||
|
||||
$('#complication-br').val(Configuration.ComplicationBR)
|
||||
$('#complication-br-custom-url').val(Configuration.ComplicationBRURL)
|
||||
|
||||
$('input[name="foreground"][value="' + Configuration.Foreground + '"]').attr('checked','checked')
|
||||
$('input[name="background"][value="' + Configuration.Background + '"]').attr('checked','checked')
|
||||
}
|
||||
restoreConfiguration();
|
||||
|
||||
/**** retrieveInputs ****/
|
||||
|
||||
function retrieveInputs () {
|
||||
ClockSize = $('input[name="clock-size"]:checked').val()
|
||||
ClockSizeURL = ($('#clock-size-custom-url').val() || '').trim()
|
||||
|
||||
ClockFace = $('input[name="clock-face"]:checked').val()
|
||||
ClockFaceNumerals = $('input[name="clock-face-numerals"]:checked').val()
|
||||
ClockFaceDots = $('input[name="clock-face-dots"]:checked').val()
|
||||
ClockFaceURL = ($('#clock-face-custom-url').val() || '').trim()
|
||||
|
||||
ClockHands = $('input[name="clock-hands"]:checked').val()
|
||||
FillColor = $('input[name="fill-color"]:checked').val()
|
||||
SecondHand = $('input[name="second-hand"]:checked').val()
|
||||
ClockHandsURL = ($('#clock-hands-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationTL = $('#complication-tl').val()
|
||||
ComplicationTLURL = ($('#complication-tl-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationT = $('#complication-t').val()
|
||||
ComplicationTURL = ($('#complication-t-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationTR = $('#complication-tr').val()
|
||||
ComplicationTRURL = ($('#complication-tr-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationL = $('#complication-l').val()
|
||||
ComplicationLURL = ($('#complication-l-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationR = $('#complication-r').val()
|
||||
ComplicationRURL = ($('#complication-r-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationBL = $('#complication-bl').val()
|
||||
ComplicationBLURL = ($('#complication-bl-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationB = $('#complication-b').val()
|
||||
ComplicationBURL = ($('#complication-b-custom-url').val() || '').trim()
|
||||
|
||||
ComplicationBR = $('#complication-br').val()
|
||||
ComplicationBRURL = ($('#complication-br-custom-url').val() || '').trim()
|
||||
|
||||
Foreground = $('input[name="foreground"]:checked').val()
|
||||
Background = $('input[name="background"]:checked').val()
|
||||
}
|
||||
retrieveInputs()
|
||||
|
||||
/**** validateInputs ****/
|
||||
|
||||
function validateInputs () {
|
||||
function withError (Message) {
|
||||
showMessage(Message)
|
||||
$('#UploadButton').attr('disabled',true)
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case (ClockSize === 'custom') && (ClockSizeURL === ''):
|
||||
return withError('please enter the URL of your custom "Clock Size Calculator"')
|
||||
|
||||
case (ClockFace === 'custom') && (ClockFaceURL === ''):
|
||||
return withError('please enter the URL of your custom clock face')
|
||||
|
||||
case (ClockHands === 'custom') && (ClockHandsURL === ''):
|
||||
return withError('please enter the URL of your custom clock hands')
|
||||
|
||||
case (ComplicationTL === 'custom') && (ComplicationTLURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the top-left corner')
|
||||
case (ComplicationT === 'custom') && (ComplicationTURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the top edge')
|
||||
case (ComplicationTR === 'custom') && (ComplicationTRURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the top-right corner')
|
||||
case (ComplicationL === 'custom') && (ComplicationLURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the left edge')
|
||||
case (ComplicationR === 'custom') && (ComplicationRURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the right edge')
|
||||
case (ComplicationBL === 'custom') && (ComplicationBLURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the bottom-left corner')
|
||||
case (ComplicationB === 'custom') && (ComplicationBURL === ''):
|
||||
return withError('please enter the URL of your custom complication at the bottom edge')
|
||||
case (ComplicationBR === 'custom') && (ComplicationBRURL === ''):
|
||||
return withError('please enter the URL of your custom complication in the bottom-right corner')
|
||||
}
|
||||
|
||||
hideMessage()
|
||||
$('#UploadButton').removeAttr('disabled')
|
||||
}
|
||||
|
||||
/**** hide/showMesssage ****/
|
||||
|
||||
function hideMessage () { $('#MessageView').hide() }
|
||||
|
||||
function showMessage (Message) {
|
||||
$('#MessageView').text(Message).show()
|
||||
}
|
||||
|
||||
/**** createAndUploadApp ****/
|
||||
|
||||
function createAndUploadApp () {
|
||||
function WidgetsOnBackground () {
|
||||
return (
|
||||
ClockSize === 'smart'
|
||||
? "require('https://raw.githubusercontent.com/rozek/banglejs-2-widgets-on-background/main/drawWidgets.js');"
|
||||
: ''
|
||||
)
|
||||
}
|
||||
|
||||
function chosenClockSize () {
|
||||
switch (ClockSize) {
|
||||
case 'simple': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clock-size/main/ClockSize.js')"
|
||||
case 'smart': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-smart-clock-size/main/ClockSize.js')"
|
||||
case 'custom': return "require('" + ClockSizeURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenClockFace () {
|
||||
switch (ClockFace) {
|
||||
case 'none': return "undefined"
|
||||
case 'four-numbered': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-four-numbered-clock-face/main/ClockFace.js')"
|
||||
case 'twelve-numbered': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-twelve-numbered-clock-face/main/ClockFace.js')"
|
||||
case 'rainbow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rainbow-clock-face/main/ClockFace.js')"
|
||||
case 'custom': return "require('" + ClockFaceURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenClockHands () {
|
||||
switch (ClockHands) {
|
||||
case 'simple': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-simpled-clock-hands/main/ClockHands.js')"
|
||||
case 'rounded': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-rounded-clock-hands/main/ClockHands.js')"
|
||||
case 'hollow': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-hollow-clock-hands/main/ClockHands.js')"
|
||||
case 'custom': return "require('" + ClockHandsURL + "')"
|
||||
}
|
||||
}
|
||||
|
||||
function chosenComplication (Complication, customURL) {
|
||||
switch (Complication) {
|
||||
case 'none': return "undefined"
|
||||
case 'date': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-date-complication/main/Complication.js')"
|
||||
case 'weekday': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-weekday-complication/main/Complication.js')"
|
||||
case 'calendar-week': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-calendar-week-complication/main/Complication.js')"
|
||||
case 'moon-phase': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-moon-phase-complication/main/Complication.js')"
|
||||
case 'custom': return "require('" + customURL + "')"
|
||||
}
|
||||
}
|
||||
function chosenComplicationAt (Position) {
|
||||
switch (Position) {
|
||||
case 'tl': return chosenComplication(ComplicationTL, ComplicationTLURL)
|
||||
case 't': return chosenComplication(ComplicationT, ComplicationTURL)
|
||||
case 'tr': return chosenComplication(ComplicationTR, ComplicationTRURL)
|
||||
case 'l': return chosenComplication(ComplicationL, ComplicationLURL)
|
||||
case 'r': return chosenComplication(ComplicationR, ComplicationRURL)
|
||||
case 'bl': return chosenComplication(ComplicationBL, ComplicationBLURL)
|
||||
case 'b': return chosenComplication(ComplicationB, ComplicationBURL)
|
||||
case 'br': return chosenComplication(ComplicationBR, ComplicationBRURL)
|
||||
}
|
||||
}
|
||||
|
||||
function chosenColor (ColorChoice) {
|
||||
return (ColorChoice === 'none' ? 'undefined' : "'" + ColorChoice + "'")
|
||||
}
|
||||
|
||||
function chosenForeground () { return chosenColor(Foreground) }
|
||||
function chosenBackground () { return chosenColor(Background) }
|
||||
function chosenSecondHand () { return chosenColor(SecondHand) }
|
||||
function chosenFillColor () { return chosenColor(FillColor) }
|
||||
|
||||
function chosenNumerals () { return (ClockFaceNumerals === 'roman' ? 'true' : 'false') }
|
||||
function chosenDots () { return (ClockFaceDots === 'with-dots' ? 'true' : 'false') }
|
||||
|
||||
let AppSource = `
|
||||
${WidgetsOnBackground()}
|
||||
|
||||
let Clockwork = require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clockwork/main/Clockwork.js');
|
||||
|
||||
Clockwork.windUp({
|
||||
size: ${chosenClockSize()},
|
||||
background:null,
|
||||
face: ${chosenClockFace()},
|
||||
hands: ${chosenClockHands()},
|
||||
complications:{
|
||||
tl:${chosenComplicationAt('tl')},
|
||||
t: ${chosenComplicationAt('t')},
|
||||
tr:${chosenComplicationAt('tr')},
|
||||
l: ${chosenComplicationAt('l')},
|
||||
r: ${chosenComplicationAt('r')},
|
||||
bl:${chosenComplicationAt('bl')},
|
||||
b: ${chosenComplicationAt('b')},
|
||||
br:${chosenComplicationAt('br')},
|
||||
}
|
||||
},{
|
||||
Foreground: ${chosenForeground()},
|
||||
Background: ${chosenBackground()},
|
||||
Seconds: ${chosenSecondHand()},
|
||||
withDots: ${chosenDots()},
|
||||
romanNumerals:${chosenNumerals()},
|
||||
FillColor: ${chosenFillColor()}
|
||||
});
|
||||
`
|
||||
console.log('the configured AC-AC app looks as follows:')
|
||||
console.log(AppSource)
|
||||
|
||||
backupConfiguration()
|
||||
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:'ac_ac.app.js', url:'app.js', content:AppSource},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
/**** register event handlers ****/
|
||||
|
||||
function retrieveAndValidateInputs () {
|
||||
retrieveInputs ()
|
||||
validateInputs ()
|
||||
}
|
||||
|
||||
$('input[type="radio"]').on('change',retrieveAndValidateInputs)
|
||||
$('input[type="url"]'). on('input', retrieveAndValidateInputs)
|
||||
$('select'). on('change',retrieveAndValidateInputs)
|
||||
$('#UploadButton').on('click',createAndUploadApp)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>
|
||||
Please customize your analog clock for the Bangle.js 2 according to your needs.
|
||||
When finished, click on "Upload" at the bottom of this form.
|
||||
</p><p>
|
||||
(Pressing "Upload" will also backup your current configuration so that you
|
||||
won't have to enter the same settings over and over again when you come back
|
||||
to this page later)
|
||||
</p>
|
||||
|
||||
<h3>Clock Size Calculation</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock size calculator (if you installed some widgets
|
||||
on your Bangle.js 2, the smart one may produce larger clock faces than the
|
||||
simple one):
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="simple">
|
||||
<img src="simpleClockSize.png"/>
|
||||
</label><br>
|
||||
simple
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="smart" checked>
|
||||
<img src="smartClockSize.png"/>
|
||||
</label><br>
|
||||
smart
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-size" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
If you prefer a "custom" clock size calculator, please enter the URL
|
||||
of its JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-size-custom-url" size="50">
|
||||
</p>
|
||||
|
||||
<h3>Clock Face</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock face:
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="none" checked>
|
||||
<img src="none.png"/>
|
||||
</label><br>
|
||||
(none)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="four-numbered">
|
||||
<img src="fournumberedClockFace.png"/>
|
||||
</label><br>
|
||||
four-numbered
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="twelve-numbered">
|
||||
<img src="twelvenumberedClockFace.png"/>
|
||||
</label><br>
|
||||
twelve-numbered
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="rainbow">
|
||||
<img src="RainbowClockFace.png"/>
|
||||
</label><br>
|
||||
"rainbow"<br>colored
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
If you prefer a "custom" clock face, please enter the URL
|
||||
of its JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-face-custom-url" size="50">
|
||||
</p><p>
|
||||
Clock faces are drawn in the configured foreground and background colors
|
||||
(you may select them at the end of this form)
|
||||
</p><p>
|
||||
"Four-numbered" clock faces may draw indian-arabic or roman numerals. Which do you prefer?
|
||||
</p><p>
|
||||
<input type="radio" name="clock-face-numerals" value="indian" checked> indian-arabic (3, 6, 9, 12)<br>
|
||||
<input type="radio" name="clock-face-numerals" value="roman"> roman (III, VI, IX, XII)
|
||||
</p><p>
|
||||
The "twelve-numbered" and "rainbow"-colored faces may be drawn with or without
|
||||
dots marking the position of every minute. Which variant do you prefer?
|
||||
</p><p>
|
||||
<input type="radio" name="clock-face-dots" value="without-dots" checked> without dots <br>
|
||||
<input type="radio" name="clock-face-dots" value="with-dots"> with dots
|
||||
</p>
|
||||
|
||||
<h3>Clock Hands</h3>
|
||||
|
||||
<p>
|
||||
Click on the desired clock hands:
|
||||
</p><p>
|
||||
<table class="centered"><tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="simple">
|
||||
<img src="simpleClockHands.png"/>
|
||||
</label><br>
|
||||
simple
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="rounded" checked>
|
||||
<img src="roundedClockHands.png"/>
|
||||
</label><br>
|
||||
rounded
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="hollow">
|
||||
<img src="hollowClockHands.png"/>
|
||||
</label><br>
|
||||
hollow
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-hands" value="custom">
|
||||
<img src="custom.png"/>
|
||||
</label><br>
|
||||
(custom)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p><p>
|
||||
If you prefer "custom" clock hands, please enter the URL
|
||||
of their JavaScript module below:
|
||||
</p><p>
|
||||
custom URL: <input type="url" id="clock-hands-custom-url" size="50">
|
||||
</p><p>
|
||||
Clock hands are drawn in the configured foreground and background colors
|
||||
(you may select them at the end of this form)
|
||||
</p><p>
|
||||
Hollow clock hands may optionally be filled with a given color. If you have
|
||||
chosen hollow hands, please specify the desired fill mode and color below:
|
||||
</p><p>
|
||||
<b>Hollow Hand Fill Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="fill-color" value="none" checked/>
|
||||
<span>none</span>
|
||||
</label>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="fill-color" value="Theme"/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="fill-color" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="fill-color" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="fill-color" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="fill-color" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="fill-color" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="fill-color" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="fill-color" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="fill-color" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
Additionally, all clock hands may be drawn with or without second hands.
|
||||
If you want them to be drawn, please click on their desired color below
|
||||
(or choose "themed" to use your Bangle's configured theme) - if not, just
|
||||
select "none":
|
||||
</p><p>
|
||||
<b>Second Hand Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="second-hand" value="none" checked/>
|
||||
<span>none</span>
|
||||
</label>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="second-hand" value="Theme"/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="second-hand" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="second-hand" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="second-hand" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="second-hand" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="second-hand" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="second-hand" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="second-hand" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="second-hand" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p>
|
||||
|
||||
<h3>Complications</h3>
|
||||
|
||||
<p>
|
||||
Complications are small displays for additional information. If you want
|
||||
one or multiple complications to be added to your clock, you'll have to
|
||||
specify which one to be loaded and where it should be placed.
|
||||
</p><p>
|
||||
Up to 6 possible positions exist (top-left, top-right, left, right,
|
||||
bottom-left and bottom-right). Alternatively, the positions "top-left" and
|
||||
"top-right" may be traded for a slightly larger complication at position
|
||||
"top" or "bottom-left" and "bottom-right" for one at the "bottom":
|
||||
</p>
|
||||
<img src="smallPlaceholders.png" width="88" height="88"/>
|
||||
<img src="largePlaceholders.png" width="88" height="88"/>
|
||||
<p>
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td colspan="3"><b>top-left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-tl">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-tl-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>top:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-t">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-t-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>top-right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-tr">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-tr-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-l">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-l-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-r">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-r-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom-left:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-bl">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-bl-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-b">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-b-custom-url" size="50"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>bottom-right:</b></td>
|
||||
</tr><tr>
|
||||
<td> </td>
|
||||
<td> Complication:</td>
|
||||
<td>
|
||||
<select id="complication-br">
|
||||
<option value="none" selected>(none)</option>
|
||||
<option value="date"> date</option>
|
||||
<option value="weekday"> weekday</option>
|
||||
<option value="calendar-week">calendar week</option>
|
||||
<option value="moon-phase"> moon phase</option>
|
||||
<option value="custom"> (custom)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>custom URL:</td>
|
||||
<td><input type="url" id="complication-br-custom-url" size="50"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p>
|
||||
|
||||
<h3>Settings</h3>
|
||||
|
||||
<p>
|
||||
Color faces, hands and complications are often drawn using configurable
|
||||
foreground and background colors.
|
||||
</p><p>
|
||||
Here you may specify these colors. Click on a color to select it - or on
|
||||
"themed" if you want the clock to use the currently configured theme on
|
||||
your Bangle.js 2:
|
||||
</p><p>
|
||||
<b>Background Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="background" value="Theme" checked/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="background" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="background" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="background" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="background" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="background" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="background" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="background" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="background" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
<b>Foreground Color:</b>
|
||||
</p><p>
|
||||
<label class="ColorPatch">
|
||||
<input type="radio" name="foreground" value="Theme" checked/>
|
||||
<span>themed</span>
|
||||
</label>
|
||||
<input type="radio" name="foreground" value="#000000" class="ColorPatch" style="background:#000000"/>
|
||||
<input type="radio" name="foreground" value="#FF0000" class="ColorPatch" style="background:#FF0000"/>
|
||||
<input type="radio" name="foreground" value="#00FF00" class="ColorPatch" style="background:#00FF00"/>
|
||||
<input type="radio" name="foreground" value="#0000FF" class="ColorPatch" style="background:#0000FF"/>
|
||||
<input type="radio" name="foreground" value="#FFFF00" class="ColorPatch" style="background:#FFFF00"/>
|
||||
<input type="radio" name="foreground" value="#FF00FF" class="ColorPatch" style="background:#FF00FF"/>
|
||||
<input type="radio" name="foreground" value="#00FFFF" class="ColorPatch" style="background:#00FFFF"/>
|
||||
<input type="radio" name="foreground" value="#FFFFFF" class="ColorPatch" style="background:#FFFFFF"/>
|
||||
</p><p>
|
||||
When you are satisfied with your configuration, just click on "Upload" in
|
||||
order to generate the specified clock and upload it to your Bangle.js 2:
|
||||
</p>
|
||||
<p id="MessageView" style="display:none; color:red"></p>
|
||||
|
||||
<button id="UploadButton">Upload</button>
|
||||
</p><p>
|
||||
This application is based on the author's
|
||||
<a href="https://github.com/rozek/banglejs-2-analog-clock-construction-kit">Analog Clock Construction Kit (ACCK)</a>.
|
||||
If you need a different "clockwork", clock size calculation or clock face,
|
||||
or specific clock hands or complications, just follow the link to learn how to
|
||||
implement your own clock parts.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# AC-AC - A Configurable Analog Clock #
|
||||
|
||||
This app implements an analog clock with various faces, hands and complications
|
||||
to choose from before uploading to a Bangle.js 2.
|
||||
|
||||
It is based on the [Analog Clock Construction Kit (ACCK)](https://github.com/rozek/banglejs-2-analog-clock-construction-kit)
|
||||
and makes most of the currently implemented parts available with a few mouse
|
||||
clicks - just click on "Upload" and you will be directed to a web form where
|
||||
you compose your very own, personal analog clock.
|
||||
|
||||
You currently have the choice between
|
||||
|
||||
* 2 different clock sizes,
|
||||
* 4 different clock faces,
|
||||
* 3 different clock hands and
|
||||
* 4 different complications
|
||||
|
||||
Alternatively, you may specify the GitHub URL of ACCK compatible modules for
|
||||
external clock sizes, faces, hands or complications.
|
||||
|
||||
Additionally, you may use the currently configured global theme or configure
|
||||
your own colors for clock fore- and background and second hands.
|
||||
|
||||
Consequently, even without external modules you already have the choice between
|
||||
102144 combinations!
|
||||
|
||||
<!--
|
||||
1 + (8 Fg colors * 7 Bg colors) * 2 sizes * 4(7) faces * 3(4) hands *
|
||||
8 positions * 4 complications (w/o placeholder)
|
||||
-->
|
||||
|
||||
## License ##
|
||||
|
||||
[MIT License](LICENSE)
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgn/ABH+AQPvBpIAI/n8/3f5/PCp/v9oHF7w1CABffGxAYMH4f9z/514YDCxW/O4gFBxwHD/ZEL7/9GgX8GwQLCBQQXH/uP/Hf/2N44IBAgIXJ7oaD/3v/3uAYIIB9wQGAA2+/iRG5oSIM4f+1nrPYgAB3aHIAC77QYYRoCAAP676ICABXYFIntDoPf3+PC5f+BoPOX4vPNBn7IogEB/eu3QXC9wNEAAeKBIP+dgbSCDYMwgEApQVEygPCeRH8iAWBAAMHPwXDgoRGAonACwYABgN5uMAC4q8GC4U0DQsAggRF9gXFgggB/2hC4kdVAQCBVAX7xwXCVAnGCwUadAeeDYfr7IhEAAf93e+A4gpB9yRB/mqcgndRgQAHzqRE1gEC/KoCjLZEsgCB9evO4gOC/RyEgqdC2KnFO4S/KgFYsC/Ga5EBs1AX5bXHgx1C2YXEnp7GCARgB4AfE64WCnawFCgf9VAK/G/3M7zWDz4PF/maXJIAD7D8EVAP85QXN3OP/42DfoQXN/wvE/ySGABa8FAC37AgepVwQ9E1SfBAAJIEAAnrBQ39xgwJ7pRHFQX+3QECCAbyG9bPDzwXC9QMBdgQXIAAf41wEC5pLCJJBcF9fZQ5IAGYYn81q7RJQwWC/wXM9/tA4veCxooDIAPv55PEABwpB97rDAAw"))
|
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,2 @@
|
|||
let Clockwork = require('https://raw.githubusercontent.com/rozek/banglejs-2-simple-clockwork/main/Clockwork.js');
|
||||
Clockwork.windUp();
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,18 @@
|
|||
{ "id": "ac_ac",
|
||||
"name": "A Configurable Analog Clock",
|
||||
"shortName":"Configurable Clock",
|
||||
"version":"0.03",
|
||||
"description": "AC-AC, a highly customizable analog clock with several clock faces, hands and complications to choose from",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": false,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"custom": "Customizer.html",
|
||||
"storage": [
|
||||
{"name":"ac_ac.app.js","url":"app.js"},
|
||||
{"name":"ac_ac.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA/4AB304ief85L/ABNVAAwKCgILHoALBgoLHqALOrVVr4BEBZIFBBYiaCAAPq2oLQEYlqF5VrBZWnBZWvBZNWz4LGBoQLHJ4O///6v/1BZHa/4LFLYOlr9pR49r1ILJ09qr4ZBBY2vrWdBY5PBq2uyoLIquqBY5bBKoZTFLYILJJ4STDBY77IJ4QLUJ4QLU1QAE0oLPqoAGBZ0BBY9ABYMABY4KCAH4AGA="))
|
|
@ -0,0 +1,24 @@
|
|||
Bangle.loadWidgets();
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
var R = Bangle.appRect;
|
||||
|
||||
var x = 0;
|
||||
var last;
|
||||
|
||||
function getY(v) {
|
||||
return (R.y+R.y2 + v*R.h/2)/2;
|
||||
}
|
||||
Bangle.on('accel', a => {
|
||||
g.reset();
|
||||
if (last) {
|
||||
g.setColor("#f00").drawLine(x-1,getY(last.x),x,getY(a.x));
|
||||
g.setColor("#0f0").drawLine(x-1,getY(last.y),x,getY(a.y));
|
||||
g.setColor("#00f").drawLine(x-1,getY(last.z),x,getY(a.z));
|
||||
}
|
||||
last = a;x++;
|
||||
if (x>=g.getWidth()) {
|
||||
x = 1;
|
||||
g.clearRect(R);
|
||||
}
|
||||
});
|
After Width: | Height: | Size: 944 B |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "accelgraph",
|
||||
"name": "Accelerometer Graph",
|
||||
"shortName":"Accel Graph",
|
||||
"version":"0.01",
|
||||
"description": "A simple app to draw a graph of data from the accelerometer on the screen",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,debug",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"accelgraph.app.js","url":"app.js"},
|
||||
{"name":"accelgraph.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "accellog",
|
||||
"name": "Acceleration Logger",
|
||||
"shortName": "Accel Log",
|
||||
"version": "0.03",
|
||||
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoor",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"accellog.app.js","url":"app.js"},
|
||||
{"name":"accellog.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"wildcard":"accellog.?.csv"}]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "accelrec",
|
||||
"name": "Acceleration Recorder",
|
||||
"shortName": "Accel Rec",
|
||||
"version": "0.02",
|
||||
"description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"accelrec.app.js","url":"app.js"},
|
||||
{"name":"accelrec.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"wildcard":"accelrec.?.csv"}]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "aclock",
|
||||
"name": "Analog Clock",
|
||||
"version": "0.15",
|
||||
"description": "An Analog Clock",
|
||||
"icon": "clock-analog.png",
|
||||
"screenshots": [{"url":"screenshot_analog.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"aclock.app.js","url":"clock-analog.js"},
|
||||
{"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Faster maze generation
|
||||
0.03: Avoid clearing bottom widgets
|
|
@ -0,0 +1,17 @@
|
|||
# AccelaMaze
|
||||
|
||||
Tilt the watch to roll a ball through a maze.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
* Use the menu to select difficulty level (or exit).
|
||||
* Wait until the maze gets generated and a red ball appears.
|
||||
* Tilt the watch to get the ball into the green cell.
|
||||
|
||||
At any time you can click the button to return to the menu.
|
||||
|
||||
## Creator
|
||||
|
||||
[Nimrod Kerrett](https://zzzen.com)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggaXh3M53/AA3yl4IHn//+EM5nMAoIX/C4RfCC4szmcxC4QFBAAUxC4UPAwIOB+YCCiMRkAFCkIGBAAQfBC4IUEAQhHIAAQX/C5EDmcyCgUTAoYXDR4kzC4UBPoKVB+YFFAQSPBiAKBiCnDGoZECABDUCa4YX/C5qPBQwoXGkczmC/FQYSSCVQSSCEwQOCC4hKFX4QXCd5YX/C4qMEmQXITAinDPoIADTwSPFkKMBX47RGI47XIC/4XCgZ9DQYYABmKYBmIXFkczmEBRIK/CQYQIBkECSoiSCA4MQa5pEFd6IX/RgMyC6H/QASVCRIS/EAQrXFJQoX/C6kDRQIXCiYFD+QFBmIUCkYFD+CJBiSPCRwIFFSoQFCiF3u9wI4gAO+wXW+IXygAAW"))
|
|
@ -0,0 +1,287 @@
|
|||
const MARGIN = 25;
|
||||
const WALL_RIGHT = 1, WALL_DOWN = 2;
|
||||
const STATUS_GENERATING = 0, STATUS_PLAYING = 1,
|
||||
STATUS_SOLVED = 2, STATUS_ABORTED = -1;
|
||||
|
||||
function Maze(n) {
|
||||
this.n = n;
|
||||
this.status = STATUS_GENERATING;
|
||||
this.wall_length = Math.floor((g.getHeight()-2*MARGIN)/n);
|
||||
this.total_length = this.wall_length*n;
|
||||
this.margin = Math.floor((g.getHeight()-this.total_length)/2);
|
||||
this.ball_x = 0;
|
||||
this.ball_y = 0;
|
||||
// This voodoo is needed because otherwise
|
||||
// bottom line widgets (like digital clock)
|
||||
// disappear during maze generation
|
||||
Bangle.drawWidgets();
|
||||
g.setColor(g.theme.fg);
|
||||
for (let i=0; i<=n; i++) {
|
||||
g.drawRect(
|
||||
this.margin, this.margin+i*this.wall_length,
|
||||
g.getWidth()-this.margin, this.margin+i*this.wall_length
|
||||
);
|
||||
g.drawRect(
|
||||
this.margin+i*this.wall_length, this.margin,
|
||||
this.margin+i*this.wall_length, g.getHeight() - this.margin
|
||||
);
|
||||
}
|
||||
this.walls = new Uint8Array(n*n);
|
||||
this.groups = new Uint8Array(n*n);
|
||||
for (let cell = 0; cell<n*n; cell++) {
|
||||
this.walls[cell] = WALL_RIGHT|WALL_DOWN;
|
||||
this.groups[cell] = cell;
|
||||
}
|
||||
// Candidates of walls to break when digging the maze.
|
||||
// If candidate failed (breaking it would create a loop),
|
||||
// it would never succeed, so no need to retry it.
|
||||
let candidates_down = [],
|
||||
candidates_right = [];
|
||||
for (let r=0 ; r<n; r++) {
|
||||
for (let c=0; c<n; c++) {
|
||||
let cell = n*r+c;
|
||||
if (r<(n-1)) { // Don't break wall down for bottom row.
|
||||
candidates_down.push(cell);
|
||||
}
|
||||
if (c<(n-1)) { // Don't break wall right for rightmost column.
|
||||
candidates_right.push(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
let from_group, to_group;
|
||||
let ngroups = n*n;
|
||||
while (--ngroups) {
|
||||
// Abort if BTN1 pressed [grace period for menu]
|
||||
// (for some reason setWatch() fails inside constructor)
|
||||
if (ngroups<n*n-16 && digitalRead(BTN1)) {
|
||||
aborting = true;
|
||||
return;
|
||||
}
|
||||
from_group = to_group = -1;
|
||||
while (from_group<0) {
|
||||
let trying_down = false;
|
||||
if (Math.random()<0.5 && candidates_down.length || !candidates_right.length) {
|
||||
trying_down = true;
|
||||
}
|
||||
let candidates = trying_down ? candidates_down : candidates_right,
|
||||
candidate_index = Math.floor(Math.random()*candidates.length),
|
||||
cell = candidates.splice(candidate_index, 1)[0],
|
||||
r = Math.floor(cell/n),
|
||||
c = cell%n;
|
||||
if (trying_down) { // try to break a wall down
|
||||
if (this.groups[cell]!=this.groups[cell+n]) {
|
||||
this.walls[cell] &= ~WALL_DOWN;
|
||||
g.clearRect(
|
||||
this.margin+c*this.wall_length+1,
|
||||
this.margin+(r+1)*this.wall_length,
|
||||
this.margin+(c+1)*this.wall_length-1,
|
||||
this.margin+(r+1)*this.wall_length
|
||||
);
|
||||
g.flip(); // show progress.
|
||||
from_group = this.groups[cell];
|
||||
to_group = this.groups[cell+n];
|
||||
}
|
||||
} else { // try to break a wall right
|
||||
if (this.groups[cell]!=this.groups[cell+1]) {
|
||||
this.walls[cell] &= ~WALL_RIGHT;
|
||||
g.clearRect(
|
||||
this.margin+(c+1)*this.wall_length,
|
||||
this.margin+r*this.wall_length+1,
|
||||
this.margin+(c+1)*this.wall_length,
|
||||
this.margin+(r+1)*this.wall_length-1
|
||||
);
|
||||
g.flip(); // show progress.
|
||||
from_group = this.groups[cell];
|
||||
to_group = this.groups[cell+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let cell = 0; cell<n*n; cell++) {
|
||||
if (this.groups[cell]==from_group) {
|
||||
this.groups[cell] = to_group;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearCell = function(r, c) {
|
||||
if (!r && !c) {
|
||||
g.setColor("#ffff00");
|
||||
} else if (r==this.n-1 && c==this.n-1) {
|
||||
g.setColor("#00ff00");
|
||||
} else {
|
||||
g.setColor(g.theme.bg);
|
||||
}
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*c+1,
|
||||
this.margin+this.wall_length*r+1,
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
g.setColor(g.theme.fg);
|
||||
if (this.walls[r*n+c]&WALL_RIGHT) {
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*r,
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
}
|
||||
if (this.walls[r*n+c]&WALL_DOWN) {
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*c,
|
||||
this.margin+this.wall_length*(r+1),
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
}
|
||||
};
|
||||
this.drawBall = function(x, y) {
|
||||
g.setColor("#ff0000");
|
||||
g.fillEllipse(
|
||||
this.margin+x+1,
|
||||
this.margin+y+1,
|
||||
this.margin+x+this.wall_length-1,
|
||||
this.margin+y+this.wall_length-1
|
||||
);
|
||||
g.setColor(g.theme.fg);
|
||||
};
|
||||
this.move = function(dx, dy) {
|
||||
let next_x = this.ball_x,
|
||||
next_y = this.ball_y,
|
||||
ball_r = Math.floor(this.ball_y/this.wall_length),
|
||||
ball_c = Math.floor(this.ball_x/this.wall_length);
|
||||
if (this.ball_x%this.wall_length) {
|
||||
if (dx) {
|
||||
next_x += dx;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (this.ball_y%this.wall_length) {
|
||||
if (dy) {
|
||||
next_y += dy;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else { // exactly in a cell. Check walls
|
||||
if (dy<0 && ball_r>0 && !(this.walls[n*(ball_r-1)+ball_c]&WALL_DOWN)) {
|
||||
next_y--;
|
||||
} else if (dy>0 && ball_r<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_DOWN)) {
|
||||
next_y++;
|
||||
} else if (dx<0 && ball_c>0 && !(this.walls[n*ball_r+ball_c-1]&WALL_RIGHT)) {
|
||||
next_x--;
|
||||
} else if (dx>0 && ball_c<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_RIGHT)) {
|
||||
next_x++;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.clearCell(ball_r, ball_c);
|
||||
if (this.ball_x%this.wall_length) {
|
||||
this.clearCell(ball_r, ball_c+1);
|
||||
}
|
||||
if (this.ball_y%this.wall_length) {
|
||||
this.clearCell(ball_r+1, ball_c);
|
||||
}
|
||||
this.ball_x = next_x;
|
||||
this.ball_y = next_y;
|
||||
this.drawBall(this.ball_x, this.ball_y);
|
||||
if (this.ball_x==(n-1)*this.wall_length && this.ball_y==(n-1)*this.wall_length) {
|
||||
this.status = STATUS_SOLVED;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.try_move_horizontally = function(accel_x) {
|
||||
if (accel_x>0.15) {
|
||||
return this.move(-1, 0);
|
||||
} else if (accel_x<-0.15) {
|
||||
return this.move(1, 0);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.try_move_vertically = function(accel_y) {
|
||||
if (accel_y<-0.15) {
|
||||
return this.move(0,1);
|
||||
} else if (accel_y>0.15) {
|
||||
return this.move(0,-1);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.tick = function() {
|
||||
accel = Bangle.getAccel();
|
||||
if (this.ball_x%this.wall_length) {
|
||||
this.try_move_horizontally(accel.x);
|
||||
} else if (this.ball_y%this.wall_length) {
|
||||
this.try_move_vertically(accel.y);
|
||||
} else {
|
||||
if (Math.abs(accel.x)>Math.abs(accel.y)) { // prefer horizontally
|
||||
if (!this.try_move_horizontally(accel.x)) {
|
||||
this.try_move_vertically(accel.y);
|
||||
}
|
||||
} else { // prefer vertically
|
||||
if (!this.try_move_vertically(accel.y)) {
|
||||
this.try_move_horizontally(accel.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.clearCell(0,0);
|
||||
this.clearCell(n-1,n-1);
|
||||
this.drawBall(0,0);
|
||||
this.status = STATUS_PLAYING;
|
||||
}
|
||||
|
||||
function timeToText(t) { // Courtesy of stopwatch app
|
||||
let hrs = Math.floor(t/3600000);
|
||||
let mins = Math.floor(t/60000)%60;
|
||||
let secs = Math.floor(t/1000)%60;
|
||||
let tnth = Math.floor(t/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0)
|
||||
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||
else
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||
return text;
|
||||
}
|
||||
|
||||
let aborting = false;
|
||||
let start_time = 0;
|
||||
let duration = 0;
|
||||
let maze=null;
|
||||
let mazeMenu = {
|
||||
"": { "title": "Maze size", "selected": 1 },
|
||||
"Easy (8x8)": function() { E.showMenu(); maze = new Maze(8); },
|
||||
"Medium (10x10)": function() { E.showMenu(); maze = new Maze(10); },
|
||||
"Hard (14x14)": function() { E.showMenu(); maze = new Maze(14); },
|
||||
"< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock
|
||||
};
|
||||
|
||||
g.reset();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDTimeout(0);
|
||||
E.showMenu(mazeMenu);
|
||||
let maze_interval = setInterval(
|
||||
function() {
|
||||
if (maze) {
|
||||
if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) {
|
||||
maze = null;
|
||||
start_time = duration = 0;
|
||||
aborting = false;
|
||||
setTimeout(function() {E.showMenu(mazeMenu); }, 100);
|
||||
return;
|
||||
}
|
||||
if (!start_time) {
|
||||
start_time = Date.now();
|
||||
}
|
||||
if (maze.status==STATUS_PLAYING) {
|
||||
maze.tick();
|
||||
}
|
||||
if (maze.status==STATUS_SOLVED && !duration) {
|
||||
duration = Date.now()-start_time;
|
||||
g.setFontAlign(0,0).setColor(g.theme.fg);
|
||||
g.setFont("Vector",18);
|
||||
g.drawString(`Solved ${maze.n}X${maze.n} in\n ${timeToText(duration)} \nBtn1 to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||
}
|
||||
}
|
||||
}, 25);
|
After Width: | Height: | Size: 8.7 KiB |
|
@ -0,0 +1,15 @@
|
|||
{ "id": "acmaze",
|
||||
"name": "AccelaMaze",
|
||||
"shortName":"AccelaMaze",
|
||||
"version":"0.03",
|
||||
"description": "Tilt the watch to roll a ball through a maze.",
|
||||
"icon": "app.png",
|
||||
"tags": "game",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"acmaze.app.js","url":"app.js"},
|
||||
{"name":"acmaze.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 9.8 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "activepedom",
|
||||
"name": "Active Pedometer",
|
||||
"shortName": "Active Pedometer",
|
||||
"version": "0.09",
|
||||
"description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoors,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"600.png"},{"url":"10600.png"},{"url":"1600.png"}],
|
||||
"storage": [
|
||||
{"name":"activepedom.wid.js","url":"widget.js"},
|
||||
{"name":"activepedom.settings.js","url":"settings.js"},
|
||||
{"name":"activepedom.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"activepedom.app.js","url":"app.js"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "alarm",
|
||||
"name": "Default Alarm & Timer",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.14",
|
||||
"description": "Set and respond to alarms and timers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"alarm.app.js","url":"app.js"},
|
||||
{"name":"alarm.boot.js","url":"boot.js"},
|
||||
{"name":"alarm.js","url":"alarm.js"},
|
||||
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"alarm.wid.js","url":"widget.js"}
|
||||
],
|
||||
"data": [{"name":"alarm.json"}]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "alpinenav",
|
||||
"name": "Alpine Nav",
|
||||
"version": "0.01",
|
||||
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
|
||||
"icon": "app-icon.png",
|
||||
"tags": "outdoors,gps",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"alpinenav.app.js","url":"app.js"},
|
||||
{"name":"alpinenav.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "analogimgclk",
|
||||
"name": "Analog Clock (Image background)",
|
||||
"shortName": "Analog Clock",
|
||||
"version": "0.03",
|
||||
"description": "An analog clock with an image background",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"analogimgclk.app.js","url":"app.js"},
|
||||
{"name":"analogimgclk.bg.img","url":"bg.img"},
|
||||
{"name":"analogimgclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "andark",
|
||||
"name": "Analog Dark",
|
||||
"shortName":"AnDark",
|
||||
"version":"0.04",
|
||||
"description": "analog clock face without disturbing widgets",
|
||||
"icon": "andark_icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"andark.app.js","url":"app.js"},
|
||||
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -4,3 +4,4 @@
|
|||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
||||
0.05: Fix handling of message actions
|
||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# Android Integration
|
||||
|
||||
This app allows your Bangle.js to receive notifications [from the Gadgetbridge app on Android](http://www.espruino.com/Gadgetbridge)
|
||||
|
||||
See [this link](http://www.espruino.com/Gadgetbridge) for notes on how to install
|
||||
the Android app (and how it works).
|
||||
|
||||
It requires the `Messages` app on Bangle.js (which should be automatically installed) to
|
||||
display any notifications that are received.
|
||||
|
||||
## Settings
|
||||
|
||||
You can access the settings menu either from the `Android` icon in the launcher,
|
||||
or from `App Settings` in the `Settings` menu.
|
||||
|
||||
It contains:
|
||||
|
||||
* `Connected` - shows whether there is an active Bluetooth connection or not
|
||||
* `Find Phone` - opens a submenu where you can activate the `Find Phone` functionality
|
||||
of Gadgetbridge - making your phone make noise so you can find it.
|
||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||
keep any messages it has received, or should it delete them?
|
||||
* `Messages` - launches the messages app, showing a list of messages
|
||||
|
||||
## How it works
|
||||
|
||||
Gadgetbridge on Android connects to Bangle.js, and sends commands over the
|
||||
BLE UART connection. These take the form of `GB({ ... JSON ... })\n` - so they
|
||||
call a global function called `GB` which then interprets the JSON.
|
||||
|
||||
Responses are sent back to Gadgetbridge simply as one line of JSON.
|
||||
|
||||
More info on message formats on http://www.espruino.com/Gadgetbridge
|
||||
|
||||
## Testing
|
||||
|
||||
Bangle.js can only hold one connection open at a time, so it's hard to see
|
||||
if there are any errors when handling Gadgetbridge messages.
|
||||
|
||||
However you can:
|
||||
|
||||
* Use the `Gadgetbridge Debug` app on Bangle.js to display/log the messages received from Gadgetbridge
|
||||
* Connect with the Web IDE and manually enter the Gadgetbridge messages on the left-hand side to
|
||||
execute them as if they came from Gadgetbridge, for instance:
|
||||
|
||||
```
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
```
|
|
@ -4,6 +4,7 @@
|
|||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
|
@ -51,7 +52,8 @@
|
|||
// 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
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
|
@ -68,4 +70,6 @@
|
|||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.06",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
"dependencies": {"messages":"app"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.settings.js","url":"settings.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}],
|
||||
"sortorder": -8
|
||||
}
|
|
@ -2,17 +2,29 @@
|
|||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON("android.settings.json", settings);
|
||||
}
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : "Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
"On" : _=>gb({t:"findPhone",n:true}),
|
||||
"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
}),
|
||||
"Messages" : ()=>load("messages.app.js")
|
||||
/*LANG*/"Keep Msgs" : {
|
||||
value : !!settings.keep,
|
||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.keep = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js")
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"id": "animals",
|
||||
"name": "Animals Game",
|
||||
"version": "0.01",
|
||||
"description": "Simple toddler's game - displays a different number of animals each time the screen is pressed",
|
||||
"icon": "animals.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"animals.app.js","url":"animals.js"},
|
||||
{"name":"animals.img","url":"animals-icon.js","evaluate":true},
|
||||
{"name":"animals-snake.img","url":"animals-snake.js","evaluate":true},
|
||||
{"name":"animals-duck.img","url":"animals-duck.js","evaluate":true},
|
||||
{"name":"animals-swan.img","url":"animals-swan.js","evaluate":true},
|
||||
{"name":"animals-fox.img","url":"animals-fox.js","evaluate":true},
|
||||
{"name":"animals-camel.img","url":"animals-camel.js","evaluate":true},
|
||||
{"name":"animals-pig.img","url":"animals-pig.js","evaluate":true},
|
||||
{"name":"animals-sheep.img","url":"animals-sheep.js","evaluate":true},
|
||||
{"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "animclk",
|
||||
"name": "Animated Clock",
|
||||
"shortName": "Anim Clock",
|
||||
"version": "0.03",
|
||||
"description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock,animated",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"animclk.app.js","url":"app.js"},
|
||||
{"name":"animclk.pixels1","url":"animclk.pixels1"},
|
||||
{"name":"animclk.pixels2","url":"animclk.pixels2"},
|
||||
{"name":"animclk.pal","url":"animclk.pal"},
|
||||
{"name":"animclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,10 @@
|
|||
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.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.
|
||||
0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off)
|
||||
when weekday name "Off": week #:<num>
|
||||
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
|
||||
0.06: fixes #1271 - wrong settings name
|
||||
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
|
||||
week is buffered until date or timezone changes
|
|
@ -0,0 +1,79 @@
|
|||
# Anton Clock - Large font digital watch with seconds and date
|
||||
|
||||
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
||||
|
||||
## Features
|
||||
|
||||
The basic time representation only shows hours and minutes of the current time. However, Anton clock can show additional information:
|
||||
|
||||
* Seconds can be shown, either always or only if the screen is unlocked.
|
||||
* To help easy recognition, the seconds can be coloured in blue on the Bangle.js 2.
|
||||
* Date can be shown in three different formats:
|
||||
* ISO-8601: 2021-12-19
|
||||
* short local format: 19/12/2021, 19.12.2021
|
||||
* long local format: DEC 19 2021
|
||||
* Weekday can be shown (on seconds screen only instead of year)
|
||||
|
||||
## Usage
|
||||
|
||||
Install Anton clock through the Bangle.js app loader.
|
||||
Configure it through the default Bangle.js configuration mechanism
|
||||
(Settings app, "Apps" menu, "Anton clock" submenu).
|
||||
If you like it, make it your default watch face
|
||||
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
||||
|
||||
## Configuration
|
||||
|
||||
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
||||
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
|
||||
You configure Anton clock through several "on/off" switches in two menus.
|
||||
|
||||
### The main menu
|
||||
|
||||
The main menu contains several settings covering Anton clock in general.
|
||||
|
||||
* **Seconds...** - Opens the submenu for configuring the presentation of the current time's seconds.
|
||||
* **Date** - Format of the date representation. Possible values are
|
||||
* **Long** - "Long" date format in the current locale. Usually with the month as name, not number.
|
||||
* **Short** - "Short" date format in the current locale. Usually with the month as number.
|
||||
* **ISO8601** - Show the date in ISO-8601 format (YYYY-MM-DD), irrespective of the current locale.
|
||||
* **Show Weekday** - Weekday is shown in the time presentation without seconds.
|
||||
Weekday name depends on the current locale.
|
||||
If seconds are shown, the weekday is never shown as there is not enough space on the watch face.
|
||||
* **Show CalWeek** - Week-number (ISO-8601) is shown. (default: Off)
|
||||
If "Show Weekday" is "Off" displays the week-number as "week #<num>".
|
||||
If "Show Weekday" is "On" displays "weekday name short" with " #<num>" .
|
||||
If seconds are shown, the week number is never shown as there is not enough space on the watch face.
|
||||
* **Vector font** - Use the built-in vector font for dates and weekday.
|
||||
This can improve readability.
|
||||
Otherwise, a scaled version of the built-in 6x8 pixels font is used.
|
||||
|
||||
### The "Seconds" submenu
|
||||
|
||||
The "Seconds" submenu configures how (and if) seconds are shown on the "Anton" watch face.
|
||||
|
||||
* **Show** - Configure when the seconds should be shown at all:
|
||||
* **Never** - Seconds are never shown.
|
||||
In this case, hour and minute are a bit more centered on the screen and the clock will always only update every minute.
|
||||
This saves battery power.
|
||||
* **Unlocked** - Seconds are shown if the display is unlocked.
|
||||
On locked displays, only hour, minutes, date and optionally the weekday are shown.
|
||||
_This option is highly recommended on the Bangle.js 2!_
|
||||
* **Always** - Seconds are _always_ shown, irrespective of the display's unlock state.
|
||||
_Enabling this option increases power consumption as the watch face will update once per second instead of once per minute._
|
||||
* **With ":"** - If enabled, a colon ":" is prepended to the seconds.
|
||||
This resembles the usual time representation "hh:mm:ss", even though the seconds are printed on a separate line.
|
||||
* **Color** - If enabled, seconds are shown in blue instead of black.
|
||||
If the date is shown on the seconds screen, it is colored read instead of black.
|
||||
This make the visual orientation much easier on the watch face.
|
||||
* **Date** - It is possible to show the date together with the seconds:
|
||||
* **No** - Date is _not_ shown in the seconds screen.
|
||||
In this case, the seconds are centered below hour and minute.
|
||||
* **Year** - Date is shown with day, month, and year. If "Date" in the main settings is configured to _ISO8601_, this is used here, too. Otherwise, the short local format is used.
|
||||
* **Weekday** - Date is shown with day, month, and weekday.
|
||||
|
||||
The date is coloured in red if the "Coloured" option is chosen.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Anton clock makes use of core Bangle.js 2 features (coloured display, display lock state). It also runs on the Bangle.js 1 but these features are not available there due to hardware restrictions.
|
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"version": "0.06",
|
||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"antonclk.app.js","url":"app.js"},
|
||||
{"name":"antonclk.settings.js","url":"settings.js"},
|
||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"antonclk.json"}]
|
||||
}
|
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,107 @@
|
|||
// Settings menu for the enhanced Anton clock
|
||||
|
||||
(function(back) {
|
||||
var FILE = "antonclk.json";
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
secondsOnUnlock: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
var mainmenu = {
|
||||
"": {
|
||||
"title": "Anton clock"
|
||||
},
|
||||
"< Back": () => back(),
|
||||
"Seconds...": () => E.showMenu(secmenu),
|
||||
"Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]),
|
||||
"Show Weekday": {
|
||||
value: (settings.weekDay !== undefined ? settings.weekDay : true),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.weekDay = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Show CalWeek": {
|
||||
value: (settings.calWeek !== undefined ? settings.calWeek : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.calWeek = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Uppercase": {
|
||||
value: (settings.upperCase !== undefined ? settings.upperCase : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.upperCase = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Vector font": {
|
||||
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.vectorFont = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Submenu
|
||||
var secmenu = {
|
||||
"": {
|
||||
"title": "Show seconds..."
|
||||
},
|
||||
"< Back": () => E.showMenu(mainmenu),
|
||||
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
|
||||
"With \":\"": {
|
||||
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.secondsWithColon = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Color": {
|
||||
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.secondsColoured = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"])
|
||||
};
|
||||
|
||||
// Actually display the menu
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
|
||||
// end of file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "arrow",
|
||||
"name": "Arrow Compass",
|
||||
"version": "0.05",
|
||||
"description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
|
||||
"icon": "arrow.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"arrow.app.js","url":"app.js"},
|
||||
{"name":"arrow.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Update to work with Bangle.js 2
|
||||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
|
|
|
@ -8,34 +8,72 @@
|
|||
<p>GPS can take a long time (~5 minutes) to get an accurate position the first time it is used.
|
||||
AGPS uploads a few hints to the GPS receiver about satellite positions that allow it
|
||||
to get a faster, more accurate fix - however they are only valid for a short period of time.</p>
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<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" 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)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
<div id="banglejs1-info" style="display:none">
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<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" 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)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<div id="banglejs2-info" style="display:none">
|
||||
<p>Using fewer GNSS systems may decrease the time to fix. (If unsure, select only GPS)</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Select which GNSS system you want.</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="3"><i class="form-icon"></i> GPS+BDS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="4"><i class="form-icon"></i> GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="5"><i class="form-icon"></i> GPS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> BDS+GLONASS
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="gnss_select" value="6"><i class="form-icon"></i> GPS+BDS+GLONASS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p id="upload-wrap" style="display:none">Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
var isB1; // is Bangle.js 1?
|
||||
var isB2; // is Bangle.js 2?
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
var url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
var url;
|
||||
if (isB1) {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
}
|
||||
if (isB2) {
|
||||
url = "https://www.espruino.com/agps/casic.base64";
|
||||
}
|
||||
console.log("Sending...");
|
||||
//var text = document.getElementById("agpsperiod").value;
|
||||
get(url, function(b64) {
|
||||
|
@ -48,6 +86,8 @@
|
|||
});
|
||||
});
|
||||
|
||||
// =================================================== Bangle.js 1 UBLOX
|
||||
|
||||
function UBX_CMD(cmd) {
|
||||
var d = [0xB5,0x62]; // sync chars
|
||||
d = d.concat(cmd);
|
||||
|
@ -79,13 +119,40 @@
|
|||
return UBX_CMD([].slice.call(a));
|
||||
}
|
||||
|
||||
// =================================================== Bangle.js 2 CASIC
|
||||
|
||||
function CASIC_CHECKSUM(cmd) {
|
||||
var cs = 0;
|
||||
for (var i=1;i<cmd.length;i++)
|
||||
cs = cs ^ cmd.charCodeAt(i);
|
||||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
var bin = atob(b64);
|
||||
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 += "\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
|
||||
if (isB1) { // UBLOX
|
||||
//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 += "\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
|
||||
}
|
||||
if (isB2) { // CASIC
|
||||
|
||||
// Select what GNSS System to use for decreased fix time.
|
||||
var radios = document.getElementsByName('gnss_select');
|
||||
var gnss_select="1";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
gnss_select=radios[i].value;
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
}
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
|
@ -106,6 +173,15 @@
|
|||
oReq.send();
|
||||
}
|
||||
|
||||
// Called when we know what device we're using
|
||||
function onInit(device) {
|
||||
isB2 = (device && device.id=="BANGLEJS2");
|
||||
isB1 = !isB2;
|
||||
document.getElementById("banglejs1-info").style = isB1?"":"display:none";
|
||||
document.getElementById("banglejs2-info").style = isB2?"":"display:none";
|
||||
document.getElementById("upload-wrap").style = "";
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Update (AGPS)",
|
||||
"version": "0.03",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,outdoors,agps",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": []
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "astral",
|
||||
"name": "Astral Clock",
|
||||
"version": "0.03",
|
||||
"description": "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. See Readme before using.",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"astral.app.js","url":"app.js"},
|
||||
{"name":"astral.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": "astrocalc",
|
||||
"name": "Astrocalc",
|
||||
"version": "0.02",
|
||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
||||
"icon": "astrocalc.png",
|
||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
|
||||
{"name":"suncalc.js","url":"suncalc.js"},
|
||||
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
|
||||
{"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true},
|
||||
{"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true},
|
||||
{"name":"waning-crescent.img","url":"waning-crescent-icon.js","evaluate":true},
|
||||
{"name":"waning-gibbous.img","url":"waning-gibbous-icon.js","evaluate":true},
|
||||
{"name":"full.img","url":"full-icon.js","evaluate":true},
|
||||
{"name":"new.img","url":"new-icon.js","evaluate":true},
|
||||
{"name":"waxing-gibbous.img","url":"waxing-gibbous-icon.js","evaluate":true},
|
||||
{"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "astroid",
|
||||
"name": "Asteroids!",
|
||||
"version": "0.03",
|
||||
"description": "Retro asteroids game",
|
||||
"icon": "asteroids.png",
|
||||
"screenshots": [{"url":"screenshot_asteroids.png"}],
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"astroid.app.js","url":"asteroids.js"},
|
||||
{"name":"astroid.img","url":"asteroids-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "authentiwatch",
|
||||
"name": "2FA Authenticator",
|
||||
"shortName": "AuthWatch",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version": "0.04",
|
||||
"description": "Google Authenticator compatible tool.",
|
||||
"tags": "tool",
|
||||
"interface": "interface.html",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"authentiwatch.app.js","url":"app.js"},
|
||||
{"name":"authentiwatch.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"authentiwatch.json"}]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id":"awairmonitor",
|
||||
"name":"Awair Monitor",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"version":"0.03",
|
||||
"description": "Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awair device.",
|
||||
"type": "clock",
|
||||
"tags": "clock,tool,health",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"awairmonitor.app.js","url":"app.js"},
|
||||
{"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
(() => {
|
||||
(() => {
|
||||
BANGLEJS2 = process.env.HWVERSION==2;
|
||||
Bangle.setLCDTimeout(0);
|
||||
let intervalID;
|
||||
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
||||
|
@ -6,7 +7,9 @@
|
|||
// density, elasticity of bounces, "drag coefficient"
|
||||
const rho = 100, e = 0.3, C = 0.01;
|
||||
// screen width & height in pixels
|
||||
const sW = 240, sH = 160;
|
||||
const sW = g.getWidth();
|
||||
const sH = g.getHeight()*2/3;
|
||||
const bgColour ="#f00"; // only for Bangle.js 2
|
||||
// gravity constant (lowercase was already taken)
|
||||
const G = 9.80665;
|
||||
|
||||
|
@ -17,14 +20,16 @@
|
|||
// The play area is 240x160, sizes are the ball radius, so we can use common
|
||||
// denominators of 120x80 to get square rooms
|
||||
// Reverse the order to show the easiest on top of the menu
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(),
|
||||
// even size 1 actually works, but larger mazes take forever to generate
|
||||
minSize = 4, defaultSize = 10;
|
||||
const sizeNames = {
|
||||
1: "Insane", 2: "Gigantic", 4: "Enormous", 5: "Huge", 8: "Large",
|
||||
10: "Medium", 16: "Small", 20: "Tiny", 40: "Trivial",
|
||||
};
|
||||
|
||||
// even size 1 actually works, but larger mazes take forever to generate
|
||||
if (!BANGLEJS2) {
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20, 40].reverse(), minSize = 4, defaultSize = 10;
|
||||
} else {
|
||||
const sizes = [1, 2, 4, 5, 8, 10, 16, 20 ].reverse(), minSize = 4, defaultSize = 10;
|
||||
}
|
||||
/**
|
||||
* Draw something to all screen buffers
|
||||
* @param draw {function} Callback which performs the drawing
|
||||
|
@ -45,17 +50,17 @@
|
|||
|
||||
// use unbuffered graphics for UI stuff
|
||||
function showMessage(message, title) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showMessage(message, title);
|
||||
}
|
||||
|
||||
function showPrompt(prompt, options) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showPrompt(prompt, options);
|
||||
}
|
||||
|
||||
function showMenu(menu) {
|
||||
Bangle.setLCDMode();
|
||||
if (!BANGLEJS2) Bangle.setLCDMode();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -105,7 +110,7 @@
|
|||
generateMaze(); // this shows unbuffered progress messages
|
||||
if (settings.cheat && r>1) findRoute(); // not enough memory for r==1 :-(
|
||||
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
|
||||
clearAll();
|
||||
drawAll(drawMaze);
|
||||
intervalID = setInterval(tick, 100);
|
||||
|
@ -307,6 +312,7 @@
|
|||
const range = {top: 0, left: 0, bottom: rows, right: cols};
|
||||
const w = sW/cols, h = sH/rows;
|
||||
g.clear();
|
||||
if (BANGLEJS2) g.setBgColor(bgColour);
|
||||
g.setColor(0.76, 0.60, 0.42);
|
||||
for(let row = range.top; row<=range.bottom; row++) {
|
||||
for(let col = range.left; col<=range.right; col++) {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "ballmaze",
|
||||
"name": "Ball Maze",
|
||||
"version": "0.02",
|
||||
"description": "Navigate a ball through a maze by tilting your watch.",
|
||||
"icon": "icon.png",
|
||||
"type": "app",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"ballmaze.app.js","url":"app.js"},
|
||||
{"name":"ballmaze.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"ballmaze.json"}]
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial version of Balltastic released! Happy!
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||
0.03: Now also works on Bangle.js 2
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
BANGLEJS2 = process.env.HWVERSION==2;
|
||||
Bangle.setLCDBrightness(1);
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
if (!BANGLEJS2) Bangle.setLCDMode("doublebuffered");
|
||||
Bangle.setLCDTimeout(0);
|
||||
|
||||
let points = 0;
|
||||
let level = 1;
|
||||
let levelSpeedStart = 0.8;
|
||||
let nextLevelPoints = 20;
|
||||
let nextLevelPoints = 10;
|
||||
let levelSpeedFactor = 0.2;
|
||||
let counterWidth = 10;
|
||||
let gWidth = g.getWidth() - counterWidth;
|
||||
|
@ -81,12 +82,23 @@ function drawLevelText() {
|
|||
g.setColor("#26b6c7");
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("4x6", 5);
|
||||
g.drawString("Level " + level, 120, 80);
|
||||
g.drawString("Level " + level, g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
|
||||
function drawPointsText() {
|
||||
g.setColor("#26b6c7");
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("4x6", 2);
|
||||
g.drawString("Points " + points, g.getWidth()/2, g.getHeight()-20);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
//bg
|
||||
g.setColor("#71c6cf");
|
||||
if (!BANGLEJS2) {
|
||||
g.setColor("#71c6cf");
|
||||
} else {
|
||||
g.setColor("#002000");
|
||||
}
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
//counter
|
||||
|
@ -94,6 +106,7 @@ function draw() {
|
|||
|
||||
//draw level
|
||||
drawLevelText();
|
||||
drawPointsText();
|
||||
|
||||
//dot
|
||||
g.setColor("#ff0000");
|
||||
|
@ -152,7 +165,7 @@ function count() {
|
|||
if (counter <= 0) {
|
||||
running = false;
|
||||
clearInterval(drawInterval);
|
||||
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
|
||||
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Game over!");},50);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "balltastic",
|
||||
"name": "Balltastic",
|
||||
"version": "0.03",
|
||||
"description": "Simple but fun ball eats dots game.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"bangle2-balltastic-screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "game,fun",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"balltastic.app.js","url":"app.js"},
|
||||
{"name":"balltastic.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "banglebridge",
|
||||
"name": "BangleBridge",
|
||||
"shortName": "BangleBridge",
|
||||
"version": "0.01",
|
||||
"description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"banglebridge.wid.js","url":"widget.js"},
|
||||
{"name":"banglebridge.watch.img","url":"watch.img"},
|
||||
{"name":"banglebridge.heart.img","url":"heart.img"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "banglerun",
|
||||
"name": "BangleRun",
|
||||
"shortName": "BangleRun",
|
||||
"version": "0.10",
|
||||
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
|
||||
"icon": "banglerun.png",
|
||||
"tags": "run,running,fitness,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"interface": "interface.html",
|
||||
"allow_emulator": false,
|
||||
"storage": [
|
||||
{"name":"banglerun.app.js","url":"app.js"},
|
||||
{"name":"banglerun.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Add sit ups
|
||||
Add more feedback to the user about the exercises
|
||||
Clean up code
|
|
@ -0,0 +1,40 @@
|
|||
# BanglExercise
|
||||
|
||||
Can automatically track exercises while wearing the Bangle.js watch.
|
||||
|
||||
Currently only push ups, curls and sit ups are supported.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This app is experimental but it seems to work quiet reliable for me.
|
||||
It could be and is likely that the threshold values for detecting exercises do not work for everyone.
|
||||
Therefore it would be great if we could improve this app together :-)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Select the exercise type you want to practice and go for it!
|
||||
Press stop to end your exercise.
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## TODO
|
||||
* Add other exercise types:
|
||||
* Rope jumps
|
||||
* Star jumps
|
||||
* ...
|
||||
* Save exercise summaries to file system
|
||||
* Configure daily goal for exercises
|
||||
* Find a nicer icon
|
||||
|
||||
|
||||
## Contribute
|
||||
Feel free to send in improvements and remarks.
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA=="))
|
|
@ -0,0 +1,384 @@
|
|||
const Layout = require("Layout");
|
||||
const heatshrink = require('heatshrink');
|
||||
const storage = require('Storage');
|
||||
|
||||
let tStart;
|
||||
let historyY = [];
|
||||
let historyZ = [];
|
||||
let historyAvgY = [];
|
||||
let historyAvgZ = [];
|
||||
let historySlopeY = [];
|
||||
let historySlopeZ = [];
|
||||
|
||||
let lastZeroPassCameFromPositive;
|
||||
let lastZeroPassTime = 0;
|
||||
|
||||
let lastExerciseCompletionTime = 0;
|
||||
let lastExerciseHalfCompletionTime = 0;
|
||||
|
||||
let exerciseType = {
|
||||
"id": "",
|
||||
"name": ""
|
||||
};
|
||||
|
||||
// add new exercises here:
|
||||
const exerciseTypes = [{
|
||||
"id": "pushup",
|
||||
"name": "push ups",
|
||||
"useYaxis": true,
|
||||
"useZaxis": false,
|
||||
"threshold": 2500,
|
||||
"thresholdMinTime": 800, // mininmal time between two push ups in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two push ups in ms
|
||||
"thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms
|
||||
},
|
||||
{
|
||||
"id": "curl",
|
||||
"name": "curls",
|
||||
"useYaxis": true,
|
||||
"useZaxis": false,
|
||||
"threshold": 2500,
|
||||
"thresholdMinTime": 800, // mininmal time between two curls in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two curls in ms
|
||||
"thresholdMinDurationTime": 500, // mininmal duration of half a curl in ms
|
||||
},
|
||||
{
|
||||
"id": "situp",
|
||||
"name": "sit ups",
|
||||
"useYaxis": false,
|
||||
"useZaxis": true,
|
||||
"threshold": 3500,
|
||||
"thresholdMinTime": 800, // mininmal time between two sit ups in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two sit ups in ms
|
||||
"thresholdMinDurationTime": 500, // mininmal duration of half a sit up in ms
|
||||
}
|
||||
];
|
||||
let exerciseCounter = 0;
|
||||
|
||||
let layout;
|
||||
let recordActive = false;
|
||||
|
||||
// Size of average window for data analysis
|
||||
const avgSize = 6;
|
||||
|
||||
let hrtValue;
|
||||
|
||||
let settings = storage.readJSON("banglexercise.json", 1) || {
|
||||
'buzz': true
|
||||
};
|
||||
|
||||
function showMainMenu() {
|
||||
let menu;
|
||||
menu = {
|
||||
"": {
|
||||
title: "BanglExercise"
|
||||
}
|
||||
};
|
||||
|
||||
exerciseTypes.forEach(function(et) {
|
||||
menu[et.name] = function() {
|
||||
exerciseType = et;
|
||||
E.showMenu();
|
||||
startTraining();
|
||||
};
|
||||
});
|
||||
|
||||
if (exerciseCounter > 0) {
|
||||
menu["--------"] = {
|
||||
value: ""
|
||||
};
|
||||
menu["Last:"] = {
|
||||
value: exerciseCounter + " " + exerciseType.name
|
||||
};
|
||||
}
|
||||
menu.exit = function() {
|
||||
load();
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function accelHandler(accel) {
|
||||
if (!exerciseType) return;
|
||||
const t = Math.round(new Date().getTime()); // time in ms
|
||||
const y = exerciseType.useYaxis ? accel.y * 8192 : 0;
|
||||
const z = exerciseType.useZaxis ? accel.z * 8192 : 0;
|
||||
//console.log(t, y, z);
|
||||
|
||||
if (exerciseType.useYaxis) {
|
||||
while (historyY.length > avgSize)
|
||||
historyY.shift();
|
||||
|
||||
historyY.push(y);
|
||||
|
||||
if (historyY.length > avgSize / 2) {
|
||||
const avgY = E.sum(historyY) / historyY.length;
|
||||
historyAvgY.push([t, avgY]);
|
||||
while (historyAvgY.length > avgSize)
|
||||
historyAvgY.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (exerciseType.useZaxis) {
|
||||
while (historyZ.length > avgSize)
|
||||
historyZ.shift();
|
||||
|
||||
historyZ.push(z);
|
||||
|
||||
if (historyZ.length > avgSize / 2) {
|
||||
const avgZ = E.sum(historyZ) / historyZ.length;
|
||||
historyAvgZ.push([t, avgZ]);
|
||||
while (historyAvgZ.length > avgSize)
|
||||
historyAvgZ.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Y
|
||||
if (exerciseType.useYaxis) {
|
||||
let l = historyAvgY.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgY[l - 2];
|
||||
const p2 = historyAvgY[l - 1];
|
||||
const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
|
||||
// we use this data for exercises which can be detected by using Y axis data
|
||||
isValidExercise(slopeY, t);
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Z
|
||||
if (exerciseType.useZaxis) {
|
||||
l = historyAvgZ.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgZ[l - 2];
|
||||
const p2 = historyAvgZ[l - 1];
|
||||
const slopeZ = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
|
||||
// we use this data for some exercises which can be detected by using Z axis data
|
||||
isValidExercise(slopeZ, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if slope value of Y-axis or Z-axis data (depending on exercise type) looks like an exercise
|
||||
*
|
||||
* In detail we look for slop values which are bigger than the configured threshold for the current exercise type
|
||||
* Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
|
||||
* If we find one pair of these values this could be part of one exercise.
|
||||
* Then we look for a pair of values which cross the zero from the otherwise direction
|
||||
*/
|
||||
function isValidExercise(slope, t) {
|
||||
if (!exerciseType) return;
|
||||
|
||||
const threshold = exerciseType.threshold;
|
||||
const historySlopeValues = exerciseType.useYaxis ? historySlopeY : historySlopeZ;
|
||||
const thresholdMinTime = exerciseType.thresholdMinTime;
|
||||
const thresholdMaxTime = exerciseType.thresholdMaxTime;
|
||||
const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
|
||||
const exerciseName = exerciseType.name;
|
||||
|
||||
|
||||
if (Math.abs(slope) >= threshold) {
|
||||
historySlopeValues.push([t, slope]);
|
||||
//console.log(t, Math.abs(slope));
|
||||
|
||||
const lSlopeHistory = historySlopeValues.length;
|
||||
if (lSlopeHistory > 1) {
|
||||
const p1 = historySlopeValues[lSlopeHistory - 1][1];
|
||||
const p2 = historySlopeValues[lSlopeHistory - 2][1];
|
||||
if (p1 > 0 && p2 < 0) {
|
||||
if (lastZeroPassCameFromPositive == false) {
|
||||
lastExerciseHalfCompletionTime = t;
|
||||
console.log(t, exerciseName + " half complete...");
|
||||
|
||||
layout.progress.label = "½";
|
||||
layout.recording.label = "TRAINING";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = true;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
if (p2 > 0 && p1 < 0) {
|
||||
if (lastZeroPassCameFromPositive == true) {
|
||||
const tDiffLastExercise = t - lastExerciseCompletionTime;
|
||||
const tDiffStart = t - tStart;
|
||||
console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
|
||||
// check minimal time between exercises:
|
||||
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
|
||||
|
||||
// check maximal time between exercises:
|
||||
if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) {
|
||||
|
||||
// check minimal duration of exercise:
|
||||
const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime;
|
||||
if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) {
|
||||
//console.log(t, exerciseName + " complete!!!");
|
||||
|
||||
lastExerciseCompletionTime = t;
|
||||
exerciseCounter++;
|
||||
|
||||
layout.count.label = exerciseCounter;
|
||||
layout.progress.label = "";
|
||||
layout.recording.label = "Good!";
|
||||
|
||||
g.clear();
|
||||
layout.render();
|
||||
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(200, 0.5);
|
||||
} else {
|
||||
console.log(t, exerciseName + " too quick for duration time threshold!"); // thresholdMinDurationTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go slower!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
} else {
|
||||
console.log(t, exerciseName + " top slow for time threshold!"); // thresholdMaxTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go faster!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
} else {
|
||||
console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go slower!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = false;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
historyY = [];
|
||||
historyZ = [];
|
||||
historyAvgY = [];
|
||||
historyAvgZ = [];
|
||||
historySlopeY = [];
|
||||
historySlopeZ = [];
|
||||
|
||||
lastZeroPassCameFromPositive = undefined;
|
||||
lastZeroPassTime = 0;
|
||||
lastExerciseHalfCompletionTime = 0;
|
||||
lastExerciseCompletionTime = 0;
|
||||
exerciseCounter = 0;
|
||||
tStart = 0;
|
||||
}
|
||||
|
||||
|
||||
function startTraining() {
|
||||
if (recordActive) return;
|
||||
g.clear(1);
|
||||
reset();
|
||||
Bangle.setLCDTimeout(0); // force LCD on
|
||||
Bangle.setHRMPower(1, "banglexercise");
|
||||
if (!hrtValue) hrtValue = "...";
|
||||
|
||||
layout = new Layout({
|
||||
type: "v",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "type",
|
||||
font: "6x8:2",
|
||||
label: exerciseType.name,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "count",
|
||||
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
|
||||
label: exerciseCounter,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "progress",
|
||||
font: "6x8:2",
|
||||
label: "",
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "img",
|
||||
pad: 4,
|
||||
src: function() {
|
||||
return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "hrtRate",
|
||||
font: "6x8:2",
|
||||
label: hrtValue,
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "recording",
|
||||
font: "6x8:2",
|
||||
label: "TRAINING",
|
||||
bgCol: "#f00",
|
||||
pad: 5,
|
||||
fillx: 1
|
||||
},
|
||||
]
|
||||
}, {
|
||||
btns: [{
|
||||
label: "STOP",
|
||||
cb: () => {
|
||||
stopTraining();
|
||||
}
|
||||
}],
|
||||
lazy: false
|
||||
});
|
||||
layout.render();
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
|
||||
tStart = new Date().getTime();
|
||||
recordActive = true;
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(200, 1);
|
||||
|
||||
// delay start a little bit
|
||||
setTimeout(() => {
|
||||
Bangle.on('accel', accelHandler);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTraining() {
|
||||
if (!recordActive) return;
|
||||
|
||||
g.clear(1);
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
Bangle.setHRMPower(0, "banglexercise");
|
||||
showMainMenu();
|
||||
recordActive = false;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
hrtValue = hrm.bpm;
|
||||
});
|
||||
|
||||
g.clear(1);
|
||||
showMainMenu();
|
After Width: | Height: | Size: 690 B |
|
@ -0,0 +1,21 @@
|
|||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version":"0.02",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "sport",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"banglexercise.app.js","url":"app.js"},
|
||||
{"name":"banglexercise.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"banglexercise.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"banglexercise.json"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,21 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "banglexercise.json";
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
E.showMenu({
|
||||
'': { 'title': 'BanglExercise' },
|
||||
'< Back': back,
|
||||
'Buzz': {
|
||||
value: "buzz" in settings ? settings.buzz : false,
|
||||
format: () => (settings.buzz ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.buzz = !settings.buzz;
|
||||
save('buzz', settings.buzz);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "barclock",
|
||||
"name": "Bar Clock",
|
||||
"version": "0.09",
|
||||
"description": "A simple digital clock showing seconds as a bar",
|
||||
"icon": "clock-bar.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"barclock.app.js","url":"clock-bar.js"},
|
||||
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "batchart",
|
||||
"name": "Battery Chart",
|
||||
"shortName": "Battery Chart",
|
||||
"version": "0.10",
|
||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||
"icon": "app.png",
|
||||
"tags": "app,widget,battery,time,record,chart,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"batchart.wid.js","url":"widget.js"},
|
||||
{"name":"batchart.app.js","url":"app.js"},
|
||||
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "batclock",
|
||||
"name": "Bat Clock",
|
||||
"shortName": "Bat Clock",
|
||||
"version": "0.02",
|
||||
"description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
|
||||
"icon": "bat-clock.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"batclock.app.js","url":"bat-clock.app.js"},
|
||||
{"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "battleship",
|
||||
"name": "Battleship",
|
||||
"version": "0.01",
|
||||
"description": "The classic game of battleship",
|
||||
"icon": "battleship-icon.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"bangle1-battle-ship-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"battleship.app.js","url":"battleship.js"},
|
||||
{"name":"battleship.img","url":"battleship-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "bclock",
|
||||
"name": "Binary Clock",
|
||||
"version": "0.03",
|
||||
"description": "A simple binary clock watch face",
|
||||
"icon": "clock-binary.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-binary-clock-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"bclock.app.js","url":"clock-binary.js"},
|
||||
{"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "beebclock",
|
||||
"name": "Beeb Clock",
|
||||
"version": "0.05",
|
||||
"description": "Clock face that may be coincidentally familiar to BBC viewers",
|
||||
"icon": "beebclock.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"screenshots": [{"url":"bangle1-beeb-clock-screenshot.png"}],
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"beebclock.app.js","url":"beebclock.js"},
|
||||
{"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "beer",
|
||||
"name": "Beer Compass",
|
||||
"version": "0.01",
|
||||
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports": ["BANGLEJS"],
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"beer.app.js"},
|
||||
{"name":"beer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "berlinc",
|
||||
"name": "Berlin Clock",
|
||||
"version": "0.05",
|
||||
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
|
||||
"icon": "berlin-clock.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"berlin-clock-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"berlinc.app.js","url":"berlin-clock.js"},
|
||||
{"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true}
|
||||
]
|
||||
}
|