Merge branch 'espruino:master' into master
commit
0f3be07fe6
|
|
@ -9,3 +9,4 @@ appdates.csv
|
|||
_config.yml
|
||||
tests/Layout/bin/tmp.*
|
||||
tests/Layout/testresult.bmp
|
||||
apps.local.json
|
||||
47
README.md
47
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -101,8 +101,8 @@
|
|||
<script>
|
||||
$(function () {
|
||||
let ClockSize, ClockSizeURL
|
||||
let ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL
|
||||
let ClockHands, SecondHand, ClockHandsURL, FillColor
|
||||
let ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots
|
||||
let ClockHands, ClockHandsURL, SecondHand, FillColor
|
||||
let ComplicationTL, ComplicationTLURL
|
||||
let ComplicationT, ComplicationTURL
|
||||
let ComplicationTR, ComplicationTRURL
|
||||
|
|
@ -118,8 +118,8 @@
|
|||
function backupConfiguration () {
|
||||
let Configuration = {
|
||||
ClockSize, ClockSizeURL,
|
||||
ClockFace, ClockFaceNumerals, ClockFaceDots, ClockFaceURL,
|
||||
ClockHands, SecondHand, ClockHandsURL, FillColor,
|
||||
ClockFace, ClockFaceURL, ClockFaceNumerals, ClockFaceDots,
|
||||
ClockHands, ClockHandsURL, SecondHand, FillColor,
|
||||
ComplicationTL, ComplicationTLURL,
|
||||
ComplicationT, ComplicationTURL,
|
||||
ComplicationTR, ComplicationTRURL,
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
ComplicationBR, ComplicationBRURL,
|
||||
Foreground, Background
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
localStorage.setItem('ac_ac',JSON.stringify(Configuration))
|
||||
} catch (Signal) {
|
||||
|
|
@ -311,11 +311,11 @@
|
|||
|
||||
function chosenClockFace () {
|
||||
switch (ClockFace) {
|
||||
case 'none': return "undefined"
|
||||
case 'four-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-four-fold-clock-face/main/ClockFace.js')"
|
||||
case 'twelve-fold': return "require('https://raw.githubusercontent.com/rozek/banglejs-2-twelve-fold-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 + "')"
|
||||
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 + "')"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,7 +412,7 @@ console.log(AppSource)
|
|||
}
|
||||
|
||||
$('input[type="radio"]').on('change',retrieveAndValidateInputs)
|
||||
$('input[type="url"]'). on('change',retrieveAndValidateInputs)
|
||||
$('input[type="url"]'). on('input', retrieveAndValidateInputs)
|
||||
$('select'). on('change',retrieveAndValidateInputs)
|
||||
$('#UploadButton').on('click',createAndUploadApp)
|
||||
})
|
||||
|
|
@ -485,23 +485,23 @@ console.log(AppSource)
|
|||
<input type="radio" name="clock-face" value="none" checked>
|
||||
<img src="none.png"/>
|
||||
</label><br>
|
||||
none
|
||||
(none)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="four-fold">
|
||||
<img src="fourfoldClockFace.png"/>
|
||||
<input type="radio" name="clock-face" value="four-numbered">
|
||||
<img src="fournumberedClockFace.png"/>
|
||||
</label><br>
|
||||
four-fold
|
||||
four-numbered
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<label class="Preview">
|
||||
<input type="radio" name="clock-face" value="twelve-fold">
|
||||
<img src="twelvefoldClockFace.png"/>
|
||||
<input type="radio" name="clock-face" value="twelve-numbered">
|
||||
<img src="twelvenumberedClockFace.png"/>
|
||||
</label><br>
|
||||
twelve-fold
|
||||
twelve-numbered
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
|
@ -521,25 +521,25 @@ console.log(AppSource)
|
|||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</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-fold" 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-fold" 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><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>
|
||||
|
|
@ -582,6 +582,11 @@ console.log(AppSource)
|
|||
</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)
|
||||
|
|
@ -631,11 +636,6 @@ console.log(AppSource)
|
|||
<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><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>
|
||||
|
||||
<h3>Complications</h3>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 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}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
@ -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,2 @@
|
|||
0.01: New App!
|
||||
0.02: Faster maze generation
|
||||
|
|
@ -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,295 @@
|
|||
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.clearScreen = function() {
|
||||
g.clearRect(
|
||||
0, this.margin,
|
||||
g.getWidth(), this.margin+this.total_length
|
||||
);
|
||||
};
|
||||
this.clearScreen();
|
||||
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.clearScreen = function() {
|
||||
g.clearRect(
|
||||
0, MARGIN, g.getWidth(), g.getHeight()-MARGIN-1
|
||||
);
|
||||
};
|
||||
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.clear(true);
|
||||
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)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||
}
|
||||
}
|
||||
}, 25);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "acmaze",
|
||||
"name": "AccelaMaze",
|
||||
"shortName":"AccelaMaze",
|
||||
"version":"0.02",
|
||||
"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}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
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}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"}]
|
||||
}
|
||||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "balltastic",
|
||||
"name": "Balltastic",
|
||||
"version": "0.02",
|
||||
"description": "Simple but fun ball eats dots game.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "game,fun",
|
||||
"supports": ["BANGLEJS"],
|
||||
"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}
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Add sit ups
|
||||
Add more feedback to the user about the exercises
|
||||
Clean up code
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Can automatically track exercises while wearing the Bangle.js watch.
|
||||
|
||||
Currently only push ups and curls are supported.
|
||||
Currently only push ups, curls and sit ups are supported.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ Press stop to end your exercise.
|
|||
## TODO
|
||||
* Add other exercise types:
|
||||
* Rope jumps
|
||||
* Sit ups
|
||||
* Star jumps
|
||||
* ...
|
||||
* Save exercise summaries to file system
|
||||
* Configure daily goal for exercises
|
||||
|
|
|
|||
|
|
@ -25,22 +25,32 @@ let exerciseType = {
|
|||
const exerciseTypes = [{
|
||||
"id": "pushup",
|
||||
"name": "push ups",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1400, // mininmal time between two push ups in ms
|
||||
"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": 700, // mininmal duration of half a push ups in ms
|
||||
"thresholdMinDurationTime": 600, // mininmal duration of half a push up in ms
|
||||
},
|
||||
{
|
||||
"id": "curl",
|
||||
"name": "curls",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1000, // mininmal time between two curls in ms
|
||||
"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 push ups 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;
|
||||
|
|
@ -66,7 +76,7 @@ function showMainMenu() {
|
|||
};
|
||||
|
||||
exerciseTypes.forEach(function(et) {
|
||||
menu["Do " + et.name] = function() {
|
||||
menu[et.name] = function() {
|
||||
exerciseType = et;
|
||||
E.showMenu();
|
||||
startTraining();
|
||||
|
|
@ -81,8 +91,8 @@ function showMainMenu() {
|
|||
value: exerciseCounter + " " + exerciseType.name
|
||||
};
|
||||
}
|
||||
menu.Exit = function() {
|
||||
load();
|
||||
menu.exit = function() {
|
||||
load();
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
|
|
@ -91,11 +101,11 @@ function showMainMenu() {
|
|||
function accelHandler(accel) {
|
||||
if (!exerciseType) return;
|
||||
const t = Math.round(new Date().getTime()); // time in ms
|
||||
const y = exerciseType.useYaxe ? accel.y * 8192 : 0;
|
||||
const z = exerciseType.useZaxe ? accel.z * 8192 : 0;
|
||||
const y = exerciseType.useYaxis ? accel.y * 8192 : 0;
|
||||
const z = exerciseType.useZaxis ? accel.z * 8192 : 0;
|
||||
//console.log(t, y, z);
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
if (exerciseType.useYaxis) {
|
||||
while (historyY.length > avgSize)
|
||||
historyY.shift();
|
||||
|
||||
|
|
@ -109,7 +119,7 @@ function accelHandler(accel) {
|
|||
}
|
||||
}
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
if (exerciseType.useZaxis) {
|
||||
while (historyZ.length > avgSize)
|
||||
historyZ.shift();
|
||||
|
||||
|
|
@ -124,72 +134,64 @@ function accelHandler(accel) {
|
|||
}
|
||||
|
||||
// slope for Y
|
||||
if (exerciseType.useYaxe) {
|
||||
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
|
||||
switch (exerciseType.id) {
|
||||
case "pushup":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
case "curl":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
}
|
||||
|
||||
isValidExercise(slopeY, t);
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Z
|
||||
if (exerciseType.useZaxe) {
|
||||
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] - p1[0]);
|
||||
historyAvgZ.shift();
|
||||
historySlopeZ.push([p2[0] - p1[0], slopeZ]);
|
||||
|
||||
// TODO: we can use this data for some exercises which can be detected by using Z axis data
|
||||
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 data looks like an exercise
|
||||
* 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 Y threshold for the current 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 isValidYAxisExercise(slopeY, t) {
|
||||
function isValidExercise(slope, t) {
|
||||
if (!exerciseType) return;
|
||||
|
||||
const thresholdY = exerciseType.thresholdY;
|
||||
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(slopeY) >= thresholdY) {
|
||||
historyAvgY.shift();
|
||||
historySlopeY.push([t, slopeY]);
|
||||
//console.log(t, Math.abs(slopeY));
|
||||
|
||||
const lSlopeY = historySlopeY.length;
|
||||
if (lSlopeY > 1) {
|
||||
const p1 = historySlopeY[lSlopeY - 1][1];
|
||||
const p2 = historySlopeY[lSlopeY - 2][1];
|
||||
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...");
|
||||
console.log(t, exerciseName + " half complete...");
|
||||
|
||||
layout.progress.label = "½";
|
||||
layout.recording.label = "TRAINING";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
|
@ -201,7 +203,7 @@ function isValidYAxisExercise(slopeY, t) {
|
|||
if (lastZeroPassCameFromPositive == true) {
|
||||
const tDiffLastExercise = t - lastExerciseCompletionTime;
|
||||
const tDiffStart = t - tStart;
|
||||
//console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
|
||||
// check minimal time between exercises:
|
||||
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
|
||||
|
|
@ -219,22 +221,36 @@ function isValidYAxisExercise(slopeY, t) {
|
|||
|
||||
layout.count.label = exerciseCounter;
|
||||
layout.progress.label = "";
|
||||
layout.recording.label = "Good!";
|
||||
|
||||
g.clear();
|
||||
layout.render();
|
||||
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(100, 0.4);
|
||||
Bangle.buzz(200, 0.5);
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to quick for duration time threshold!");
|
||||
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 + " to slow for time threshold!");
|
||||
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 + " to quick for time threshold!");
|
||||
console.log(t, exerciseName + " too quick for time threshold!"); // thresholdMinTime
|
||||
lastExerciseCompletionTime = t;
|
||||
|
||||
layout.recording.label = "Go slower!";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +283,7 @@ function startTraining() {
|
|||
if (recordActive) return;
|
||||
g.clear(1);
|
||||
reset();
|
||||
Bangle.setLCDTimeout(0); // force LCD on
|
||||
Bangle.setHRMPower(1, "banglexercise");
|
||||
if (!hrtValue) hrtValue = "...";
|
||||
|
||||
|
|
@ -285,7 +302,7 @@ function startTraining() {
|
|||
type: "txt",
|
||||
id: "count",
|
||||
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
|
||||
label: 10,
|
||||
label: exerciseCounter,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
|
|
@ -337,11 +354,16 @@ function startTraining() {
|
|||
layout.render();
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
Bangle.on('accel', accelHandler);
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "binclock",
|
||||
"name": "Binary Clock",
|
||||
"shortName": "Binary Clock",
|
||||
"version": "0.03",
|
||||
"description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock,binary",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"binclock.app.js","url":"app.js"},
|
||||
{"name":"binclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{ "id": "binwatch",
|
||||
"name": "Binary Watch",
|
||||
"shortName":"BinWatch",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.04",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator":true,
|
||||
"description": "Famous binary watch",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"binwatch.app.js","url":"app.js"},
|
||||
{"name":"binwatch.bg176.img","url":"Background176_center.img"},
|
||||
{"name":"binwatch.bg240.img","url":"Background240_center.img"},
|
||||
{"name":"binwatch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "blackjack",
|
||||
"name": "Black Jack game",
|
||||
"shortName": "Black Jack game",
|
||||
"version": "0.02",
|
||||
"description": "Simple implementation of card game Black Jack",
|
||||
"icon": "blackjack.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"blackjack.app.js","url":"blackjack.app.js"},
|
||||
{"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "bledetect",
|
||||
"name": "BLE Detector",
|
||||
"shortName": "BLE Detector",
|
||||
"version": "0.03",
|
||||
"description": "Detect BLE devices and show some informations.",
|
||||
"icon": "bledetect.png",
|
||||
"tags": "app,bluetooth,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bledetect.app.js","url":"bledetect.js"},
|
||||
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "blescan",
|
||||
"name": "BLE Scanner",
|
||||
"version": "0.01",
|
||||
"description": "Scan for advertising BLE devices",
|
||||
"icon": "blescan.png",
|
||||
"tags": "bluetooth",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"blescan.app.js","url":"blescan.js"},
|
||||
{"name":"blescan.img","url":"blescan-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "blobclk",
|
||||
"name": "Large Digit Blob Clock",
|
||||
"shortName": "Blob Clock",
|
||||
"version": "0.06",
|
||||
"description": "A clock with big digits",
|
||||
"icon": "clock-blob.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot.png"},{"url":"bangle1-large-digit-blob-clock-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"blobclk.app.js","url":"clock-blob.js"},
|
||||
{"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "bluetoothdock",
|
||||
"name": "Bluetooth Dock",
|
||||
"shortName": "Dock",
|
||||
"version": "0.01",
|
||||
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
|
||||
"icon": "app.png",
|
||||
"tags": "bluetooth",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bluetoothdock.app.js","url":"app.js"},
|
||||
{"name":"bluetoothdock.boot.js","url":"boot.js"},
|
||||
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "boldclk",
|
||||
"name": "Bold Clock",
|
||||
"version": "0.05",
|
||||
"description": "Simple, readable and practical clock",
|
||||
"icon": "bold_clock.png",
|
||||
"screenshots": [{"url":"screenshot_bold.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"boldclk.app.js","url":"bold_clock.js"},
|
||||
{"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.41",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":".boot0","url":"boot0.js"},
|
||||
{"name":".bootcde","url":"bootloader.js"},
|
||||
{"name":"bootupdate.js","url":"bootupdate.js"}
|
||||
],
|
||||
"sortorder": -10
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "bootgattbat",
|
||||
"name": "BLE GATT Battery Service",
|
||||
"shortName": "BLE Battery Service",
|
||||
"version": "0.01",
|
||||
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
|
||||
"icon": "bluetooth.png",
|
||||
"type": "bootloader",
|
||||
"tags": "battery,ble,bluetooth,gatt",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gattbat.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "breath",
|
||||
"name": "Breathing App",
|
||||
"shortName": "Breathing App",
|
||||
"version": "0.01",
|
||||
"description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR",
|
||||
"icon": "app-icon.png",
|
||||
"tags": "tools,health",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"breath.app.js","url":"app.js"},
|
||||
{"name":"breath.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"breath.settings.json","url":"settings.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.03",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthrm.app.js","url":"bthrm.js"},
|
||||
{"name":"bthrm.recorder.js","url":"recorder.js"},
|
||||
{"name":"bthrm.boot.js","url":"boot.js"},
|
||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"bthrm.settings.js","url":"settings.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": "buffgym",
|
||||
"name": "BuffGym",
|
||||
"version": "0.02",
|
||||
"description": "BuffGym is the famous 5x5 workout program for the BangleJS",
|
||||
"icon": "buffgym.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors,gym,exercise",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"interface": "buffgym.html",
|
||||
"allow_emulator": false,
|
||||
"storage": [
|
||||
{"name":"buffgym.app.js","url":"buffgym.app.js"},
|
||||
{"name":"buffgym-set.js","url":"buffgym-set.js"},
|
||||
{"name":"buffgym-exercise.js","url":"buffgym-exercise.js"},
|
||||
{"name":"buffgym-workout.js","url":"buffgym-workout.js"},
|
||||
{"name":"buffgym-workout-a.json","url":"buffgym-workout-a.json"},
|
||||
{"name":"buffgym-workout-b.json","url":"buffgym-workout-b.json"},
|
||||
{"name":"buffgym-workout-index.json","url":"buffgym-workout-index.json"},
|
||||
{"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "calculator",
|
||||
"name": "Calculator",
|
||||
"shortName": "Calculator",
|
||||
"version": "0.05",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||
"icon": "calculator.png",
|
||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||
"tags": "app,tool",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"calculator.app.js","url":"app.js"},
|
||||
{"name":"calculator.img","url":"calculator-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.06",
|
||||
"description": "Simple calendar",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
"tags": "calendar",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"calendar.app.js","url":"calendar.js"},
|
||||
{"name":"calendar.settings.js","url":"settings.js"},
|
||||
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"calendar.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "carcrazy",
|
||||
"name": "Car Crazy",
|
||||
"shortName": "Car Crazy",
|
||||
"version": "0.03",
|
||||
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
|
||||
"icon": "carcrash.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"carcrazy.app.js","url":"app.js"},
|
||||
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"carcrazy.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"CarCrazy.csv"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "chargeanim",
|
||||
"name": "Charge Animation",
|
||||
"version": "0.02",
|
||||
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
|
||||
"icon": "icon.png",
|
||||
"tags": "battery",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"chargeanim.app.js","url":"app.js"},
|
||||
{"name":"chargeanim.boot.js","url":"boot.js"},
|
||||
{"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "choozi",
|
||||
"name": "Choozi",
|
||||
"version": "0.01",
|
||||
"description": "Choose people or things at random using Bangle.js.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||
"storage": [
|
||||
{"name":"choozi.app.js","url":"app.js"},
|
||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "chrono",
|
||||
"name": "Chrono",
|
||||
"shortName": "Chrono",
|
||||
"version": "0.01",
|
||||
"description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.",
|
||||
"icon": "chrono.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"chrono.app.js","url":"chrono.js"},
|
||||
{"name":"chrono.img","url":"chrono-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "chronowid",
|
||||
"name": "Chrono Widget",
|
||||
"shortName": "Chrono Widget",
|
||||
"version": "0.05",
|
||||
"description": "Chronometer (timer) which runs as widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"chronowid.wid.js","url":"widget.js"},
|
||||
{"name":"chronowid.app.js","url":"app.js"},
|
||||
{"name":"chronowid.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -7,3 +7,7 @@
|
|||
Make circles and text slightly bigger
|
||||
0.05: Show correct percentage values in circles
|
||||
Show humidity as weather circle data
|
||||
0.06: Allow settings empty circles
|
||||
Support to choose between humidity and wind speed for weather circle progress
|
||||
Support to show time and progress until next sunrise or sunset
|
||||
Load daily steps from Bangle health if available
|
||||
|
|
|
|||
|
|
@ -5,23 +5,20 @@ A clock with circles for different data at the bottom in a probably familiar sty
|
|||
By default the time, date and day of week is shown.
|
||||
|
||||
It can show the following information (this can be configured):
|
||||
* Steps (requires [pedometer widget](https://banglejs.com/apps/#pedometer))
|
||||
* Steps distance (depending on steps)
|
||||
* Steps
|
||||
* Steps distance
|
||||
* Heart rate (automatically updates when screen is on and unlocked)
|
||||
* Battery (including charging status and battery low warning)
|
||||
* Weather (requires [weather app](https://banglejs.com/apps/#weather))
|
||||
* Humidity as circle progress
|
||||
* Humidity or wind speed as circle progress
|
||||
* Temperature inside circle
|
||||
* Condition as icon below circle
|
||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
||||
# TODO
|
||||
* Add sunrise and sunset
|
||||
* Display moon instead of sun during night on weather circle
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const locale = require("locale");
|
||||
const heatshrink = require("heatshrink");
|
||||
const storage = require("Storage");
|
||||
const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
|
||||
const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA="));
|
||||
const shoesIconGreen = heatshrink.decompress(atob("h0OwYJGhIEDgVIAgUEyQKDkmACgcggVACIeQAYMSgIRCgmApIbDiQUDAAkBkAFDGoYAD"));
|
||||
|
|
@ -11,6 +12,7 @@ const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSA
|
|||
|
||||
const weatherCloudy = heatshrink.decompress(atob("iEQwYWTgP//+AAoMPAoPwAoN/AocfAgP//0AAgQAB/AFEABgdDAAMDDohMRA"));
|
||||
const weatherSunny = heatshrink.decompress(atob("iEQwYLIg3AAgVgAQMMAo8Am3YAgUB23bAoUNAoIUBjYFCsOwBYoFDDpFgHYI1JI4gFGAAYA="));
|
||||
const weatherMoon = heatshrink.decompress(atob("iEQwIFCgOAh/wj/4n/8AId//wBBBIoRBCoIZBDoI"));
|
||||
const weatherPartlyCloudy = heatshrink.decompress(atob("iEQwYQNv0AjgGDn4EDh///gFChwREC4MfxwIBv0//+AC4X4j4FCv/AgfwgED/wIBuAaBBwgFDgP4gf/AAXABwIEBDQQAEA=="));
|
||||
const weatherRainy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AocAnAFBFIU4EAM//gRBEAIOBhw1C/AmDAosAC4JNIAAg"));
|
||||
const weatherPartlyRainy = heatshrink.decompress(atob("h0OwYJGjkAnAFCj+AAgU//4FCuEA8EAg8ch/4gEB4////AAoIIBCIMD/wgCg4bBg/8BwMD+AgBh4ZBDQf/FIIABh4IBgAA=="));
|
||||
|
|
@ -18,6 +20,9 @@ const weatherSnowy = heatshrink.decompress(atob("iEQwYROn/8AocH8AECuAFBh0Agf+CIN
|
|||
const weatherFoggy = heatshrink.decompress(atob("iEQwYROn/8AgUB/EfwAFBh/AgfwgED/wIBuEABwd/4EcDQgFDgE4Fosf///8f//A/Lj/xCQIRNA="));
|
||||
const weatherStormy = heatshrink.decompress(atob("iEQwYLIg/gAgUB///wAFBh/AgfwgED/wIBuEAj4OCv0AjgaCh/4AoX8gE4AoQpBnAdBF4IRBDQMH/kOHgY7DAo4AOA=="));
|
||||
|
||||
const sunSetDown = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wLDg1ggfACoo"));
|
||||
const sunSetUp = heatshrink.decompress(atob("iEQwIHEgOAAocT5EGtEEkF//wRFgfAg1gBIY"));
|
||||
|
||||
let settings;
|
||||
|
||||
function loadSettings() {
|
||||
|
|
@ -29,6 +34,7 @@ function loadSettings() {
|
|||
'stepLength': 0.8,
|
||||
'batteryWarn': 30,
|
||||
'showWidgets': false,
|
||||
'weatherCircleData': 'humidity',
|
||||
'circle1': 'hr',
|
||||
'circle2': 'steps',
|
||||
'circle3': 'battery'
|
||||
|
|
@ -40,9 +46,21 @@ function loadSettings() {
|
|||
}
|
||||
}
|
||||
loadSettings();
|
||||
|
||||
|
||||
/*
|
||||
* Read location from myLocation app
|
||||
*/
|
||||
function getLocation() {
|
||||
return storage.readJSON("mylocation.json", 1) || undefined;
|
||||
}
|
||||
let location = getLocation();
|
||||
|
||||
const showWidgets = settings.showWidgets || false;
|
||||
|
||||
let hrtValue;
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
|
||||
// layout values:
|
||||
const colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
|
|
@ -64,7 +82,6 @@ const radiusOuter = 25;
|
|||
const radiusInner = 20;
|
||||
const circleFont = "Vector:15";
|
||||
const circleFontBig = "Vector:16";
|
||||
const circleFontSmall = "Vector:13";
|
||||
|
||||
function draw() {
|
||||
g.clear(true);
|
||||
|
|
@ -93,6 +110,7 @@ function draw() {
|
|||
g.setFontAlign(0, -1);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8);
|
||||
now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
// date & dow
|
||||
g.setFont("Vector:21");
|
||||
|
|
@ -127,19 +145,42 @@ function drawCircle(index) {
|
|||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
case "empty":
|
||||
// we draw nothing here
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// serves as cache for quicker lookup of circle positions
|
||||
let circlePositionsCache = [];
|
||||
/*
|
||||
* Looks in the following order if a circle with the given type is somewhere visible/configured
|
||||
* 1. circlePositionsCache
|
||||
* 2. settings
|
||||
* 3. defaultCircleTypes
|
||||
*
|
||||
* In case 2 and 3 the circlePositionsCache will be updated
|
||||
*/
|
||||
function getCirclePosition(type) {
|
||||
if (circlePositionsCache[type] >= 0) {
|
||||
return circlePosX[circlePositionsCache[type]];
|
||||
}
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const setting = settings['circle' + i];
|
||||
if (setting == type) return circlePosX[i - 1];
|
||||
if (setting == type) {
|
||||
circlePositionsCache[type] = i - 1;
|
||||
return circlePosX[i - 1];
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < defaultCircleTypes.length; i++) {
|
||||
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
|
||||
return circlePosX[i];
|
||||
}
|
||||
}
|
||||
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
|
||||
circlePositionsCache[type] = i;
|
||||
return circlePosX[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -147,16 +188,12 @@ function isCircleEnabled(type) {
|
|||
return getCirclePosition(type) != undefined;
|
||||
}
|
||||
|
||||
|
||||
function drawSteps(w) {
|
||||
if (!w) w = getCirclePosition("steps");
|
||||
const steps = getSteps();
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
drawCircleBackground(w);
|
||||
|
||||
const stepGoal = settings.stepGoal || 10000;
|
||||
if (stepGoal > 0) {
|
||||
|
|
@ -165,15 +202,9 @@ function drawSteps(w) {
|
|||
drawGauge(w, h3, percent, colorBlue);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(shortValue(steps), w + 2, h3);
|
||||
writeCircleText(w, shortValue(steps));
|
||||
|
||||
g.drawImage(shoesIcon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
|
@ -184,12 +215,7 @@ function drawStepsDistance(w) {
|
|||
const stepDistance = settings.stepLength || 0.8;
|
||||
const stepsDistance = Math.round(steps * stepDistance);
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
drawCircleBackground(w);
|
||||
|
||||
const stepDistanceGoal = settings.stepDistanceGoal || 8000;
|
||||
if (stepDistanceGoal > 0) {
|
||||
|
|
@ -198,15 +224,9 @@ function drawStepsDistance(w) {
|
|||
drawGauge(w, h3, percent, colorGreen);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(shortValue(stepsDistance), w + 2, h3);
|
||||
writeCircleText(w, shortValue(stepsDistance));
|
||||
|
||||
g.drawImage(shoesIconGreen, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
|
@ -214,28 +234,18 @@ function drawStepsDistance(w) {
|
|||
function drawHeartRate(w) {
|
||||
if (!w) w = getCirclePosition("hr");
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
drawCircleBackground(w);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
if (hrtValue != undefined && hrtValue > 0) {
|
||||
if (hrtValue != undefined) {
|
||||
const minHR = settings.minHR || 40;
|
||||
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
|
||||
const maxHR = settings.maxHR || 200;
|
||||
const percent = (hrtValue - minHR) / (maxHR - minHR);
|
||||
drawGauge(w, h3, percent, colorRed);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont(circleFontBig);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(hrtValue != undefined ? hrtValue : "-", w, h3);
|
||||
writeCircleText(w, hrtValue != undefined ? hrtValue : "-");
|
||||
|
||||
g.drawImage(heartIcon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
|
@ -244,25 +254,14 @@ function drawBattery(w) {
|
|||
if (!w) w = getCirclePosition("battery");
|
||||
const battery = E.getBattery();
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
drawCircleBackground(w);
|
||||
|
||||
if (battery > 0) {
|
||||
const percent = battery / 100;
|
||||
drawGauge(w, h3, percent, colorYellow);
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
|
||||
g.setFont(circleFont);
|
||||
g.setFontAlign(0, 0);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
let icon = powerIcon;
|
||||
let color = colorFg;
|
||||
|
|
@ -275,8 +274,7 @@ function drawBattery(w) {
|
|||
icon = powerIconRed;
|
||||
}
|
||||
}
|
||||
g.setColor(color);
|
||||
g.drawString(battery + '%', w, h3);
|
||||
writeCircleText(w, battery + '%');
|
||||
|
||||
g.drawImage(icon, w - 6, h3 + radiusOuter - 6);
|
||||
}
|
||||
|
|
@ -285,30 +283,37 @@ function drawWeather(w) {
|
|||
if (!w) w = getCirclePosition("weather");
|
||||
const weather = getWeather();
|
||||
const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
const humidity = weather ? weather.hum : undefined;
|
||||
const code = weather ? weather.code : -1;
|
||||
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
drawCircleBackground(w);
|
||||
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
|
||||
if (humidity >= 0) {
|
||||
drawGauge(w, h3, humidity / 100, colorYellow);
|
||||
const data = settings.weatherCircleData || "humidity";
|
||||
switch (data) {
|
||||
case "humidity":
|
||||
const humidity = weather ? weather.hum : undefined;
|
||||
if (humidity >= 0) {
|
||||
drawGauge(w, h3, humidity / 100, colorYellow);
|
||||
}
|
||||
break;
|
||||
case "wind":
|
||||
if (weather) {
|
||||
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
if (wind[1] >= 0) {
|
||||
if (wind[2] == "kmh") {
|
||||
wind[1] = windAsBeaufort(wind[1]);
|
||||
}
|
||||
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
drawGauge(w, h3, wind[1] / 12, colorYellow);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "empty":
|
||||
break;
|
||||
}
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
g.fillPoly([w, h3, w - 25, h3 + radiusOuter + 5, w + 25, h3 + radiusOuter + 5]);
|
||||
|
||||
const content = tempString ? tempString : "?";
|
||||
g.setFont(content.length < 4 ? circleFont : circleFontSmall);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(content, w, h3);
|
||||
writeCircleText(w, tempString ? tempString : "?");
|
||||
|
||||
if (code > 0) {
|
||||
const icon = getWeatherIconByCode(code);
|
||||
|
|
@ -316,6 +321,69 @@ function drawWeather(w) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function drawSunProgress(w) {
|
||||
if (!w) w = getCirclePosition("sunprogress");
|
||||
const percent = getSunProgress();
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
drawGauge(w, h3, percent, colorYellow);
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
let icon = powerIcon;
|
||||
let color = colorFg;
|
||||
if (isDay()) {
|
||||
// day
|
||||
color = colorFg;
|
||||
icon = sunSetDown;
|
||||
} else {
|
||||
// night
|
||||
color = colorGrey;
|
||||
icon = sunSetUp;
|
||||
}
|
||||
g.setColor(color);
|
||||
|
||||
let text = "?";
|
||||
const times = getSunData();
|
||||
if (times != undefined) {
|
||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
if (!isDay()) {
|
||||
// night
|
||||
if (now > sunRise) {
|
||||
// after sunRise
|
||||
const upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
text = formatSeconds(upcomingSunRise - now);
|
||||
} else {
|
||||
text = formatSeconds(sunRise - now);
|
||||
}
|
||||
} else {
|
||||
// day, approx sunrise tomorrow:
|
||||
text = formatSeconds(sunSet - now);
|
||||
}
|
||||
}
|
||||
|
||||
writeCircleText(w, text);
|
||||
|
||||
g.drawImage(icon, w - 6, h3 + radiusOuter - 6);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
*/
|
||||
function windAsBeaufort(windInKmh) {
|
||||
const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118];
|
||||
let l = 0;
|
||||
while (l < beaufort.length && beaufort[l] < windInKmh) {
|
||||
l++;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Choose weather icon to display based on weather conditition code
|
||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||
|
|
@ -350,7 +418,7 @@ function getWeatherIconByCode(code) {
|
|||
case 8:
|
||||
switch (code) {
|
||||
case 800:
|
||||
return weatherSunny;
|
||||
return isDay() ? weatherSunny : weatherMoon;
|
||||
case 801:
|
||||
return weatherPartlyCloudy;
|
||||
case 802:
|
||||
|
|
@ -365,32 +433,122 @@ function getWeatherIconByCode(code) {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
function isDay() {
|
||||
const times = getSunData();
|
||||
if (times == undefined) return true;
|
||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
|
||||
return (now > sunRise && now < sunSet);
|
||||
}
|
||||
|
||||
function formatSeconds(s) {
|
||||
if (s > 60 * 60) { // hours
|
||||
return Math.round(s / (60 * 60)) + "h";
|
||||
}
|
||||
if (s > 60) { // minutes
|
||||
return Math.round(s / 60) + "m";
|
||||
}
|
||||
return "<1m";
|
||||
}
|
||||
|
||||
function getSunData() {
|
||||
if (location != undefined && location.lat != undefined) {
|
||||
// get today's sunlight times for lat/lon
|
||||
return SunCalc.getTimes(new Date(), location.lat, location.lon);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculated progress of the sun between sunrise and sunset in percent
|
||||
*
|
||||
* Taken from rebble app and modified
|
||||
*/
|
||||
function getSunProgress() {
|
||||
const times = getSunData();
|
||||
if (times == undefined) return 0;
|
||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
|
||||
if (isDay()) {
|
||||
// during day
|
||||
const dayLength = sunSet - sunRise;
|
||||
if (now > sunRise) {
|
||||
return (now - sunRise) / dayLength;
|
||||
} else {
|
||||
return (sunRise - now) / dayLength;
|
||||
}
|
||||
} else {
|
||||
// during night
|
||||
if (sunSet < sunRise) {
|
||||
const upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
return 1 - (upcomingSunRise - now) / (upcomingSunRise - sunSet);
|
||||
} else {
|
||||
const lastSunSet = sunSet - 60 * 60 * 24;
|
||||
return (now - lastSunSet) / (sunRise - lastSunSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draws the background and the grey circle
|
||||
*/
|
||||
function drawCircleBackground(w) {
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
// Draw grey background circle:
|
||||
g.setColor(colorGrey);
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
}
|
||||
|
||||
function drawInnerCircleAndTriangle(w) {
|
||||
// Draw inner circle
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
// Draw triangle which covers the bottom of the circle
|
||||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
}
|
||||
|
||||
function radians(a) {
|
||||
return a * Math.PI / 180;
|
||||
}
|
||||
|
||||
/*
|
||||
* This draws the actual gauge consisting out of lots of little filled circles
|
||||
*/
|
||||
function drawGauge(cx, cy, percent, color) {
|
||||
const offset = 15;
|
||||
const end = 345;
|
||||
const r = radiusInner + 3;
|
||||
const radius = radiusInner + 3;
|
||||
const size = radiusOuter - radiusInner - 2;
|
||||
|
||||
if (percent <= 0) return;
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
const startrot = -offset;
|
||||
const endrot = startrot - ((end - offset) * percent);
|
||||
const startRotation = -offset;
|
||||
const endRotation = startRotation - ((end - offset) * percent);
|
||||
|
||||
g.setColor(color);
|
||||
|
||||
const size = radiusOuter - radiusInner - 2;
|
||||
// draw gauge
|
||||
for (let i = startrot; i > endrot - size; i -= size) {
|
||||
x = cx + r * Math.sin(radians(i));
|
||||
y = cy + r * Math.cos(radians(i));
|
||||
for (let i = startRotation; i > endRotation - size; i -= size) {
|
||||
x = cx + radius * Math.sin(radians(i));
|
||||
y = cy + radius * Math.cos(radians(i));
|
||||
g.fillCircle(x, y, size);
|
||||
}
|
||||
}
|
||||
|
||||
function writeCircleText(w, content) {
|
||||
if (content == undefined) return;
|
||||
g.setFont(content.length < 4 ? circleFontBig : circleFont);
|
||||
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(content, w, h3);
|
||||
}
|
||||
|
||||
function shortValue(v) {
|
||||
if (isNaN(v)) return '-';
|
||||
if (v <= 999) return v;
|
||||
|
|
@ -405,6 +563,9 @@ function shortValue(v) {
|
|||
}
|
||||
|
||||
function getSteps() {
|
||||
if (Bangle.getHealthStatus) {
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
if (WIDGETS && WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.06",
|
||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"circlesclock.app.js","url":"app.js"},
|
||||
{"name":"circlesclock.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"circlesclock.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"circlesclock.json"}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,8 +6,12 @@
|
|||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
var valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather"];
|
||||
var namesCircleTypes = ["steps", "distance", "heart", "battery", "weather"];
|
||||
|
||||
const valuesCircleTypes = ["steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "empty"];
|
||||
const namesCircleTypes = ["steps", "distance", "heart", "battery", "weather", "sun progress", "empty"];
|
||||
|
||||
const weatherData = ["humidity", "wind", "empty"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'circlesclock' },
|
||||
'< Back': back,
|
||||
|
|
@ -76,21 +80,27 @@
|
|||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: x => save('showWidgets', x),
|
||||
},
|
||||
'weather circle': {
|
||||
value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 0,
|
||||
min: 0, max: 2,
|
||||
format: v => weatherData[v],
|
||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
||||
},
|
||||
'left': {
|
||||
value: settings.circle1 ? valuesCircleTypes.indexOf(settings.circle1) : 0,
|
||||
min: 0, max: 4,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle1', valuesCircleTypes[x]),
|
||||
},
|
||||
'middle': {
|
||||
value: settings.circle2 ? valuesCircleTypes.indexOf(settings.circle2) : 2,
|
||||
min: 0, max: 4,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle2', valuesCircleTypes[x]),
|
||||
},
|
||||
'right': {
|
||||
value: settings.circle3 ? valuesCircleTypes.indexOf(settings.circle3) : 3,
|
||||
min: 0, max: 4,
|
||||
min: 0, max: 6,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save('circle3', valuesCircleTypes[x]),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "clickms",
|
||||
"name": "Click Master",
|
||||
"version": "0.01",
|
||||
"description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
|
||||
"icon": "click-master.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"storage": [
|
||||
{"name":"clickms.app.js","url":"click-master.js"},
|
||||
{"name":"clickms.img","url":"click-master-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "cliclockJS2Enhanced",
|
||||
"name": "Commandline-Clock JS2 Enhanced",
|
||||
"shortName": "CLI-Clock JS2",
|
||||
"version": "0.03",
|
||||
"description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screengrab.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock,cli,command,bash,shell",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"cliclockJS2Enhanced.app.js","url":"app.js"},
|
||||
{"name":"cliclockJS2Enhanced.img","url":"app.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "clicompleteclk",
|
||||
"name": "CLI complete clock",
|
||||
"shortName":"CLI cmplt clock",
|
||||
"version":"0.03",
|
||||
"description": "Command line styled clock with lots of information",
|
||||
"icon": "app.png",
|
||||
"allow_emulator": true,
|
||||
"type": "clock",
|
||||
"tags": "clock,cli,command,bash,shell,weather,hrt",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"clicompleteclk.app.js","url":"app.js"},
|
||||
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"clicompleteclk.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"clicompleteclk.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "cliock",
|
||||
"name": "Commandline-Clock",
|
||||
"shortName": "CLI-Clock",
|
||||
"version": "0.15",
|
||||
"description": "Simple CLI-Styled Clock",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_cli.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock,cli,command,bash,shell",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"cliock.app.js","url":"app.js"},
|
||||
{"name":"cliock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "clock2x3",
|
||||
"name": "2x3 Pixel Clock",
|
||||
"version": "0.05",
|
||||
"description": "This is a simple clock using minimalist 2x3 pixel numerical digits",
|
||||
"icon": "clock2x3.png",
|
||||
"screenshots": [{"url":"screenshot_pixel.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"clock2x3.app.js","url":"clock2x3-app.js"},
|
||||
{"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "clotris",
|
||||
"name": "Clock-Tris",
|
||||
"version": "0.01",
|
||||
"description": "A fully functional clone of a classic game of falling blocks",
|
||||
"icon": "clock-tris.png",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"bangle1-clock-tris-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"clotris.app.js","url":"clock-tris.js"},
|
||||
{"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true},
|
||||
{"name":".trishig","url":"clock-tris-high"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "color_catalog",
|
||||
"name": "Colors Catalog",
|
||||
"shortName": "Colors Catalog",
|
||||
"version": "0.01",
|
||||
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
|
||||
"icon": "app.png",
|
||||
"tags": "Color,input,buttons,touch,UI",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"color_catalog.app.js","url":"app.js"},
|
||||
{"name":"color_catalog.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "colorful_clock",
|
||||
"name": "Colorful Analog Clock",
|
||||
"shortName":"Colorful Clock",
|
||||
"version":"0.03",
|
||||
"description": "a colorful analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"colorful_clock.app.js","url":"app.js"},
|
||||
{"name":"colorful_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id":"colorwheel",
|
||||
"name":"Color Wheel",
|
||||
"tags":"app,tool",
|
||||
"version":"0.01",
|
||||
"description":"a tappable wheel of good-looking colors",
|
||||
"readme":"README.md",
|
||||
"supports":["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"icon":"colorwheel.png",
|
||||
"storage": [
|
||||
{"name":"colorwheel.app.js","url":"app.js"},
|
||||
{"name":"colorwheel.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "compass",
|
||||
"name": "Compass",
|
||||
"version": "0.05",
|
||||
"description": "Simple compass that points North",
|
||||
"icon": "compass.png",
|
||||
"screenshots": [{"url":"screenshot_compass.png"}],
|
||||
"tags": "tool,outdoors",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"compass.app.js","url":"compass.js"},
|
||||
{"name":"compass.img","url":"compass-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "configurable_clock",
|
||||
"name": "Configurable Analog Clock",
|
||||
"shortName":"Configurable Clock",
|
||||
"version":"0.02",
|
||||
"description": "an analog clock with several kinds of faces, hands and colors to choose from",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"configurable_clock.app.js","url":"app.js"},
|
||||
{"name":"configurable_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.01",
|
||||
"icon": "app.png",
|
||||
"description": "A Minimalist clockface with large Digits. Looks best with the dark theme",
|
||||
"screenshots" : [{"url":"screenshot.png"}],
|
||||
"tags": "clock",
|
||||
"allow_emulator":true,
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"contourclock.app.js","url":"app.js"},
|
||||
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"id": "coretemp",
|
||||
"name": "CoreTemp",
|
||||
"version": "0.03",
|
||||
"description": "Display CoreTemp device sensor data",
|
||||
"icon": "coretemp.png",
|
||||
"type": "app",
|
||||
"tags": "health",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"coretemp.wid.js","url":"widget.js"},
|
||||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||
{"name":"coretemp.recorder.js","url":"recorder.js"},
|
||||
{"name":"coretemp.settings.js","url":"settings.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
|
||||
{"name":"coretemp.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
|
||||
"screenshots": [{"url":"screenshot.png"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "countdowntimer",
|
||||
"name": "Countdown Timer",
|
||||
"version": "0.01",
|
||||
"description": "A simple countdown timer with a focus on usability",
|
||||
"icon": "countdowntimer.png",
|
||||
"tags": "timer,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"countdowntimer.app.js","url":"countdowntimer.js"},
|
||||
{"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "counter",
|
||||
"name": "Counter",
|
||||
"version": "0.03",
|
||||
"description": "Simple counter",
|
||||
"icon": "counter_icon.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"screenshots": [{"url":"bangle1-counter-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"counter.app.js","url":"counter.js"},
|
||||
{"name":"counter.img","url":"counter-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue