diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b4716aa3a --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +Copyright 2019 Gordon Williams, Pur3 Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + diff --git a/README.md b/README.md index 79708bd76..ee1173ffe 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,16 @@ Bangle.js App Loader (and Apps) Try it live at [banglejs.com/apps](https://banglejs.com/apps) -### How does it work? +## How does it work? * A list of apps is in `apps.json` -* Each element references an app in `apps/` which is uploaded +* Each element references an app in `apps/` which is uploaded * When it starts, BangleAppLoader checks the JSON and compares it with the files it sees in the watch's storage. * To upload an app, BangleAppLoader checks the files that are listed in `apps.json`, loads them, and sends them over Web Bluetooth. -### What filenames are used +## What filenames are used Filenames in storage are limited to 8 characters. To easily distinguish between file types, we use the following: @@ -22,15 +22,21 @@ easily distinguish between file types, we use the following: * `-stuff` is JS code * `=stuff` is JS code for stuff that is run at boot time - eg. handling settings or creating widgets on the clock screen -### Developing your own app +## Developing your own app + +* Head over to [the Web IDE](https://www.espruino.com/ide/) and ensure `Save on Send` in settings set to the *default setting* of `To RAM` +* We'd recommend that you start off using code from 'Example Applications' (below) to get started... +* Load [`app.js`](apps/_example_app/app.js) or [`widget.js`](apps/_example_widget/widget.js) into the IDE and start developing. +* The `Upload` button will load your app to Bangle.js temporarily + +## Adding your app to the menu -* Start writing your code in the IDE, with `Save on Send` in settings set to -the *default* of `To RAM` -* When you have your app as you want it, add it as a file in `apps/`, lets assume `apps/my-great-app.js` * Come up with a unique 7 character name, we'll assume `7chname` -* Create `apps/my-great-app.png` as a 48px icon -* Use http://www.espruino.com/Image+Converter to create as 1 bit, 4 bit or 8 bit Web Palette "Image String" and save it as `apps/my-great-app-icon.js` -* Create an entry in `apps/my-great-app.json` as follows: +* Create a folder called `apps/`, lets assume `apps/7chname` +* We'd recommend that you copy files from 'Example Applications' (below) as a base, or... +* `apps/7chname/app.png` should be a 48px icon +* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String" +* Create an entry in `apps/7chname/app.json` as follows: ``` { @@ -40,23 +46,117 @@ the *default* of `To RAM` } ``` -* Create an entry in `apps.json` as follows: +* Create an entry in `apps.json` as follows: ``` { "id": "7chname", "name": "My app's human readable name", - "icon": "my-great-app.png", + "icon": "app.png", "description": "A detailed description of my great app", "tags": "", "storage": [ - {"name":"+7chname","url":"my-great-app.json"}, - {"name":"-7chname","url":"my-great-app.js"}, - {"name":"*7chname","url":"my-great-app.js","evaluate":true} + {"name":"+7chname","url":"app.json"}, + {"name":"-7chname","url":"app.js"}, + {"name":"*7chname","url":"app-icon.js","evaluate":true} ], }, ``` -### `apps.json` format +## Testing + +### Online + +This is the best way to test... + +* Fork the https://github.com/espruino/BangleApps git repository +* Add your files +* Go to GitHub Settings and activate GitHub Pages +* Run your personal `Bangle App Loader` at https://\.github.io/BangleApps/index.html to load apps onto your device +* Your apps should be inside it - if there are problems, check your web browser's 'developer console' for errors + +Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect. + +### Offline + +You can add the following to the Espruino Web IDE: + +``` +// replace with your 7chname app name +var appname = "mygreat"; + +require("Storage").write('*'+appname, + // place app-icon.js contents here +); + +// +require("Storage").write("+"+appname,{ + "name":"My Great App","type":"", + "icon":"*"+appname, + "src":"-"+appname, +}); + +require("Storage").write("-"+appname,` +// place contents of app.js here +// be aware of double-quoting templated strings +` +``` + +When you upload code this way, your app will be uploaded to Bangle.js's menu +without you having to use the `Bangle App Loader` + +## Example Applications + +To make the process easier we've come up with some example applications that you can use as a base +when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app` +or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to +`apps.json`. + +### App Example + +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 widget 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.json` - short app name for Bangle.js menu and storage filenames +* `app.js` - app code + +#### `app-icon.js` + +The icon image and short description is used in the menu entry as selection posibility. + +Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file. + +Follow this steps to create a readable icon as image string. + +1. upload a png file +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_ +6. set Output as: _Image String_ + +Replace this line with the image converter output: + +``` +require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")); +``` + +Keep in mind to use this converter for creating images you like to draw with `g.drawImage()` with your app. + + +### Widget Example + +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 +* `widget.json` - short widget name and storage names +* `widget.js` - widget code + + +## `apps.json` format ``` { "id": "appid", // 7 character app id @@ -84,6 +184,87 @@ the *default* of `To RAM` } ``` -### Credits +* name, icon and description present the app in the app loader. +* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget` or empty. +* storage is used to identify the app files and how to handle them + +## Coding hints + +- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24" + +- use `g.drawString(text,x,y,true)` to draw with background color to overwrite existing text + +- use `g.clearRect()` to clear parts of the screen, instead of using `g.clear()` + +- use `g.fillPoly()` or `g.drawImage()` for complex graphic elements + +- using `g.clear()` can cause screen flicker + +- using `g.setLCDBrightness()` can save you power during long periods with lcd on + +- chaining graphics methodes, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)` + +### Graphic areas + +The screen is parted in a widget and app area for lcd mode `direct`(default). + +| areas | as rectangle or point | +| :-:| :-: | +| Widget | (0,0,239,23) | +| Apps | (0,24,239,239) | +| BTN1 | (230, 55) | +| BTN2 | (230, 140) | +| BTN3 | (230, 210) | +| BTN4 | (0,0,119, 239)| +| BTN5 | (120,0,239,239) | + +- Use `g.setFontAlign(0, 0, 3)` to draw rotated string to BTN1-BTN3 with `g.drawString()`. + +- For BTN4-5 the touch area is named + +## Available colors + +Yuu can use `g.setColor(r,g,b)` OR `g.setColor(16bitnumber)` - some common 16 bit colors are below: + +| color-name | color-value| +| :-: | :-: | +| Black | 0x0000 | +| Navy | 0x000F | +| DarkGreen | 0x03E0 | +| DarkCyan | 0x03EF | +| Maroon | 0x7800 | +| Purple | 0x780F | +| Olive | 0x7BE0 +| LightGray | 0xC618 +| DarkGrey | 0x7BEF +| Blue | 0x001F +| Green | 0x07E0 | +| Cyan | 0x07FF | +| RED | 0xF800 | +| Magenta | 0xF81F | +| Yellow | 0xFFE0 | +| White | 0xFFFF | +| Orange | 0xFD20 | +| GreenYellow | 0xAFE5 | +| Pink | 0xF81F | + +## API Reference + +[Reference](http://www.espruino.com/Reference#software) + +[Bangle Class](https://banglejs.com/reference#Bangle) + +[Graphics Class](https://banglejs.com/reference#Graphics) + + +## 'Testing' folder + +The [`testing`](testing) folder contains snippets of code that might be useful for your apps. + +* `testing/colors.js` - 16 bit colors as name value pairs +* `testing/gpstrack.js` - code to store a GPS track in Bagle.js storage and output it back to the console +* `testing/map` - code for splitting an image into map tiles and then displaying them + +## Credits The majority of icons used for these apps are from [Icons8](https://icons8.com/) - we have a commercial license but icons are also free for Open Source projects. diff --git a/appinfo.js b/appinfo.js index 5efcfde50..6fa4aa474 100644 --- a/appinfo.js +++ b/appinfo.js @@ -10,7 +10,7 @@ var AppInfo = { if (storageFile.content) return Promise.resolve(storageFile); else if (storageFile.url) - return fileGetter("apps/"+storageFile.url).then(content => { + return fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => { return { name : storageFile.name, content : content, diff --git a/apps.json b/apps.json index 32dcb6375..b3b0d8da0 100644 --- a/apps.json +++ b/apps.json @@ -502,5 +502,30 @@ {"name": "-gpsinfo","url": "gps-info.js"}, {"name": "*gpsinfo","url": "gps-info-icon.js","evaluate": true} ] + }, + { + "id": "promodo", + "name":"Promodoro", + "icon":"promodoro.png", + "description": "A simple promodoro timer.", + "tags": "promodoro,cooking,tools", + "type": "app", + "storage": [ + {"name": "+promodo","url": "promodoro.json"}, + {"name": "-promodo","url": "promodoro.js"}, + {"name": "*promodo","url": "promodoro-icon.js","evaluate": true} + ] +}, + { "id": "blobclk", + "name": "Large Digit Clock", + "icon": "clock-blob.png", + "description": "A clock with big digits", + "tags": "clock", + "type":"clock", + "storage": [ + {"name":"+blobclk","url":"clock-blob.json"}, + {"name":"-blobclk","url":"clock-blob.js"}, + {"name":"*blobclk","url":"clock-blob-icon.js","evaluate":true} + ] } ] \ No newline at end of file diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/add_to_apps.json new file mode 100644 index 000000000..941880a65 --- /dev/null +++ b/apps/_example_app/add_to_apps.json @@ -0,0 +1,12 @@ +// Create an entry in apps.json as follows: +{ "id": "7chname", + "name": "My app's human readable name", + "icon": "app.png", + "description": "A detailed description of my great app", + "tags": "", + "storage": [ + {"name":"+7chname","url":"app.json"}, + {"name":"-7chname","url":"app.js"}, + {"name":"*7chname","url":"app-icon.js","evaluate":true} + ], +} diff --git a/apps/_example_app/app-icon.js b/apps/_example_app/app-icon.js new file mode 100644 index 000000000..52a11577f --- /dev/null +++ b/apps/_example_app/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")); diff --git a/apps/_example_app/app.js b/apps/_example_app/app.js new file mode 100644 index 000000000..0db3abdf9 --- /dev/null +++ b/apps/_example_app/app.js @@ -0,0 +1,15 @@ +(() => { + + // place your const, vars, functions or classes here + + + // special function to handle display switch on + Bangle.on('lcdPower', (on) => { + if (on) { + // call your app function here + }}); + + g.clear(); + // call your app function here + +})(); diff --git a/apps/_example_app/app.json b/apps/_example_app/app.json new file mode 100644 index 000000000..65168c5a1 --- /dev/null +++ b/apps/_example_app/app.json @@ -0,0 +1,5 @@ +{ + "name":"Short Name", + "icon":"*7chname", + "src":"-7chname" +} diff --git a/apps/_example_app/app.png b/apps/_example_app/app.png new file mode 100644 index 000000000..582cb2e08 Binary files /dev/null and b/apps/_example_app/app.png differ diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/add_to_apps.json new file mode 100644 index 000000000..9bc4a8e6e --- /dev/null +++ b/apps/_example_widget/add_to_apps.json @@ -0,0 +1,11 @@ +// Create an entry in apps.json as follows: +{ "id": "7chname", + "name": "My widget's human readable name", + "icon": "widget.png", + "description": "A detailed description of my great widget", + "tags": "", + "storage": [ + {"name":"+7chname","url":"widget.json"}, + {"name":"-7chname","url":"widget.js"}, + ], +} diff --git a/apps/_example_widget/widget.js b/apps/_example_widget/widget.js new file mode 100644 index 000000000..9606125cd --- /dev/null +++ b/apps/_example_widget/widget.js @@ -0,0 +1,17 @@ +(() => { + + // add the width + var xpos = WIDGETPOS.tr-; + WIDGETPOS.tr-=; + + // draw your widget at xpos + function draw() { + + // add your code + + } + + // add your widget + WIDGETS["mywidget"]={draw:draw}; + +})() \ No newline at end of file diff --git a/apps/_example_widget/widget.json b/apps/_example_widget/widget.json new file mode 100644 index 000000000..239e18c77 --- /dev/null +++ b/apps/_example_widget/widget.json @@ -0,0 +1,4 @@ +{ + "name":"widgetname", "type":"widget", + "src":"-7chname" +} diff --git a/apps/_example_widget/widget.png b/apps/_example_widget/widget.png new file mode 100644 index 000000000..582cb2e08 Binary files /dev/null and b/apps/_example_widget/widget.png differ diff --git a/apps/clock-analog-icon.js b/apps/aclock/clock-analog-icon.js similarity index 100% rename from apps/clock-analog-icon.js rename to apps/aclock/clock-analog-icon.js diff --git a/apps/clock-analog.js b/apps/aclock/clock-analog.js similarity index 100% rename from apps/clock-analog.js rename to apps/aclock/clock-analog.js diff --git a/apps/clock-analog.json b/apps/aclock/clock-analog.json similarity index 100% rename from apps/clock-analog.json rename to apps/aclock/clock-analog.json diff --git a/apps/clock-analog.png b/apps/aclock/clock-analog.png similarity index 100% rename from apps/clock-analog.png rename to apps/aclock/clock-analog.png diff --git a/apps/animals-camel.js b/apps/animals/animals-camel.js similarity index 100% rename from apps/animals-camel.js rename to apps/animals/animals-camel.js diff --git a/apps/animals-duck.js b/apps/animals/animals-duck.js similarity index 100% rename from apps/animals-duck.js rename to apps/animals/animals-duck.js diff --git a/apps/animals-fox.js b/apps/animals/animals-fox.js similarity index 100% rename from apps/animals-fox.js rename to apps/animals/animals-fox.js diff --git a/apps/animals-icon.js b/apps/animals/animals-icon.js similarity index 100% rename from apps/animals-icon.js rename to apps/animals/animals-icon.js diff --git a/apps/animals-mouse.js b/apps/animals/animals-mouse.js similarity index 100% rename from apps/animals-mouse.js rename to apps/animals/animals-mouse.js diff --git a/apps/animals-pig.js b/apps/animals/animals-pig.js similarity index 100% rename from apps/animals-pig.js rename to apps/animals/animals-pig.js diff --git a/apps/animals-sheep.js b/apps/animals/animals-sheep.js similarity index 100% rename from apps/animals-sheep.js rename to apps/animals/animals-sheep.js diff --git a/apps/animals-snake.js b/apps/animals/animals-snake.js similarity index 100% rename from apps/animals-snake.js rename to apps/animals/animals-snake.js diff --git a/apps/animals-swan.js b/apps/animals/animals-swan.js similarity index 100% rename from apps/animals-swan.js rename to apps/animals/animals-swan.js diff --git a/apps/animals.js b/apps/animals/animals.js similarity index 100% rename from apps/animals.js rename to apps/animals/animals.js diff --git a/apps/animals.json b/apps/animals/animals.json similarity index 100% rename from apps/animals.json rename to apps/animals/animals.json diff --git a/apps/animals.png b/apps/animals/animals.png similarity index 100% rename from apps/animals.png rename to apps/animals/animals.png diff --git a/apps/asteroids-icon.js b/apps/astroid/asteroids-icon.js similarity index 100% rename from apps/asteroids-icon.js rename to apps/astroid/asteroids-icon.js diff --git a/apps/asteroids.js b/apps/astroid/asteroids.js similarity index 100% rename from apps/asteroids.js rename to apps/astroid/asteroids.js diff --git a/apps/asteroids.json b/apps/astroid/asteroids.json similarity index 100% rename from apps/asteroids.json rename to apps/astroid/asteroids.json diff --git a/apps/asteroids.png b/apps/astroid/asteroids.png similarity index 100% rename from apps/asteroids.png rename to apps/astroid/asteroids.png diff --git a/apps/clock-binary-icon.js b/apps/bclock/clock-binary-icon.js similarity index 100% rename from apps/clock-binary-icon.js rename to apps/bclock/clock-binary-icon.js diff --git a/apps/clock-binary.js b/apps/bclock/clock-binary.js similarity index 100% rename from apps/clock-binary.js rename to apps/bclock/clock-binary.js diff --git a/apps/clock-binary.json b/apps/bclock/clock-binary.json similarity index 100% rename from apps/clock-binary.json rename to apps/bclock/clock-binary.json diff --git a/apps/clock-binary.png b/apps/bclock/clock-binary.png similarity index 100% rename from apps/clock-binary.png rename to apps/bclock/clock-binary.png diff --git a/apps/beercompass.html b/apps/beer/beercompass.html similarity index 99% rename from apps/beercompass.html rename to apps/beer/beercompass.html index fa7b9941d..ba3988aec 100644 --- a/apps/beercompass.html +++ b/apps/beer/beercompass.html @@ -1,7 +1,7 @@ - + - + diff --git a/apps/route.png b/apps/route/route.png similarity index 100% rename from apps/route.png rename to apps/route/route.png diff --git a/apps/widget-battery.js b/apps/sbat/widget-battery.js similarity index 100% rename from apps/widget-battery.js rename to apps/sbat/widget-battery.js diff --git a/apps/widget-battery.json b/apps/sbat/widget-battery.json similarity index 100% rename from apps/widget-battery.json rename to apps/sbat/widget-battery.json diff --git a/apps/widget-battery.png b/apps/sbat/widget-battery.png similarity index 100% rename from apps/widget-battery.png rename to apps/sbat/widget-battery.png diff --git a/apps/widget-bluetooth.js b/apps/sbt/widget-bluetooth.js similarity index 100% rename from apps/widget-bluetooth.js rename to apps/sbt/widget-bluetooth.js diff --git a/apps/widget-bluetooth.json b/apps/sbt/widget-bluetooth.json similarity index 100% rename from apps/widget-bluetooth.json rename to apps/sbt/widget-bluetooth.json diff --git a/apps/widget-bluetooth.png b/apps/sbt/widget-bluetooth.png similarity index 100% rename from apps/widget-bluetooth.png rename to apps/sbt/widget-bluetooth.png diff --git a/apps/clock-simple-icon.js b/apps/sclock/clock-simple-icon.js similarity index 100% rename from apps/clock-simple-icon.js rename to apps/sclock/clock-simple-icon.js diff --git a/apps/clock-simple.js b/apps/sclock/clock-simple.js similarity index 100% rename from apps/clock-simple.js rename to apps/sclock/clock-simple.js diff --git a/apps/clock-simple.json b/apps/sclock/clock-simple.json similarity index 100% rename from apps/clock-simple.json rename to apps/sclock/clock-simple.json diff --git a/apps/clock-simple.png b/apps/sclock/clock-simple.png similarity index 100% rename from apps/clock-simple.png rename to apps/sclock/clock-simple.png diff --git a/apps/show-color-icon.js b/apps/scolor/show-color-icon.js similarity index 100% rename from apps/show-color-icon.js rename to apps/scolor/show-color-icon.js diff --git a/apps/show-color.js b/apps/scolor/show-color.js similarity index 100% rename from apps/show-color.js rename to apps/scolor/show-color.js diff --git a/apps/show-color.json b/apps/scolor/show-color.json similarity index 100% rename from apps/show-color.json rename to apps/scolor/show-color.json diff --git a/apps/show-color.png b/apps/scolor/show-color.png similarity index 100% rename from apps/show-color.png rename to apps/scolor/show-color.png diff --git a/apps/settings-default.json b/apps/setting/settings-default.json similarity index 100% rename from apps/settings-default.json rename to apps/setting/settings-default.json diff --git a/apps/settings-icon.js b/apps/setting/settings-icon.js similarity index 100% rename from apps/settings-icon.js rename to apps/setting/settings-icon.js diff --git a/apps/settings-init.js b/apps/setting/settings-init.js similarity index 100% rename from apps/settings-init.js rename to apps/setting/settings-init.js diff --git a/apps/settings.js b/apps/setting/settings.js similarity index 86% rename from apps/settings.js rename to apps/setting/settings.js index ae14a53bc..29af0b029 100644 --- a/apps/settings.js +++ b/apps/setting/settings.js @@ -27,6 +27,7 @@ function resetSettings() { HID : false, HIDGestures: false, debug: false, + clock: null }; setLCDTimeout(settings.timeout); updateSettings(); @@ -92,6 +93,7 @@ function showMainMenu() { } } }, + 'Select Clock': showClockMenu, 'Time Zone': { value: settings.timezone, min: -11, @@ -174,6 +176,37 @@ function makeConnectable() { showMainMenu(); }); } +function showClockMenu() { + var clockApps = require("Storage").list().filter(a=>a[0]=='+').map(app=>{ + try { return require("Storage").readJSON(app); } + catch (e) {} + }).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder); + const clockMenu = { + '': { + 'title': 'Select Clock', + }, + '< Back': showMainMenu, + }; + clockApps.forEach((app,index) => { + var label = app.name; + if ((!settings.clock && index === 0) || (settings.clock === app.src)) { + label = "* "+label; + } + clockMenu[label] = () => { + if (settings.clock !== app.src) { + settings.clock = app.src; + updateSettings(); + showMainMenu(); + } + }; + }); + if (clockApps.length === 0) { + clockMenu["No Clocks Found"] = () => {}; + } + return Bangle.menu(clockMenu); +} + + function showSetTimeMenu() { d = new Date(); @@ -248,8 +281,8 @@ function showSetTimeMenu() { }, 'Year': { value: d.getFullYear(), - min: d.getFullYear() - 10, - max: d.getFullYear() + 10, + min: 2019, + max: 2100, step: 1, onchange: v => { d = new Date(); diff --git a/apps/settings.json b/apps/setting/settings.json similarity index 100% rename from apps/settings.json rename to apps/setting/settings.json diff --git a/apps/settings.png b/apps/setting/settings.png similarity index 100% rename from apps/settings.png rename to apps/setting/settings.png diff --git a/apps/spiritlevel-icon.js b/apps/slevel/spiritlevel-icon.js similarity index 100% rename from apps/spiritlevel-icon.js rename to apps/slevel/spiritlevel-icon.js diff --git a/apps/spiritlevel.js b/apps/slevel/spiritlevel.js similarity index 100% rename from apps/spiritlevel.js rename to apps/slevel/spiritlevel.js diff --git a/apps/spiritlevel.json b/apps/slevel/spiritlevel.json similarity index 100% rename from apps/spiritlevel.json rename to apps/slevel/spiritlevel.json diff --git a/apps/spiritlevel.png b/apps/slevel/spiritlevel.png similarity index 100% rename from apps/spiritlevel.png rename to apps/slevel/spiritlevel.png diff --git a/apps/speedo-icon.js b/apps/speedo/speedo-icon.js similarity index 100% rename from apps/speedo-icon.js rename to apps/speedo/speedo-icon.js diff --git a/apps/speedo.js b/apps/speedo/speedo.js similarity index 100% rename from apps/speedo.js rename to apps/speedo/speedo.js diff --git a/apps/speedo.json b/apps/speedo/speedo.json similarity index 100% rename from apps/speedo.json rename to apps/speedo/speedo.json diff --git a/apps/speedo.png b/apps/speedo/speedo.png similarity index 100% rename from apps/speedo.png rename to apps/speedo/speedo.png diff --git a/apps/start-bangle.js b/apps/start/start-bangle.js similarity index 100% rename from apps/start-bangle.js rename to apps/start/start-bangle.js diff --git a/apps/start-icon.js b/apps/start/start-icon.js similarity index 100% rename from apps/start-icon.js rename to apps/start/start-icon.js diff --git a/apps/start-nceu.js b/apps/start/start-nceu.js similarity index 100% rename from apps/start-nceu.js rename to apps/start/start-nceu.js diff --git a/apps/start-nfr.js b/apps/start/start-nfr.js similarity index 100% rename from apps/start-nfr.js rename to apps/start/start-nfr.js diff --git a/apps/start-nodew.js b/apps/start/start-nodew.js similarity index 100% rename from apps/start-nodew.js rename to apps/start/start-nodew.js diff --git a/apps/start-tf.js b/apps/start/start-tf.js similarity index 100% rename from apps/start-tf.js rename to apps/start/start-tf.js diff --git a/apps/start.js b/apps/start/start.js similarity index 100% rename from apps/start.js rename to apps/start/start.js diff --git a/apps/start.json b/apps/start/start.json similarity index 100% rename from apps/start.json rename to apps/start/start.json diff --git a/apps/start.png b/apps/start/start.png similarity index 100% rename from apps/start.png rename to apps/start/start.png diff --git a/apps/stopwatch-icon.js b/apps/swatch/stopwatch-icon.js similarity index 100% rename from apps/stopwatch-icon.js rename to apps/swatch/stopwatch-icon.js diff --git a/apps/stopwatch.js b/apps/swatch/stopwatch.js similarity index 100% rename from apps/stopwatch.js rename to apps/swatch/stopwatch.js diff --git a/apps/stopwatch.json b/apps/swatch/stopwatch.json similarity index 100% rename from apps/stopwatch.json rename to apps/swatch/stopwatch.json diff --git a/apps/stopwatch.png b/apps/swatch/stopwatch.png similarity index 100% rename from apps/stopwatch.png rename to apps/swatch/stopwatch.png diff --git a/apps/trex-icon.js b/apps/trex/trex-icon.js similarity index 100% rename from apps/trex-icon.js rename to apps/trex/trex-icon.js diff --git a/apps/trex.js b/apps/trex/trex.js similarity index 100% rename from apps/trex.js rename to apps/trex/trex.js diff --git a/apps/trex.json b/apps/trex/trex.json similarity index 100% rename from apps/trex.json rename to apps/trex/trex.json diff --git a/apps/trex.png b/apps/trex/trex.png similarity index 100% rename from apps/trex.png rename to apps/trex/trex.png diff --git a/apps/clock-word-icon.js b/apps/wclock/clock-word-icon.js similarity index 100% rename from apps/clock-word-icon.js rename to apps/wclock/clock-word-icon.js diff --git a/apps/clock-word.js b/apps/wclock/clock-word.js similarity index 100% rename from apps/clock-word.js rename to apps/wclock/clock-word.js diff --git a/apps/clock-word.json b/apps/wclock/clock-word.json similarity index 100% rename from apps/clock-word.json rename to apps/wclock/clock-word.json diff --git a/apps/clock-word.png b/apps/wclock/clock-word.png similarity index 100% rename from apps/clock-word.png rename to apps/wclock/clock-word.png diff --git a/firmware.js b/firmware.js index 6a2995a24..9bf18ab09 100644 --- a/firmware.js +++ b/firmware.js @@ -1,10 +1,10 @@ -require('Storage').write(".bootcde","E.setFlags({pretokenise:1});\nvar startapp;\ntry {\n startapp = require('Storage').readJSON('+start');\n} catch (e) {}\nif (startapp) {\n eval(require(\"Storage\").read(startapp.src));\n} else {\n delete startapp;\n setWatch(function displayMenu() {\n Bangle.setLCDMode(\"direct\");\n g.clear();\n clearInterval();\n clearWatch();\n Bangle.removeAllListeners();\n var s = require(\"Storage\");\n\n var apps = s.list().filter(a=>a[0]=='+').map(app=>{\n try { return s.readJSON(app); }\n catch (e) { return {name:\"DEAD: \"+app.substr(1)} }\n }).filter(app=>app.type==\"app\" || app.type==\"clock\" || !app.type);\n apps.sort((a,b)=>{\n var n=(0|a.sortorder)-(0|b.sortorder);\n if (n) return n; // do sortorder first\n if (a.nameb.name) return 1;\n return 0;\n });\n var selected = 0;\n var menuScroll = 0;\n var menuShowing = false;\n\n function drawMenu() {\n g.setFont(\"6x8\",2);\n g.setFontAlign(-1,0);\n var n = 3;\n if (selected>=n+menuScroll) menuScroll = 1+selected-n;\n if (selectedn+menuScroll) g.fillPoly([120,239,100,219,140,219]);\n else g.clearRect(100,219,140,239);\n for (var i=0;i0) {\n selected--;\n drawMenu();\n }\n }, BTN1, {repeat:true});\n setWatch(function() {\n if (selected+1WIDGETS[k].draw());\n }\n var clockApp = require(\"Storage\").list().filter(a=>a[0]=='+').map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).find(app=>app.type==\"clock\");\n if (clockApp) eval(require(\"Storage\").read(clockApp.src));\n else E.showMessage(\"No Clock Found\");\n delete clockApp;\n require(\"Storage\").list().filter(a=>a[0]=='=').forEach(widget=>eval(require(\"Storage\").read(widget)));\n setTimeout(drawWidgets,100);\n}\n"); +require('Storage').write(".bootcde","E.setFlags({pretokenise:1});\nvar startapp;\ntry {\n startapp = require('Storage').readJSON('+start');\n} catch (e) {}\nif (startapp) {\n eval(require(\"Storage\").read(startapp.src));\n} else {\n delete startapp;\n setWatch(function displayMenu() {\n Bangle.setLCDMode(\"direct\");\n g.clear();\n clearInterval();\n clearWatch();\n Bangle.removeAllListeners();\n var s = require(\"Storage\");\n\n var apps = s.list().filter(a=>a[0]=='+').map(app=>{\n try { return s.readJSON(app); }\n catch (e) { return {name:\"DEAD: \"+app.substr(1)} }\n }).filter(app=>app.type==\"app\" || app.type==\"clock\" || !app.type);\n apps.sort((a,b)=>{\n var n=(0|a.sortorder)-(0|b.sortorder);\n if (n) return n; // do sortorder first\n if (a.nameb.name) return 1;\n return 0;\n });\n var selected = 0;\n var menuScroll = 0;\n var menuShowing = false;\n\n function drawMenu() {\n g.setFont(\"6x8\",2);\n g.setFontAlign(-1,0);\n var n = 3;\n if (selected>=n+menuScroll) menuScroll = 1+selected-n;\n if (selectedn+menuScroll) g.fillPoly([120,239,100,219,140,219]);\n else g.clearRect(100,219,140,239);\n for (var i=0;i0) {\n selected--;\n drawMenu();\n }\n }, BTN1, {repeat:true});\n setWatch(function() {\n if (selected+1a[0]=='=').forEach(widget=>eval(require(\"Storage\").read(widget)));\n setTimeout(drawWidgets,100);\n } else {\n delete WIDGETS;\n delete WIDGETPOS;\n delete drawWidgets;\n }\n }, BTN2, {repeat:true,edge:\"falling\"});\n }, BTN2, {repeat:false,edge:\"falling\"}); // menu on middle button\n\n var WIDGETPOS={tl:32,tr:g.getWidth()-32,bl:32,br:g.getWidth()-32};\n var WIDGETS={};\n function drawWidgets() {\n for (var w of WIDGETS) w.draw();\n }\n var settings;\n try {\n settings = require(\"Storage\").readJSON('@setting');\n } catch (e) {\n settings = {}\n }\n var clockApp = settings.clock;\n if (clockApp) {\n clockApp = require(\"Storage\").read(clockApp)\n }\n if (!clockApp) {\n var clockApps = require(\"Storage\").list().filter(a=>a[0]=='+').map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n if (clockApps && clockApps.length > 0) {\n clockApp = require(\"Storage\").read(clockApps[0].src);\n }\n }\n if (clockApp) eval(clockApp);\n else E.showMessage(\"No Clock Found\");\n\n delete clockApps;\n require(\"Storage\").list().filter(a=>a[0]=='=').forEach(widget=>eval(require(\"Storage\").read(widget)));\n setTimeout(drawWidgets,100);\n}\n"); require('Storage').write("+mclock",{"name":"Morphing Clock","type":"clock","icon":"*mclock","src":"-mclock","sortorder":-10,"files":"+mclock,-mclock,*mclock"}); -require('Storage').write("-mclock","// Enable 'Set Current Time' in Settings -> Communications before sending\n(function(){ // make our own scope so this is GC'd when intervals are cleared\n// Offscreen buffer\nvar buf = Graphics.createArrayBuffer(240,86,1,{msb:true});\nfunction flip() {\n g.setColor(1,1,1);\n g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,50);\n}\n// The last time that we displayed\nvar lastTime = \" \";\n// If animating, this is the interval's id\nvar animInterval;\n\n/* Get array of lines from digit d to d+1.\n n is the amount (0..1)\n maxFive is true is this digit only counts 0..5 */\nconst DIGITS = {\n\" \":n=>[],\n\"0\":n=>[\n[n,0,1,0],\n[1,0,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[n,1,n,2],\n[n,0,n,1]],\n\"1\":n=>[\n[1-n,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1-n,1,1-n,2],\n[1-n,2,1,2]],\n\"2\":n=>[\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[0,1+n,0,2],\n[1,2-n,1,2],\n[0,2,1,2]],\n\"3\":n=>[\n[0,0,1-n,0],\n[0,0,0,n],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[n,2,1,2]],\n\"4\":n=>[\n[0,0,0,1],\n[1,0,1-n,0],\n[1,0,1,1-n],\n[0,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2]],\n\"5\": (n,maxFive)=>maxFive ? [ // 5 -> 0\n[0,0,0,1],\n[0,0,1,0],\n[n,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2,0,2],\n[1,1-n,1,1],\n[0,1,0,1+n]] : [ // 5 -> 6\n[0,0,0,1],\n[0,0,1,0],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2-n,0,2]],\n\"6\":n=>[\n[0,0,0,1-n],\n[0,0,1,0],\n[n,1,1,1],\n[1,1-n,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[0,1-n,0,2-2*n]],\n\"7\":n=>[\n[0,0,0,n],\n[0,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2],\n[1-n,1,1-n,2]],\n\"8\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,1,0,2-n]],\n\"9\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1-n,1],\n[0,1,0,1+n],\n[1,1,1,2],\n[0,2,1,2]],\n\":\":n=>[\n[0.4,0.4,0.6,0.4],\n[0.6,0.4,0.6,0.6],\n[0.6,0.6,0.4,0.6],\n[0.4,0.4,0.4,0.6],\n[0.4,1.4,0.6,1.4],\n[0.6,1.4,0.6,1.6],\n[0.6,1.6,0.4,1.6],\n[0.4,1.4,0.4,1.6]]\n};\n\n/* Draw a transition between lastText and thisText.\n 'n' is the amount - 0..1 */\nfunction draw(lastText,thisText,n) {\n buf.clear();\n var x = 1; // x offset\n const p = 2; // padding around digits\n var y = p; // y offset\n const s = 34; // character size\n for (var i=0;i{\n if (c[0]!=c[2]) // horiz\n buf.fillRect(x+c[0]*s,y+c[1]*s-p,x+c[2]*s,y+c[3]*s+p);\n else if (c[1]!=c[3]) // vert\n buf.fillRect(x+c[0]*s-p,y+c[1]*s,x+c[2]*s+p,y+c[3]*s);\n });\n if (thisCh==\":\") x-=4;\n x+=s+p+7;\n }\n y += 2*s;\n var d = new Date();\n buf.setFont(\"6x8\");\n buf.setFontAlign(-1,-1);\n buf.drawString((\"0\"+d.getSeconds()).substr(-2), x, y-8);\n // date\n buf.setFontAlign(0,-1);\n var date = d.toString().substr(0,15);\n buf.drawString(date, buf.getWidth()/2, y+8);\n flip();\n}\n\n/* Show the current time, and animate if needed */\nfunction showTime() {\n if (!Bangle.isLCDOn()) return;\n if (animInterval) return; // in animation - quit\n var d = new Date();\n var t = (\" \"+d.getHours()).substr(-2)+\":\"+\n (\"0\"+d.getMinutes()).substr(-2);\n var l = lastTime;\n // same - don't animate\n if (t==l) {\n draw(t,l,0);\n return;\n }\n var n = 0;\n animInterval = setInterval(function() {\n n += 1/10;\n if (n>=1) {\n n=1;\n clearInterval(animInterval);\n animInterval=0;\n }\n draw(l,t,n);\n }, 20);\n lastTime = t;\n}\n\nBangle.on('lcdPower',function(on) {\n if (on) {\n showTime();\n drawWidgets();\n }\n});\n\ng.clear();\n// Update time once a second\nsetInterval(showTime, 1000);\nshowTime();\n})();\n"); +require('Storage').write("-mclock","(function(){ // make our own scope so this is GC'd when intervals are cleared\n// Offscreen buffer\nvar buf = Graphics.createArrayBuffer(240,86,1,{msb:true});\nfunction flip() {\n g.setColor(1,1,1);\n g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,50);\n}\n// The last time that we displayed\nvar lastTime = \" \";\n// If animating, this is the interval's id\nvar animInterval;\n\n/* Get array of lines from digit d to d+1.\n n is the amount (0..1)\n maxFive is true is this digit only counts 0..5 */\nconst DIGITS = {\n\" \":n=>[],\n\"0\":n=>[\n[n,0,1,0],\n[1,0,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[n,1,n,2],\n[n,0,n,1]],\n\"1\":n=>[\n[1-n,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1-n,1,1-n,2],\n[1-n,2,1,2]],\n\"2\":n=>[\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[0,1+n,0,2],\n[1,2-n,1,2],\n[0,2,1,2]],\n\"3\":n=>[\n[0,0,1-n,0],\n[0,0,0,n],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[n,2,1,2]],\n\"4\":n=>[\n[0,0,0,1],\n[1,0,1-n,0],\n[1,0,1,1-n],\n[0,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2]],\n\"5\": (n,maxFive)=>maxFive ? [ // 5 -> 0\n[0,0,0,1],\n[0,0,1,0],\n[n,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2,0,2],\n[1,1-n,1,1],\n[0,1,0,1+n]] : [ // 5 -> 6\n[0,0,0,1],\n[0,0,1,0],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2-n,0,2]],\n\"6\":n=>[\n[0,0,0,1-n],\n[0,0,1,0],\n[n,1,1,1],\n[1,1-n,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[0,1-n,0,2-2*n]],\n\"7\":n=>[\n[0,0,0,n],\n[0,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2],\n[1-n,1,1-n,2]],\n\"8\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,1,0,2-n]],\n\"9\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1-n,1],\n[0,1,0,1+n],\n[1,1,1,2],\n[0,2,1,2]],\n\":\":n=>[\n[0.4,0.4,0.6,0.4],\n[0.6,0.4,0.6,0.6],\n[0.6,0.6,0.4,0.6],\n[0.4,0.4,0.4,0.6],\n[0.4,1.4,0.6,1.4],\n[0.6,1.4,0.6,1.6],\n[0.6,1.6,0.4,1.6],\n[0.4,1.4,0.4,1.6]]\n};\n\n/* Draw a transition between lastText and thisText.\n 'n' is the amount - 0..1 */\nfunction draw(lastText,thisText,n) {\n buf.clear();\n var x = 1; // x offset\n const p = 2; // padding around digits\n var y = p; // y offset\n const s = 34; // character size\n for (var i=0;i{\n if (c[0]!=c[2]) // horiz\n buf.fillRect(x+c[0]*s,y+c[1]*s-p,x+c[2]*s,y+c[3]*s+p);\n else if (c[1]!=c[3]) // vert\n buf.fillRect(x+c[0]*s-p,y+c[1]*s,x+c[2]*s+p,y+c[3]*s);\n });\n if (thisCh==\":\") x-=4;\n x+=s+p+7;\n }\n y += 2*s;\n var d = new Date();\n buf.setFont(\"6x8\");\n buf.setFontAlign(-1,-1);\n buf.drawString((\"0\"+d.getSeconds()).substr(-2), x, y-8);\n // date\n buf.setFontAlign(0,-1);\n var date = d.toString().substr(0,15);\n buf.drawString(date, buf.getWidth()/2, y+8);\n flip();\n}\n\n/* Show the current time, and animate if needed */\nfunction showTime() {\n if (!Bangle.isLCDOn()) return;\n if (animInterval) return; // in animation - quit\n var d = new Date();\n var t = (\" \"+d.getHours()).substr(-2)+\":\"+\n (\"0\"+d.getMinutes()).substr(-2);\n var l = lastTime;\n // same - don't animate\n if (t==l) {\n draw(t,l,0);\n return;\n }\n var n = 0;\n animInterval = setInterval(function() {\n n += 1/10;\n if (n>=1) {\n n=1;\n clearInterval(animInterval);\n animInterval=0;\n }\n draw(l,t,n);\n }, 20);\n lastTime = t;\n}\n\nBangle.on('lcdPower',function(on) {\n if (on) {\n showTime();\n drawWidgets();\n }\n});\n\ng.clear();\n// Update time once a second\nsetInterval(showTime, 1000);\nshowTime();\n})();\n"); require('Storage').write("*mclock",require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWPmf//8zDBpFDwYVBAAc4JJYWJDAoXKn4SC+EPAgXzC5JGCx4qDC4n//BIIEIRCEC4v/GBBdHC4xhCIw5dDC5BhCJAgXCRQoXGJAQXEUhAXHJAyNGC5KRCC7p2FC5B4CC5kggQXOBwvyBQMvSA4XL+EIwCoIC8ZHCgYXNO44LBBIiPPCAIwFC5DXGAAMwGAjvPGA4XIwYXHGALBDnAXFhCQHGAaOFwAXGPA4bFC4xIMIxIXDJBJGEC4xICSJCNEIwowEMJBdCFwwXEMJBdCC5BICDA4WDIw4wEAAMzCoMzBAgWIDAwAGCxRJEAAxFJDBgWNDBAWPAH4AYA=="))); require('Storage').write("+setting",{"name":"Settings","type":"app","icon":"*settings","src":"-settings","files":"+setting,-setting,=setting,@setting,*setting"}); -require('Storage').write("-setting","Bangle.setLCDPower(1);\nBangle.setLCDTimeout(0);\n\ng.clear();\nconst storage = require('Storage');\nlet settings;\n\nfunction debug(msg, arg) {\n if (settings.debug)\n console.log(msg, arg);\n}\n\nfunction updateSettings() {\n debug('updating settings', settings);\n //storage.erase('@setting'); // - not needed, just causes extra writes if settings were the same\n storage.write('@setting', settings);\n}\n\nfunction resetSettings() {\n settings = {\n ble: false,\n dev: false,\n timeout: 10,\n vibrate: true,\n beep: true,\n timezone: 0,\n HID : false,\n HIDGestures: false,\n debug: false,\n };\n setLCDTimeout(settings.timeout);\n updateSettings();\n}\n\ntry {\n settings = storage.readJSON('@setting');\n} catch (e) {}\nif (!settings) resetSettings();\n\nconst boolFormat = (v) => v ? \"On\" : \"Off\";\n\nfunction showMainMenu() {\n const mainmenu = {\n '': { 'title': 'Settings' },\n 'BLE': {\n value: settings.ble,\n format: boolFormat,\n onchange: () => {\n settings.ble = !settings.ble;\n updateSettings();\n }\n },\n 'Programmable': {\n value: settings.dev,\n format: boolFormat,\n onchange: () => {\n settings.dev = !settings.dev;\n updateSettings();\n }\n },\n 'LCD Timeout': {\n value: settings.timeout,\n min: 0,\n max: 60,\n step: 5,\n onchange: v => {\n settings.timeout = 0 | v;\n updateSettings();\n Bangle.setLCDTimeout(settings.timeout);\n }\n },\n 'Beep': {\n value: settings.beep,\n format: boolFormat,\n onchange: () => {\n settings.beep = !settings.beep;\n updateSettings();\n if (settings.beep) {\n Bangle.beep(1);\n }\n }\n },\n 'Vibration': {\n value: settings.vibrate,\n format: boolFormat,\n onchange: () => {\n settings.vibrate = !settings.vibrate;\n updateSettings();\n if (settings.vibrate) {\n VIBRATE.write(1);\n setTimeout(()=>VIBRATE.write(0), 10);\n }\n }\n },\n 'Time Zone': {\n value: settings.timezone,\n min: -11,\n max: 12,\n step: 1,\n onchange: v => {\n settings.timezone = 0 | v;\n updateSettings();\n }\n },\n 'HID': {\n value: settings.HID,\n format: boolFormat,\n onchange: () => {\n settings.HID = !settings.HID;\n updateSettings();\n }\n },\n 'HID Gestures': {\n value: settings.HIDGestures,\n format: boolFormat,\n onchange: () => {\n settings.HIDGestures = !settings.HIDGestures;\n updateSettings();\n }\n },\n 'Debug': {\n value: settings.debug,\n format: boolFormat,\n onchange: () => {\n settings.debug = !settings.debug;\n updateSettings();\n }\n },\n 'Set Time': showSetTimeMenu,\n 'Make Connectable': makeConnectable,\n 'Reset Settings': showResetMenu,\n 'Turn Off': Bangle.off,\n '< Back': load\n };\n return Bangle.menu(mainmenu);\n}\n\nfunction showResetMenu() {\n const resetmenu = {\n '': { 'title': 'Reset' },\n '< Back': showMainMenu,\n 'Reset Settings': () => {\n E.showPrompt('Reset Settings?').then((v) => {\n if (v) {\n E.showMessage('Resetting');\n resetSettings();\n }\n setTimeout(showMainMenu, 50);\n });\n },\n // this is include for debugging. remove for production\n /*'Erase': () => {\n storage.erase('=setting');\n storage.erase('-setting');\n storage.erase('@setting');\n storage.erase('*setting');\n storage.erase('+setting');\n E.reboot();\n }*/\n };\n return Bangle.menu(resetmenu);\n}\n\nfunction makeConnectable() {\n try { NRF.wake(); } catch(e) {}\n Bluetooth.setConsole(1);\n var name=\"Bangle.js \"+NRF.getAddress().substr(-5).replace(\":\",\"\");\n E.showPrompt(name+\"\\nStay Connectable?\",{title:\"Connectable\"}).then(r=>{\n if (settings.ble!=r) {\n settings.ble = r;\n updateSettings();\n }\n if (!r) try { NRF.sleep(); } catch(e) {}\n showMainMenu();\n });\n}\n\nfunction showSetTimeMenu() {\n d = new Date();\n const timemenu = {\n '': {\n 'title': 'Set Time',\n 'predraw': function() {\n d = new Date();\n timemenu.Hour.value = d.getHours();\n timemenu.Minute.value = d.getMinutes();\n timemenu.Second.value = d.getSeconds();\n timemenu.Date.value = d.getDate();\n timemenu.Month.value = d.getMonth() + 1;\n timemenu.Year.value = d.getFullYear();\n }\n },\n '< Back': showMainMenu,\n 'Hour': {\n value: d.getHours(),\n min: 0,\n max: 23,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setHours(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Minute': {\n value: d.getMinutes(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMinutes(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Second': {\n value: d.getSeconds(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setSeconds(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Date': {\n value: d.getDate(),\n min: 1,\n max: 31,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setDate(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Month': {\n value: d.getMonth() + 1,\n min: 1,\n max: 12,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMonth(v - 1);\n setTime(d.getTime()/1000);\n }\n },\n 'Year': {\n value: d.getFullYear(),\n min: d.getFullYear() - 10,\n max: d.getFullYear() + 10,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setFullYear(v);\n setTime(d.getTime()/1000);\n }\n }\n };\n return Bangle.menu(timemenu);\n}\n\nshowMainMenu();\n"); -require('Storage').write("=setting","Bangle.HID = E.toUint8Array(atob(\"BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==\"));\n\n(function() {\n var s = require('Storage').readJSON('@setting');\n var adv = { uart: true };\n if (s.ble) {\n if (s.dev)\n Bluetooth.setConsole(true);\n else\n Terminal.setConsole(true);\n if (s.HID) {\n adv.hid = Bangle.HID;\n } else\n delete Bangle.HID;\n }\n setTimeout(function() {\n NRF.setServices({}, adv);\n if (s.ble) NRF.wake();\n else NRF.sleep();\n },10);\n\n if (!s.vibrate) Bangle.buzz=()=>Promise.resolve();\n if (!s.beep) Bangle.beep=()=>Promise.resolve();\n Bangle.setLCDTimeout(s.timeout);\n if (!s.timeout) Bangle.setLCDPower(1);\n E.setTimeZone(s.timezone);\n})()\n"); +require('Storage').write("-setting","Bangle.setLCDPower(1);\nBangle.setLCDTimeout(0);\n\ng.clear();\nconst storage = require('Storage');\nlet settings;\n\nfunction debug(msg, arg) {\n if (settings.debug)\n console.log(msg, arg);\n}\n\nfunction updateSettings() {\n debug('updating settings', settings);\n //storage.erase('@setting'); // - not needed, just causes extra writes if settings were the same\n storage.write('@setting', settings);\n}\n\nfunction resetSettings() {\n settings = {\n ble: false,\n dev: false,\n timeout: 10,\n vibrate: true,\n beep: true,\n timezone: 0,\n HID : false,\n HIDGestures: false,\n debug: false,\n clock: null\n };\n setLCDTimeout(settings.timeout);\n updateSettings();\n}\n\ntry {\n settings = storage.readJSON('@setting');\n} catch (e) {}\nif (!settings) resetSettings();\n\nconst boolFormat = (v) => v ? \"On\" : \"Off\";\n\nfunction showMainMenu() {\n const mainmenu = {\n '': { 'title': 'Settings' },\n 'BLE': {\n value: settings.ble,\n format: boolFormat,\n onchange: () => {\n settings.ble = !settings.ble;\n updateSettings();\n }\n },\n 'Programmable': {\n value: settings.dev,\n format: boolFormat,\n onchange: () => {\n settings.dev = !settings.dev;\n updateSettings();\n }\n },\n 'LCD Timeout': {\n value: settings.timeout,\n min: 0,\n max: 60,\n step: 5,\n onchange: v => {\n settings.timeout = 0 | v;\n updateSettings();\n Bangle.setLCDTimeout(settings.timeout);\n }\n },\n 'Beep': {\n value: settings.beep,\n format: boolFormat,\n onchange: () => {\n settings.beep = !settings.beep;\n updateSettings();\n if (settings.beep) {\n Bangle.beep(1);\n }\n }\n },\n 'Vibration': {\n value: settings.vibrate,\n format: boolFormat,\n onchange: () => {\n settings.vibrate = !settings.vibrate;\n updateSettings();\n if (settings.vibrate) {\n VIBRATE.write(1);\n setTimeout(()=>VIBRATE.write(0), 10);\n }\n }\n },\n 'Select Clock': showClockMenu,\n 'Time Zone': {\n value: settings.timezone,\n min: -11,\n max: 12,\n step: 1,\n onchange: v => {\n settings.timezone = 0 | v;\n updateSettings();\n }\n },\n 'HID': {\n value: settings.HID,\n format: boolFormat,\n onchange: () => {\n settings.HID = !settings.HID;\n updateSettings();\n }\n },\n 'HID Gestures': {\n value: settings.HIDGestures,\n format: boolFormat,\n onchange: () => {\n settings.HIDGestures = !settings.HIDGestures;\n updateSettings();\n }\n },\n 'Debug': {\n value: settings.debug,\n format: boolFormat,\n onchange: () => {\n settings.debug = !settings.debug;\n updateSettings();\n }\n },\n 'Set Time': showSetTimeMenu,\n 'Make Connectable': makeConnectable,\n 'Reset Settings': showResetMenu,\n 'Turn Off': Bangle.off,\n '< Back': load\n };\n return Bangle.menu(mainmenu);\n}\n\nfunction showResetMenu() {\n const resetmenu = {\n '': { 'title': 'Reset' },\n '< Back': showMainMenu,\n 'Reset Settings': () => {\n E.showPrompt('Reset Settings?').then((v) => {\n if (v) {\n E.showMessage('Resetting');\n resetSettings();\n }\n setTimeout(showMainMenu, 50);\n });\n },\n // this is include for debugging. remove for production\n /*'Erase': () => {\n storage.erase('=setting');\n storage.erase('-setting');\n storage.erase('@setting');\n storage.erase('*setting');\n storage.erase('+setting');\n E.reboot();\n }*/\n };\n return Bangle.menu(resetmenu);\n}\n\nfunction makeConnectable() {\n try { NRF.wake(); } catch(e) {}\n Bluetooth.setConsole(1);\n var name=\"Bangle.js \"+NRF.getAddress().substr(-5).replace(\":\",\"\");\n E.showPrompt(name+\"\\nStay Connectable?\",{title:\"Connectable\"}).then(r=>{\n if (settings.ble!=r) {\n settings.ble = r;\n updateSettings();\n }\n if (!r) try { NRF.sleep(); } catch(e) {}\n showMainMenu();\n });\n}\nfunction showClockMenu() {\n var clockApps = require(\"Storage\").list().filter(a=>a[0]=='+').map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n const clockMenu = {\n '': {\n 'title': 'Select Clock',\n },\n '< Back': showMainMenu,\n };\n clockApps.forEach((app,index) => {\n var label = app.name;\n if ((!settings.clock && index === 0) || (settings.clock === app.src)) {\n label = \"* \"+label;\n }\n clockMenu[label] = () => {\n if (settings.clock !== app.src) {\n settings.clock = app.src;\n updateSettings();\n showMainMenu();\n }\n };\n });\n if (clockApps.length === 0) {\n clockMenu[\"No Clocks Found\"] = () => {};\n }\n return Bangle.menu(clockMenu);\n}\n\n\n\nfunction showSetTimeMenu() {\n d = new Date();\n const timemenu = {\n '': {\n 'title': 'Set Time',\n 'predraw': function() {\n d = new Date();\n timemenu.Hour.value = d.getHours();\n timemenu.Minute.value = d.getMinutes();\n timemenu.Second.value = d.getSeconds();\n timemenu.Date.value = d.getDate();\n timemenu.Month.value = d.getMonth() + 1;\n timemenu.Year.value = d.getFullYear();\n }\n },\n '< Back': showMainMenu,\n 'Hour': {\n value: d.getHours(),\n min: 0,\n max: 23,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setHours(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Minute': {\n value: d.getMinutes(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMinutes(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Second': {\n value: d.getSeconds(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setSeconds(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Date': {\n value: d.getDate(),\n min: 1,\n max: 31,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setDate(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Month': {\n value: d.getMonth() + 1,\n min: 1,\n max: 12,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMonth(v - 1);\n setTime(d.getTime()/1000);\n }\n },\n 'Year': {\n value: d.getFullYear(),\n min: d.getFullYear() - 10,\n max: d.getFullYear() + 10,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setFullYear(v);\n setTime(d.getTime()/1000);\n }\n }\n };\n return Bangle.menu(timemenu);\n}\n\nshowMainMenu();\n"); +require('Storage').write("=setting","Bangle.HID = E.toUint8Array(atob(\"BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==\"));\n\n(function() {\n var s = require('Storage').readJSON('@setting');\n var adv = { uart: true };\n if (s.ble) {\n if (s.dev)\n Bluetooth.setConsole(true);\n else\n Terminal.setConsole(true);\n if (s.HID) {\n adv.hid = Bangle.HID;\n } else\n delete Bangle.HID;\n }\n NRF.setServices({}, adv);\n // we just reset, so BLE should be on\n try { // disable advertising if BLE should be off\n if (!s.ble) NRF.sleep();\n else NRF.wake();\n } catch(e) {}\n if (!s.vibrate) Bangle.buzz=Promise.resolve;\n if (!s.beep) Bangle.beep=Promise.resolve;\n Bangle.setLCDTimeout(s.timeout);\n if (!s.timeout) Bangle.setLCDPower(1);\n E.setTimeZone(s.timezone);\n})()\n"); require('Storage').write("@setting",{ ble: false, // Bluetooth disabled by default dev: false, // Espruino IDE disabled by default @@ -24,12 +24,12 @@ require('Storage').write("-gpstime","var img = require(\"heatshrink\").decompress(atob(\"mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=\"));\n\nBangle.setLCDPower(1);\nBangle.setLCDTimeout(0);\n\ng.clear();\n\n\n\nvar fix;\nBangle.on('GPS',function(f) {\n fix = f;\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n g.clearRect(90,30,239,90);\n if (fix.fix) {\n g.drawString(\"GPS\",170,40);\n g.drawString(\"Acquired\",170,60);\n } else {\n g.drawString(\"Waiting for\",170,40);\n g.drawString(\"GPS Fix\",170,60);\n }\n g.setFont(\"6x8\");\n g.drawString(fix.satellites+\" satellites\",170,80);\n \n g.clearRect(0,100,239,239);\n var t = fix.time.toString().split(\" \");/*\n [\n \"Sun\",\n \"Nov\",\n \"10\",\n \"2019\",\n \"15:55:35\",\n \"GMT+0100\"\n ]\n */\n //g.setFont(\"6x8\",2);\n //g.drawString(t[0],120,110); // day\n g.setFont(\"6x8\",3);\n g.drawString(t[1]+\" \"+t[2],120,135); // date\n g.setFont(\"6x8\",2);\n g.drawString(t[3],120,160); // year\n g.setFont(\"6x8\",3);\n g.drawString(t[4],120,185); // time\n // timezone\n var tz = (new Date()).getTimezoneOffset()/60;\n if (tz==0) tz=\"UTC\";\n else if (tz>0) tz=\"UTC+\"+tz;\n else tz=\"UTC\"+tz;\n g.setFont(\"6x8\",2);\n g.drawString(tz,120,210); // gmt\n g.setFontAlign(0,0,3);\n g.drawString(\"Set\",230,120);\n g.setFontAlign(0,0);\n});\n\nsetInterval(function() {\n g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});\n},100);\nsetWatch(function() {\n setTime(fix.time.getTime()/1000);\n}, BTN2, {repeat:true});\n\nBangle.setGPSPower(1)\n"); require('Storage').write("*gpstime",require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="))); require('Storage').write("+compass",{"name":"Compass","type":"app","icon":"*compass","src":"-compass","files":"+compass,-compass,*compass"}); -require('Storage').write("-compass","g.clear();\ng.setColor(0,0.5,1);\ng.fillCircle(120,130,80,80);\ng.setColor(0,0,0);\ng.fillCircle(120,130,70,70);\n\nfunction arrow(r,c) {\n r=r*Math.PI/180;\n var p = Math.PI/2;\n g.setColor(c);\n g.fillPoly([\n 120+60*Math.sin(r), 130-60*Math.cos(r),\n 120+10*Math.sin(r+p), 130-10*Math.cos(r+p),\n 120+10*Math.sin(r+-p), 130-10*Math.cos(r-p),\n ]);\n}\n\nvar oldHeading = 0;\nBangle.on('mag', function(m) {\n if (!Bangle.isLCDOn()) return;\n g.setFont(\"6x8\",3);\n g.setColor(0);\n g.fillRect(70,0,170,24);\n g.setColor(0xffff);\n g.setFontAlign(0,0);\n g.drawString((m.heading===undefined)?\"---\":Math.round(m.heading),120,12);\n g.setColor(0,0,0);\n arrow(oldHeading,0);\n arrow(oldHeading+180,0);\n arrow(m.heading,0xF800);\n arrow(m.heading+180,0x001F);\n oldHeading = m.heading;\n});\nBangle.setCompassPower(1);\n"); +require('Storage').write("-compass","g.clear();\ng.setColor(0,0.5,1);\ng.fillCircle(120,130,80,80);\ng.setColor(0,0,0);\ng.fillCircle(120,130,70,70);\n\nfunction arrow(r,c) {\n r=r*Math.PI/180;\n var p = Math.PI/2;\n g.setColor(c);\n g.fillPoly([\n 120+60*Math.sin(r), 130-60*Math.cos(r),\n 120+10*Math.sin(r+p), 130-10*Math.cos(r+p),\n 120+10*Math.sin(r+-p), 130-10*Math.cos(r-p),\n ]);\n}\n\nvar oldHeading = 0;\nBangle.on('mag', function(m) {\n if (!Bangle.isLCDOn()) return;\n g.setFont(\"6x8\",3);\n g.setColor(0);\n g.fillRect(70,0,170,24);\n g.setColor(0xffff);\n g.setFontAlign(0,0);\n g.drawString(isNaN(m.heading)?\"---\":Math.round(m.heading),120,12);\n g.setColor(0,0,0);\n arrow(oldHeading,0);\n arrow(oldHeading+180,0);\n arrow(m.heading,0xF800);\n arrow(m.heading+180,0x001F);\n oldHeading = m.heading;\n});\nBangle.setCompassPower(1);\n"); require('Storage').write("*compass",require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWOwcz///mc4DBhFDwYVBAAYYDJJAWJDAoXKCw//+YXJIwWPCQk/Aof4JBAuHC4v/GBBdHC4nzMIZGHCAIOBC4vz75hDJAgXCCgS9CC4fdAYQXGIwsyCAPyl//nvdVQoXFRofzkYXCCwJGBSIgXFQ4kymcykfdIwZgDC5XzkUyCwJGDC6FNCwPTC5i9FmQXCMgLZFC48zLgMilUv/vdkUjBII9BC6HSC55HD1WiklDNIgXIBok61QYBkSBFC5kqCwMjC6RGB1RcCR4gXIx4MC+Wqkfyl70BEQf4C4+DIwYqBC4XzGAc4C4sISAfz0QDCFgUzRwmAC4wQB+QTCC4f/AYJeCC4hIEPQi9FIwwXDbIzVHC4xICSIYXGRoRGFGAgqFXgouGC4iqDLo4XIJAQYHCwZGHGAgYBXQUzCwYuIDAwAHCxRJEAAxFJDBgWNDBAWPAH4AYA="))); require('Storage').write("+sbt",{"name":"bluetooth","type":"widget","src":"=sbt","files":"+sbt,=sbt"}); require('Storage').write("=sbt","(function(){\nvar img_bt = E.toArrayBuffer(atob(\"CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==\"));\nvar xpos = WIDGETPOS.tr-24;\nWIDGETPOS.tr-=24;\n\nfunction draw() {\n var x = xpos, y = 0;\n if (NRF.getSecurityStatus().connected)\n g.setColor(0,0.5,1);\n else\n g.setColor(0.3,0.3,0.3);\n g.drawImage(img_bt,10+x,2+y);\n g.setColor(1,1,1);\n}\nfunction changed() {\n draw();\n g.flip();\n}\nNRF.on('connected',changed);\nNRF.on('disconnected',changed);\nWIDGETS[\"bluetooth\"]={draw:draw};\n})()\n"); require('Storage').write("+sbat",{"name":"Battery Level","type":"widget","src":"=sbat","files":"+sbat,=sbat"}); -require('Storage').write("=sbat","(function(){\nvar img_charge = E.toArrayBuffer(atob(\"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg\"));\nvar xpos = WIDGETPOS.tr-64;\nWIDGETPOS.tr-=68;\n\nfunction draw() {\n var s = 63;\n var x = xpos, y = 0;\n g.clearRect(x,y,x+s,y+23);\n if (Bangle.isCharging()) {\n g.drawImage(img_charge,x,y);\n x+=16;\n s-=16;\n }\n g.setColor(1,1,1);\n g.fillRect(x,y+2,x+s-4,y+21);\n g.clearRect(x+2,y+4,x+s-6,y+19);\n g.fillRect(x+s-3,y+10,x+s,y+14);\n g.fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);\n g.setColor(1,1,1);\n}\nBangle.on('charging',function(charging) { draw(); g.flip(); if(charging)Bangle.buzz(); });\nWIDGETS[\"battery\"]={draw:draw};\n})()\n"); +require('Storage').write("=sbat","(function(){\nvar img_charge = E.toArrayBuffer(atob(\"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg\"));\nvar xpos = WIDGETPOS.tr-64;\nWIDGETPOS.tr-=68;\n\nfunction draw() {\n var s = 63;\n var x = xpos, y = 0;\n g.clearRect(x,y,x+s,y+23);\n g.setColor(1,1,1);\n if (Bangle.isCharging()) {\n g.drawImage(img_charge,x,y);\n x+=16;\n s-=16;\n }\n g.fillRect(x,y+2,x+s-4,y+21);\n g.clearRect(x+2,y+4,x+s-6,y+19);\n g.fillRect(x+s-3,y+10,x+s,y+14);\n g.fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);\n g.setColor(1,1,1);\n}\nBangle.on('charging',function(charging) { draw(); g.flip(); if(charging)Bangle.buzz(); });\nWIDGETS[\"battery\"]={draw:draw};\n})()\n"); require('Storage').write("+funrun5",{"name":"5K Fun Run","type":"app","icon":"*funrun5","src":"-funrun5","sortorder":-1,"files":"+funrun5,-funrun5,*funrun5"}); require('Storage').write("-funrun5","var coordScale = 0.6068;\r\nvar coords = new Int32Array([-807016,6918514,-807057,6918544,-807135,6918582,-807238,6918630,-807289,6918646,-807308,6918663,-807376,6918755,-807413,6918852,-807454,6919002,-807482,6919080,-807509,6919158,-807523,6919221,-807538,6919256,-807578,6919336,-807628,6919447,-807634,6919485,-807640,6919505,-807671,6919531,-807703,6919558,-807760,6919613,-807752,6919623,-807772,6919643,-807802,6919665,-807807,6919670,-807811,6919685,-807919,6919656,-807919,6919645,-807890,6919584,-807858,6919533,-807897,6919503,-807951,6919463,-807929,6919430,-807916,6919412,-807907,6919382,-807901,6919347,-807893,6919322,-807878,6919292,-807858,6919274,-807890,6919232,-807909,6919217,-807938,6919206,-807988,6919180,-807940,6919127,-807921,6919100,-807908,6919072,-807903,6919039,-807899,6919006,-807911,6918947,-807907,6918936,-807898,6918905,-807881,6918911,-807874,6918843,-807870,6918821,-807854,6918775,-807811,6918684,-807768,6918593,-807767,6918593,-807729,6918516,-807726,6918505,-807726,6918498,-807739,6918481,-807718,6918465,-807697,6918443,-807616,6918355,-807518,6918263,-807459,6918191,-807492,6918162,-807494,6918147,-807499,6918142,-807500,6918142,-807622,6918041,-807558,6917962,-807520,6917901,-807475,6917933,-807402,6917995,-807381,6918024,-807361,6918068,-807323,6918028,-807262,6918061,-807263,6918061,-807159,6918116,-807148,6918056,-807028,6918063,-807030,6918063,-806979,6918068,-806892,6918090,-806760,6918115,-806628,6918140,-806556,6918162,-806545,6918175,-806531,6918173,-806477,6918169,-806424,6918180,-806425,6918180,-806367,6918195,-806339,6918197,-806309,6918191,-806282,6918182,-806248,6918160,-806225,6918136,-806204,6918107,-806190,6918076,-806169,6917968,-806167,6917953,-806157,6917925,-806140,6917896,-806087,6917839,-806071,6917824,-805969,6917904,-805867,6917983,-805765,6918063,-805659,6918096,-805677,6918131,-805676,6918131,-805717,6918212,-805757,6918294,-805798,6918397,-805827,6918459,-805877,6918557,-805930,6918608,-805965,6918619,-806037,6918646,-806149,6918676,-806196,6918685,-806324,6918703,-806480,6918735,-806528,6918738,-806644,6918712,-806792,6918667,-806846,6918659,-806914,6918654,-806945,6918661,-806971,6918676,-806993,6918689,-806992,6918692,-807065,6918753,-807086,6918786,-807094,6918788,-807102,6918795,-807104,6918793,-807107,6918799,-807102,6918802,-807112,6918812,-807106,6918815,-807115,6918826,-807120,6918823,-807132,6918841,-807141,6918850,-807151,6918841,-807170,6918832,-807193,6918813,-807222,6918775,-807246,6918718,-807250,6918694,-807264,6918637,-807238,6918630,-807148,6918587,-807057,6918544,-806948,6918463]);\r\n\r\nvar min = {\"x\":-807988,\"y\":6917824};\r\nvar max = {\"x\":-805659,\"y\":6919685};\r\nvar gcoords = new Uint8Array(coords.length);\r\nvar coordDistance = new Uint16Array(coords.length/2);\r\n\r\nvar PT_DISTANCE = 30; // distance to a point before we consider it complete\r\n\r\nfunction toScr(p) {\r\n return {\r\n x : 10 + (p.x-min.x)*100/(max.x-min.x),\r\n y : 230 - (p.y-min.y)*100/(max.y-min.y)\r\n };\r\n}\r\n\r\nvar last;\r\nvar totalDistance = 0;\r\nfor (var i=0;i
-