From 8190b84ce666559c815619bb8f94fd64b3acea7e Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 4 Mar 2024 12:30:52 +0000 Subject: [PATCH] Undo changes to non-regattatime files --- README.md | 598 ++++++++++++++++++++++++++++++++++++++++++---- app-icon.js | 1 - app.js | 335 -------------------------- icon.png | Bin 2990 -> 0 bytes regattatimer.json | 8 - screenshot-6.png | Bin 2775 -> 0 bytes settings.js | 78 ------ 7 files changed, 549 insertions(+), 471 deletions(-) delete mode 100644 app-icon.js delete mode 100644 app.js delete mode 100644 icon.png delete mode 100644 regattatimer.json delete mode 100644 screenshot-6.png delete mode 100644 settings.js diff --git a/README.md b/README.md index 8d906e1d7..ed6a501ef 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,574 @@ -# Regatta Timer 5-4-1 countdown +Bangle.js App Loader (and Apps) +================================ -## Modes +[![Build Status](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml/badge.svg)](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml) -* **Idle** - On startup the application is in idle mode showing a large 5 in the centre of the screen and the time of day below. - `Button` switches to start mode. -* **Start** - During the countdown, the screen changes the layout several times to use as much space as - possible to display the numbers. - When time is up the buzzer sounds and the application switches to race mode. - `Button` switches to idle mode. -* **Race** - Race time, local time, SOA, number reachable GPS satellites and battery level are shown. - `Button` switches to "stopped mode". -* **Stoped** - The race counter stops. - `Button` switches to idle mode. +* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) +* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) -## Screenshots +**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By +submitting code to this repository you confirm that you are happy with it being MIT licensed, +and that it is not licensed in another way that would make this impossible. -*Idle mode: showing a big 5 and time of day below* +## How does it work? -![Idle mode: showing a big 5 and time of day below](screenshot-1.png) +* 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/` 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. -*Start mode: minutes and seconds* +## Getting Started -![Start mode: minutes and seconds](screenshot-2.png) +Check out: -*Start mode: seconds* +* [Building your first Bangle.js Application](https://www.espruino.com/Bangle.js+First+App) +* [Adding an app to the Bangle.js App Loader](https://www.espruino.com/Bangle.js+App+Loader) +* [Customising the App Loader](https://www.espruino.com/Bangle.js+App+Loader+Custom) -![Start mode: seconds](screenshot-3.png) +## What filenames are used -*Race mode: elapsed time, time of day, speed, satellites, battery* +Filenames in storage are limited to 28 characters. To +easily distinguish between file types, we use the following: -![Race mode: elapsed time, time of day, speed, satellites, battery](screenshot-4.png) +* `stuff.info` is JSON that describes an app - this is auto-generated by the App Loader +* `stuff.img` is an image +* `stuff.app.js` is JS code for applications +* `stuff.wid.js` is JS code for widgets +* `stuff.settings.js` is JS code for the settings menu +* `stuff.boot.js` is JS code that automatically gets run at boot time +* `stuff.json` is used for JSON settings for an app -*Race mode: with german abbreviations* +## Developing your own app -![Race mode: with german abbreviations](screenshot-5.png) +* 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 -*Settings page: main* +## Adding your app to the menu -![Settings page: main](screenshot-6.png) +* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js +is limited to 28 char filenames and appends a file extension (eg `.js`) so please +try and keep filenames short to avoid overflowing the buffer. +* Create a folder called `apps/`, lets assume `apps/myappid` +* 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/modify `apps/myappid/metadata.json` as follows: -*Settings page: choose the theme* +``` +{ "id": "myappid", + "name": "My app's human readable name", + "shortName" : "Short Name", + "icon": "app.png", + "description": "A detailed description of my great app", + "tags": "", + "storage": [ + {"name":"myappid.app.js","url":"app.js"}, + {"name":"myappid.img","url":"app-icon.js","evaluate":true} + ], +}, +``` -![Settings page: choose the theme](screenshot-7.png) +### Screenshots -## Localization +In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { "url":"screenshot.png" } ],` -Localization is done by the Bangle.js 2 app "Languages" -* Go to [banglejs.com/apps](https://banglejs.com/apps/) -* Search for app "Languages" -* Click the "arrow up" or "burger" icon -* Choose your language from the dropdown -* Click `upload` +To get a screenshot you can: -**Some nautical abbreviations which are not part of the Bangle.js 2 app "Languages" app are stored in `translations.json`.** +* Type `g.dump()` in the left-hand side of the Web IDE when connected to a Bangle.js 2 - you can then +right-click and save the image shown in the terminal (this only works on Bangle.js 2 - Bangle.js 1 is +unable to read data back from the LCD controller). +* Run your code in the emulator and use the screenshot button in the bottom right of the window. -## Feedback -Report bugs or request a feature at [github.com/naden](https://github.com/naden) +## Testing -## Roadmap -* add a seconds coundown layout; mimic a classic regatta chronograph -* add recording of gps course and race time -* add icons for light mode -* add flag icons +### Online -## Created by -© 2021 - 2024 [naden.de](https://naden.de) +This is the best way to test... -Icons by [Icons8](https://icons8.com/) +* 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 + +**Note:** It's a great idea to get a local copy of the repository on your PC, +then run `bin/sanitycheck.js` - it'll run through a bunch of common issues +that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule update --init`. + +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 + +Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/) +(4 discs), upload your files into the places described in your JSON: + +* `app-icon.js` -> `myappid.img` + +Now load `app.js` up in the editor, and click the down-arrow to the bottom +right of the `Send to Espruino` icon. Click `Storage` and then either choose +`myappid.app.js` (if you'd uploaded your app previously), or `New File` +and then enter `myappid.app.js` as the name. + +Now, clicking the `Send to Espruino` icon will load the app directly into +Espruino **and** will automatically run it. + +When you upload code this way, your app will even be uploaded to Bangle.js's menu +without you having to use the `Bangle App Loader` + +**Note:** Widgets need to be run inside a clock or app, so if you're +developing a widget you need to go go `Settings` -> `Communications` -> `Load after saving` +and set it to `Load default application`. + +## Example Applications + +To make the process easier we've come up with some example applications that you can use as a base +when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app` +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. + +**If you're making a widget** please start the name with `wid` to make +it easy to find! + +### 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. + +* `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 +* `ChangeLog` - A file containing a list of changes to your app so users can see what's changed + +#### `app-icon.js` + +The icon image and short description is used in Bangle.js's launcher. + +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 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_, 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: + +``` +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 +them, and then `Bangle.drawWidgets()` to draw them onto the screen whenever the app +has call to completely clear the screen. Widgets themselves will update as and when needed. + +### Widget Example + +The widget example is available in [`apps/_example_widget`](apps/_example_widget) + +* `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 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) + 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 +}; +``` + +When the widget is to be drawn, `x` and `y` values are set up in `WIDGETS["mywidget"]` +and `draw` can then use `this.x` and `this.y` to figure out where it needs to draw to. + + +### ChangeLog + +This is a file containing a list of changes to your app so users can see what's changed, for example: + +``` +0.01: New App! +0.02: Changed the colors +0.03: Made the app run quicker +``` + +Entries should be newest last, with the version number of the last entry matching the version in `metadata.json` + +Please keep the same format at the example as the file needs to be parsed by the BangleApps tools. + +### `app.info` format + +This is the file that's **auto-generated** from `metadata.json` and loaded onto Bangle.js by the App Loader, +and which gives information about the app for the Launcher. + +``` +{ + "name":"Short Name", // for Bangle.js menu + "icon":"*myappid", // for Bangle.js menu + "src":"-myappid", // source file + "type":"widget/clock/app/bootloader/...", // optional, default "app" + // see 'type' in 'metadata.json format' below for more options/info + "version":"1.23", + // 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 + "data":"appid.data.json,appid.data?.json;appidStorageFile,appidStorageFile*" + // added by BangleApps loader on upload - lists files that + // the app might write, so they can be deleted on uninstall + // typically these files are not uploaded, but created by the app + // these can include '*' or '?' wildcards +} +``` + +### `metadata.json` format + +``` +{ "id": "appid", // 7 character app id + "name": "Readable name", // readable name + "shortName": "Short name", // short name for launcher + "version": "0v01", // the version of this app + "description": "...", // long description (can contain markdown) + "icon": "icon.png", // icon in apps/ + "screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app + "type":"...", // optional(if app) - + // 'app' - an application + // 'clock' - a clock - required for clocks to automatically start + // 'widget' - a widget + // 'module' - this provides a module that can be used with 'require'. + // 'provides_modules' should be used if type:module is specified + // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js' + // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js' + // 'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js + // 'RAM' - code that runs and doesn't upload anything to storage + // 'launch' - replacement 'Launcher' + // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle + // 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers + // (currently only 'sched' app) + // 'notify' - provides 'notify' library for showing notifications + // 'locale' - provides 'locale' library for language-specific date/distance/etc + // (a version of 'locale' is included in the firmware) + "tags": "", // comma separated tag list for searching + // common types are: + // 'clock' - it's a clock + // 'widget' - it is (or provides) a widget + // 'outdoors' - useful for outdoor activities + // 'tool' - a useful utility (timer, calculator, etc) + // 'game' - a game + // 'bluetooth' - uses Bluetooth LE + // 'system' - used by the system + // 'clkinfo' - provides or uses clock_info module for data on your clock face or clocks that support it (see apps/clock_info/README.md) + // 'health' - e.g. heart rate monitors or step counting + "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 + "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above) + "dependencies" : { "messages":"app" } // optional, depend on a specific app ID + // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' + "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' - see provides_modules + "dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets + "provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require' + "provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message' + "default" : true, // set if an app is the default implementer of something (a widget/module/etc) + "readme": "README.md", // if supplied, a link to a markdown-style text file + // that contains more information about this app (usage, etc) + // A 'Read more...' link will be added under the app + + "custom": "custom.html", // if supplied, apps/custom.html is loaded in an + // iframe, and it must post back an 'app' structure + // like this one with 'storage','name' and 'id' set up + // see below for more info + + "customConnect": true, // if supplied, ensure we are connected to a device + // before the "custom.html" iframe is loaded. An + // onInit function in "custom.html" is then called + // with info on the currently connected device. + + "interface": "interface.html", // if supplied, apps/interface.html is loaded in an + // iframe, and it may interact with the connected Bangle + // to retrieve information from it + // see below for more info + + "allow_emulator":true, // if 'app.js' will run in the emulator, set to true to + // add an icon to allow your app to be tested + + "storage": [ // list of files to add to storage + {"name":"appid.js", // filename to use in storage. + // If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file + "url":"", // URL of file to load (currently relative to apps/) + "content":"...", // if supplied, this content is loaded directly + "evaluate":true, // if supplied, data isn't quoted into a String before upload + // (eg it's evaluated as JS) + "noOverwrite":true // if supplied, this file will not be overwritten if it + // already exists + "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device + // types named in the array. This allows different versions of + // the app to be uploaded for different platforms + }, + ] + "data": [ // list of files the app writes to + {"name":"appid.data.json", // filename used in storage + "storageFile":true // if supplied, file is treated as storageFile + "url":"", // if supplied URL of file to load (currently relative to apps/) + "content":"...", // if supplied, this content is loaded directly + "evaluate":true, // if supplied, data isn't quoted into a String before upload + // (eg it's evaluated as JS) + }, + {"wildcard":"appid.data.*" // wildcard of filenames used in storage + }, // this is mutually exclusive with using "name" + ], + "sortorder" : 0, // optional - choose where in the list this goes. + // this should only really be used to put system + // stuff at the top +} +``` + +* 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`, `launcher`, `bluetooth` or empty. +* 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 + +### `metadata.json`: `custom` element + +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 +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 `metadata.json`: + +``` + + + + + +

+ + + + +``` + +This'll then be loaded in to the watch. See [apps/qrcode/grcode.html](the QR Code app) +for a clean example. + +**Note:** we specify a `url` for JS files even though it doesn't have to exist +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. + +### `metadata.json`: `interface` element + +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 +the app's description, and when clicked it opens the HTML page specified +in an iframe. + +``` + + + + + + +
Loading...
+ + + +``` + +When the page is ready a function called `onInit` is called, +and in that you can call `Puck.write` and `Puck.eval` to get +the data you require from Bangle.js. + +See [apps/gpsrec/interface.html](the GPS Recorder) for a full example. + +### Adding configuration to the "Settings" menu + +Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings". +To do so, the app needs to include a `settings.js` file, containing a single function +that handles configuring the app. +When the app settings are opened, this function is called with one +argument, `back`: a callback to return to the settings menu. + +Usually it will save any information in `myappid.json` where `myappid` is the name +of your app - so you should change the example accordingly. + +Example `settings.js` +```js +// make sure to enclose the function in parentheses +(function(back) { + let settings = require('Storage').readJSON('myappid.json',1)||{}; + if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value + function save(key, value) { + settings[key] = value; + require('Storage').write('myappid.json', settings); + } + const appMenu = { + '': {'title': 'App Settings'}, + '< Back': back, + 'Monkeys': { + value: settings.monkeys, + onchange: (m) => {save('monkeys', m)} + } + }; + E.showMenu(appMenu) +}) +``` +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", + ... + "storage": [ + ... + {"name":"myappid.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"myappid.json"} + ] + }, +``` + +## Modules + +You can include any of [Espruino's modules](https://www.espruino.com/Modules) as +normal with `require("modulename")`. To include [Bangle's modules](modules) for use in the Web +IDE, [upload the modules to internal storage](modules#upload-the-module-to-the-bangles-internal-storage) +or [change the IDE's search path](modules#change-the-web-ide-search-path-to-include-banglejs-modules). +If you want to develop your own module for your +app(s) then you can do that too. Just add the module into the `modules` folder +then you can use it from your app as normal. + +You won't be able to develop apps using your own modules with the IDE, +so instead we'd recommend you write your module to a Storage File called +`modulename` on Bangle.js. You can then develop your app as normal on Bangle.js +from the IDE. + +## Coding hints + +- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24" + +- 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 methods, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)` + +### Misc Notes + +- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `myappid.json`, then load it at startup. + +- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window. + +- Locale is handled by `require("locale")`. An app may create a `locale` file in Storage which is +a module that overwrites Bangle.js's default locale. + + +### 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 + +You 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 Bangle.js storage and output it back to the console + +## 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/app-icon.js b/app-icon.js deleted file mode 100644 index 3c6f27c44..000000000 --- a/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("qFQ4UB8H/AAIJBoGtqoACDZYPDCRwUGqATNgoTDoATNgISCqhtPio6QHgg6OHggJGn+q1X8PJAHFnwSBAAO8CYxiFgeq1/Agf61XAMgpiFnWvAof61hkFCYkD1YhEgfqAwkFOwk62EAhkCwEwgEOFAkBCYgfCnYTB9gvCCZECDwMshgGBmE4GAOACY8KHQIjBAAQTBh2gCYY6EgHwTIsPgA8EAAeogAeDGAcAlQSGgQRBE5EKKAYdDA4wfGYgo6HHgbKFgHrgB3BAA0OBgQAEDQw0Hcog5IHojyEgWwWAgAFncOOAkO4ATLgZbENYIAMJIk7CZo0ElcDCRfA9AFD9A8M0ErFgjlBABXwJQiyMWgyyMQwrGBmASLhjIDUgPs//wBoc8ZAwTEmGqKQiEDh2shATDdwMA16nE1ADCh4dBeAcCAYJlEgQTDYoQPCAYevFwQfBYAgTH0eqFAcKlATEhQTG2GsgeqgE4CYM6LQITHDoUD/8A9fq4E/SYI7HgfwO4fq1RVDCY2wgevT4krfgqLDWYXqI4IACE4cO1XMY4vsn41DgBSBBgX/bYugmD/HAAcMhQgDYogAJhRWFABk6XQkPCRfwnYFD9AtEAA+gSQgEEABPoAgYsEABLTDNAo8KAgakBDQgAFnbCBAwbwBCZbuDZAayMc4i0NWQgAB9cAIYhbEBgQaGHpCDBGg0KKwgAFgQeGA4XwBIr5BD5GoHg46BlQwH9QnJ1YGDoAeDO4MsBYc4O4IwDgITDgRsBlgiBFgITBnRODCYg8BXgM6gWADIMDHQgTFnQ8BhgTBmA6BTokBqDyE1blEgYvCAAUFCYgoB14FD/TEFgtUAwkD1Wv4ED/WqEwkAitVTIs+1QAC3gLFqoTGgE/CQP8BQwTBPAgALgITBMgoAKgoTBMgoAKMQI8QHQQ8QHQQACCZoSEChoPD")) diff --git a/app.js b/app.js deleted file mode 100644 index 30d03daed..000000000 --- a/app.js +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Regatta Timer - */ -const Layout = require("Layout"); -const locale = require("locale").name == "system" ? "en" : require("locale").name.substring(0, 2); -const hs = require("heatshrink"); - -// "Anton" bold font -Graphics.prototype.setFontAnton = function(scale) { - // Actual height 69 (68 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78 + (scale << 8) + (1 << 16)); -}; - -function Regattatimer() { - return { - layout: undefined, - /* - layouts: { - idle: function() { - switch(settings.dial) { - case "Discs": - break; - case "Numeric": - default: - break; - } - }, - start: function(phase) { - switch(settings.dial) { - case "Discs": - break; - case "Numeric": - default: - break; - } - }, - race: function() { - - } - }, - */ - mode: "idle", // idle, start, race" - countdown: 300, // 5 minutes - counter: undefined, - interval: undefined, - theme: null, - themes: { - "Light": { - "fgColor": "#000000", - "bgColor": "#FFFF00", - "icons": { - "satellites": function() { - return hs.decompress(atob("jEYxH+AH4Ab6QIIBJAfNAAQtSC4gxSCwgYHHBYYMC6IYPC5AZOC8QYMC5YYLC5inSDH4waVbAYJCpgA/AAI=")); - }, - "battery": function() { - return hs.decompress(atob("jEYxH+AHHSAAgXmCgoaRC/4X/C/4X/C/4X/C64Ap")); - } - } - }, - "Dark": { - "fgColor": "#FFFF00", - "bgColor": "#000000", - "icons": { - "satellites": function() { - return hs.decompress(atob("jEYxH+AH4Ab6QIIBJAfNAAQtSC4gxSCwgYHHBYYMC6IYPC5AZOC8QYMC5YYLC5inSDH4waVbAYJCpgA/AAI=")); - }, - "battery": function() { - return hs.decompress(atob("jEYxH+AHHSAAgXmCgoaRC/4X/C/4X/C/4X/C64Ap")); - } - } - } - }, - settings: Object.assign({ - "debug": false, - "buzzer": true, - "dial": "Numeric", - "gps": true, - "record": false, - "theme": "Dark", - }, require('Storage').readJSON("regattatimer.json", true) || {}), - - translations: Object.assign({ - "de": { - "speed": "FüG", // Fahrt über Grund - "speed_unit": "kn" - }, - "en": { - "speed": "SOA", // SOA speed of advance - "speed_unit": "kn" - } - }, require('Storage').readJSON("translations.json", true) || {}), - - init: function() { - - if(this.settings.debug) { - this.countdown = 1; - } - - this.theme = this.themes[this.settings.theme]; - - Bangle.setLCDPower(1); - Bangle.setLCDTimeout(0); - - // in "idle", "start" or "stoped" mode, a button click (re)starts the countdown - // in "race" mode, a button click stops the counter - var onButtonClick = (function(ev) { - switch(this.mode) { - case "idle": - this.resetCounter(); - this.mode = "start"; - this.setLayoutStartMinSec(); - this.startCounter(); - this.interval = setInterval((function() { - this.startCounter(); - }).bind(this), 1000); - break; - case "stoped": - case "start": - this.resetCounter(); - this.setLayoutIdle(); - break; - case "race": - this.raceCounterStop(); - break; - } - }).bind(this); - - setWatch(onButtonClick, BTN1, true); - - this.setLayoutIdle(); - }, - - onGPS: function(fix) { - if(this.mode == "race") { - if(fix.fix && isFinite(fix.speed)) { - this.layout.clear(layout.speed); - this.layout.speed.label = fix.speed.toFixed(2); - this.layout.render(this.layout.speed); - } - this.layout.satellites.label = fix.satellites; - } - }, - - translate: function(slug) { - return this.translations[locale][slug]; - }, - // during the start phase, the clock counts down 5 4 1 0 minutes - // a button click restarts the countdown - startCounter: function() { - - this.counter --; - - if(this.counter >= 0) { - var counterMinutes = parseInt(this.counter / 60); - - if(counterMinutes > 0) { - this.layout.minutes.label = counterMinutes; - // this.layout.seconds.label = "0".concat(this.counter - counterMinutes * 60).toString().slice(-2); - this.layout.seconds.label = this.padZeroLeft(this.counter - counterMinutes * 60); - this.layout.render(); - } - else { - this.setLayoutStartSec(); - this.layout.seconds.label = this.counter.toString(); - this.layout.render(); - } - // this keeps the watch LCD lit up - g.flip(); - } - // time is up - else { - this.raceCounterStart(); - } - }, - padZeroLeft: function(str) { - return str.toString().padStart(2, "0"); - }, - formatTime: function(time) { - var - minutes = parseInt(time / 60), - seconds = time - (minutes * 60); - - return this.padZeroLeft(parseInt(time / 3600)) + ":" + this.padZeroLeft(minutes) + ":" + this.padZeroLeft(seconds); - }, - raceCounter: function() { - - if(this.counter % 60 == 0) { - this.layout.clear(this.layout.battery); - this.layout.battery.label = E.getBattery() + "%"; - this.layout.render(this.layout.battery); - } - - this.counter ++; - - this.layout.racetime.label = this.formatTime(this.counter); - this.layout.daytime.label = require("locale").time(new Date(), 1); - this.layout.render(); - - // keeps the watch screen lit up - g.flip(); - }, - raceCounterStop: function() { - if(this.interval) { - clearInterval(this.interval); - this.interval = undefined; - } - this.mode = "stoped"; - }, - raceCounterStart: function() { - if(this.interval) { - clearInterval(this.interval); - this.interval = undefined; - } - - if(this.settings.buzzer) { - Bangle.buzz(); - } - - this.counter = 0; - // switch to race mode - this.mode = "race"; - this.setLayoutRace(); - this.raceCounter(); - this.interval = setInterval((function() { - this.raceCounter(); - }).bind(this), 1000); - }, - - resetCounter: function() { - if(this.interval) { - clearInterval(this.interval); - this.interval = undefined; - } - this.counter = this.countdown; - }, - - setLayoutIdle: function() { - - g.clear(); - - this.mode = "idle"; - - this.layout = new Layout({ - type: "v", - bgCol: this.theme.bgColor, - c: [ - { - type: "v", - c: [ - {type: "txt", font: "Anton", label: "5", col: this.theme.fgColor, id: "minutes", fillx: 1, filly: 1}, - {type: "txt", font: "20%", label: "--:--", col: this.theme.fgColor, id: "daytime", fillx: 1, filly: 1} - ] - } - ]}, {lazy: true}); - - this.interval = setInterval((function() { - this.layout.daytime.label = require("locale").time(new Date(), 1); - this.layout.render(); - - // keeps the watch screen lit up - g.flip(); - }).bind(this), 1000); - }, - setLayoutStartMinSec: function() { - g.clear(); - - this.layout = new Layout({ - type: "v", - bgCol: this.theme.bgColor, - c: [ - { - type: "h", - c: [ - {type: "txt", font: "Anton", label: "4", col: this.theme.fgColor, id: "minutes", fillx: 1, filly: 1}, - {type: "txt", font: "Anton", label: "59", col: this.theme.fgColor, id: "seconds", fillx: 1, filly: 1}, - ] - } - ]}, {lazy: true} - ); - }, - setLayoutStartSec: function() { - g.clear(); - - this.layout = new Layout({ - type: "v", - bgCol: this.theme.bgColor, - c:[ - {type: "txt", font: "Anton", label: "", fillx: true, filly: true, col: this.theme.fgColor, id: "seconds"}, - ]}, {lazy: true}); - }, - setLayoutRace: function() { - g.clear(); - - this.layout = new Layout({ - type: "v", - bgCol: this.theme.bgColor, - c: [ - {type: "txt", font: "20%", label: "00:00:00", col: this.theme.fgColor, pad: 4, filly: 1, fillx: 1, id: "racetime"}, - {type: "txt", font: "15%", label: "-", col: this.theme.fgColor, pad: 4, filly:1, fillx:1, id: "daytime"}, - // horizontal - {type: "h", c: [ - {type: "txt", font: "10%", label: this.translate("speed"), col: this.theme.fgColor, pad:4, fillx:1, filly:1}, - {type: "txt", font: "20%", label: "0", col: this.theme.fgColor, pad:4, fillx:1, filly:1, id: "speed"}, - {type: "txt", font: "10%", label: this.translate("speed_unit"), col: this.theme.fgColor, pad:4, fillx:1, filly:1}, - ]}, - {type: "h", c: [ - {type:"img", pad: 2, src: this.theme["icons"].satellites()}, - {type: "txt", font: "10%", label: "0", col: this.theme.fgColor, pad: 2, filly:1, id: "satellites"}, - // hacky, use empty element with fillx to push the other elments to the left an right side - {type: undefined, pad: 2, fillx: 1}, - {type:"img", pad: 2, src: this.theme["icons"].battery()}, - {type: "txt", font: "10%", label: "-", col: this.theme.fgColor, pad: 2, filly: 1, id: "battery"}, - ]} - ]}, {lazy: true}); - } - }; -} - -var regattatimer = Regattatimer(); -regattatimer.init(); - -if(regattatimer.settings.gps) { - Bangle.setGPSPower(1); - Bangle.on('GPS', regattatimer.onGPS.bind(regattatimer)); -} - -Bangle.on('kill', function() { - Bangle.setLCDPower(0); - Bangle.setLCDTimeout(10); - - if(regattatimer.settings.gps) { - Bangle.setGPSPower(0); - } -}); - diff --git a/icon.png b/icon.png deleted file mode 100644 index 47712c7ed12a2f1f18797165d6b063069aa6f7b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2990 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 diff --git a/settings.js b/settings.js deleted file mode 100644 index cdcdf53f5..000000000 --- a/settings.js +++ /dev/null @@ -1,78 +0,0 @@ -(function(back) { - var - file = "regattatimer.json", - - storage = require("Storage"), - - dials = ["Numeric", "Discs"], - - themes = ["Light", "Dark"], - - settings = Object.assign({ - "debug": false, - "buzzer": true, - "dial": "Numeric", - "gps": true, - "record": false, - "theme": "Dark", - }, storage.readJSON(file, true) || {}); - - function save(key, value) { - settings[key] = value; - storage.writeJSON(file, settings); - } - - E.showMenu({ - "" : { "title" : "Regatta Timer" }, - "< Back" : () => back(), - "GPS": { - value: !!settings.gps, // !! converts undefined to false - onchange: v => { - save("gps", v); - } - }, - "THEME": { - value: themes.indexOf(settings.theme), - min: 0, - max: themes.length - 1, - step: 1, - wrap: true, - format: v => themes[v], - onchange: (d) => { - save("theme", themes[d]); - } - }, - "BUZZER": { - value: !!settings.buzzer, // !! converts undefined to false - onchange: v => { - save("buzzer", v); - } - }, - /* - "DIAL": { - value: dials.indexOf(settings.dial), - min: 0, - max: dials.length - 1, - step: 1, - wrap: true, - format: v => dials[v], - onchange: (d) => { - save("dial", dials[d]); - } - }, - "RECORD": { - value: !!settings.record, // 0| converts undefined to 0 - onchange: v => { - settings.record = v; - save("record", v); - } - }, - */ - "DEBUG": { - value: !!settings.debug, // 0| converts undefined to 0 - onchange: v => { - save("debug", v); - } - }, - }); -})(load)