Merge branch 'espruino:master' into master

master
Andrew Gregory 2022-01-21 19:56:16 +08:00 committed by GitHub
commit 0f3be07fe6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
492 changed files with 8880 additions and 6608 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ appdates.csv
_config.yml
tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp
apps.local.json

View File

@ -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

5698
apps.json

File diff suppressed because it is too large Load Diff

View File

@ -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}]
}

17
apps/93dub/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

15
apps/HRV/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -1,4 +1,3 @@
// Create an entry in apps.json as follows:
{ "id": "7chname",
"name": "My app's human readable name",
"shortName":"Short Name",

View File

@ -1,4 +1,3 @@
// Create an entry in apps.json as follows:
{ "id": "7chname",
"name": "My widget's human readable name",
"shortName":"Short Name",

View File

@ -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}
]
}

View File

@ -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}
]
}

17
apps/about/metadata.json Normal file
View File

@ -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
}

View File

@ -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>

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

18
apps/ac_ac/metadata.json Normal file
View File

@ -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}
]
}

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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}
]
}

View File

@ -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"}]
}

View File

@ -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"}]
}

16
apps/aclock/metadata.json Normal file
View File

@ -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}
]
}

2
apps/acmaze/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Faster maze generation

17
apps/acmaze/README.md Normal file
View File

@ -0,0 +1,17 @@
# AccelaMaze
Tilt the watch to roll a ball through a maze.
![Screenshot](screenshot.png)
## 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)

1
apps/acmaze/app-icon.js Normal file
View File

@ -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"))

295
apps/acmaze/app.js Normal file
View File

@ -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);

BIN
apps/acmaze/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

15
apps/acmaze/metadata.json Normal file
View File

@ -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}
]
}

BIN
apps/acmaze/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -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"}
]
}

18
apps/alarm/metadata.json Normal file
View File

@ -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"}]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

15
apps/andark/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

15
apps/arrow/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{
"id": "arrow",
"name": "Arrow Compass",
"version": "0.05",
"description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
"icon": "arrow.png",
"type": "app",
"tags": "tool,outdoors",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"arrow.app.js","url":"app.js"},
{"name":"arrow.img","url":"icon.js","evaluate":true}
]
}

View File

@ -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": []
}

15
apps/astral/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

View File

@ -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}
]
}

View File

@ -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"}
]
}

View File

@ -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}
]
}

View File

@ -1 +1,4 @@
0.01: New App!
0.02: Add sit ups
Add more feedback to the user about the exercises
Clean up code

View File

@ -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

View File

@ -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() {

View File

@ -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"}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

16
apps/bclock/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

14
apps/beer/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

16
apps/boot/metadata.json Normal file
View File

@ -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
}

View File

@ -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"}
]
}

16
apps/breath/metadata.json Normal file
View File

@ -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"}]
}

19
apps/bthrm/metadata.json Normal file
View File

@ -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"}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

View File

@ -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"}]
}

View File

@ -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}
]
}

16
apps/choozi/metadata.json Normal file
View File

@ -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}
]
}

14
apps/chrono/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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

View File

@ -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
![Screenshot dark theme](screenshot-dark.png)
![Screenshot light theme](screenshot-light.png)
# TODO
* Add sunrise and sunset
* Display moon instead of sun during night on weather circle
## Creator
Marco ([myxor](https://github.com/myxor))

View File

@ -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();
}

View File

@ -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"}
]
}

View File

@ -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]),
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

17
apps/cliock/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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}
]
}

View File

@ -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"}]
}

View File

@ -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}
]
}

View File

@ -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